如何获取某个位置周围的郊区列表,然后使用 MySql 重复其他位置?
How to get a list of suburbs surrounding a location then repeat for other locations using MySql?
我使用查询 A.
获得了距单个位置指定距离内的郊区列表
我正在尝试调整查询 A 以获取 location1 周围的郊区列表,然后获取 location2 周围的郊区列表等等(我将其称为 Queries B ).本质上,查询 B 与查询 A 执行相同的操作,但对每个单独的位置重复它。 我的问题 - 我怎样才能只使用 MySQL 来做到这一点。非常感谢有关如何执行此操作的建议。
这是我正在处理的数据示例。 SQLFiddle here
CREATE TABLE `geoname` (
`geonameid` INT(11) NOT NULL,
`asciiname` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`country` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`latitude` DECIMAL(10,7) NULL DEFAULT NULL,
`longitude` DECIMAL(10,7) NULL DEFAULT NULL,
`fcode` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`population` INT(11) NULL DEFAULT NULL,
`area` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`geonameid`),
INDEX `asciiname` (`asciiname`),
INDEX `country` (`country`),
INDEX `latitude` (`latitude`),
INDEX `longitude` (`longitude`),
INDEX `fcode` (`fcode`),
INDEX `population` (`population`),
INDEX `area` (`area`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
INSERT INTO geoname(geonameid, asciiname, country, latitude, longitude, fcode, population, area) VALUES
(2147497, 'Tamworth', 'AU', -31.0904800, 150.9290500, 'PPL', 47597, 72),
(8597559, 'Tamworth', 'AU', -21.0457400, 143.6685200, 'PPL', 0, 0),
(8805708, 'Tamworth', 'AU', -21.0471300, 143.6692000, 'HMSD', 0, 0),
(2655603, 'Birmingham', 'GB', 52.4814200, -1.8998300, 'PPL', 984333, 599),
(4782167, 'Roanoke', 'US', 37.2709700, -79.9414300, 'PPL', 97032, 321),
(10114336, 'East Tamworth', 'AU', -31.0854800, 150.9372100, 'PPLX', 2621, 0),
(10114337, 'North Tamworth', 'AU', -31.0786200, 150.9221900, 'PPPL', 0, 0),
(2143940, 'West Tamworth', 'AU', -31.1023600, 150.9144700, 'PPLX', 0, 0),
(2656867, 'Aston', 'GB', 52.5000000, -1.8833300, 'PPLX', 0, 0),
(2646814, 'Hockley', 'GB', 52.5000000, -1.9166700, 'PPLX', 13919, 0),
(2650236, 'Edgbaston', 'GB', 52.4623000, -1.9211500, 'PPLX', 0, 0),
(4754994, 'Cumberland Forest', 'US', 37.1401300, -80.3217100, 'PPLX', 0, 0),
(4774999, 'Mountain Top Estates', 'US', 37.1376300, -80.3247700, 'PPPL', 0, 0),
(4764119, 'Highland Park', 'US', 37.2237400, -80.3917200, 'PPLX', 0, 0);
我试过的
查询 A- 获取单个兴趣点周围的郊区
SELECT @lat := latitude, @lng :=longitude FROM geoname WHERE asciiname = 'Tamworth' and country='AU' and population>0 and fcode='PPL';
SELECT
name as suburb, 'Tamworth' as point_of_interest, country,
(
(
ACOS(SIN(@lat * PI() / 180) * SIN(latitude * PI() / 180) + COS(@lat * PI() / 180) * COS(latitude * PI() / 180) * COS((
@lng - longitude
) * PI() / 180)) * 180 / PI()
) * 60 * 1.851999999962112
) AS distance
FROM geoname
WHERE fcode='PPLX' OR fcode='PPPL'
HAVING distance <= '60'
ORDER BY distance ASC;
结果
上面的查询 returns 一个兴趣点的位置。
+---------------------------------+
| @lat | @lng |
+---------------------------------+
| 52.6339900 | -1.6958700 |
+---------------------------------+
以及塔姆沃思周边郊区的列表。
| point_of_interest | suburb | country | distance |
|-------------------|----------------------|---------|--------------------|
| Tamworth | East Tamworth | AU | 0.9548077598752538 |
| Tamworth | North Tamworth | AU | 1.4707125875055387 |
| Tamworth | West Tamworth | AU | 1.915025922482298 |
我尝试使用 MySQL 用户变量 GROUP_CONCAT()
和 FIND_IN_SET()
创建 查询 B。我的想法是我可以像使用数组一样循环遍历这些值。如果你愿意,我可以 post 我最后一次尝试,但我什至离解决方案还差得很远(不是因为缺乏尝试)。
更新:这是我最后的尝试之一。
SELECT @lat := GROUP_CONCAT(latitude), @lng :=GROUP_CONCAT(longitude), @city :=GROUP_CONCAT(asciiname), @area :=GROUP_CONCAT(area) FROM geoname WHERE (asciiname = 'Tamworth' or asciiname = 'Birmingham' or asciiname = 'Roanoke') and population>0 and fcode='PPL';
SELECT
FIND_IN_SET(asciiname, @city) as point_of_interest, asciiname as suburb, country,
(
(
ACOS(SIN(FIND_IN_SET(latitude, @lat) * PI() / 180) * SIN(latitude * PI() / 180) + COS(FIND_IN_SET(latitude, @lat) * PI() / 180) * COS(latitude * PI() / 180) * COS((
FIND_IN_SET(longitude, @lng) - longitude
) * PI() / 180)) * 180 / PI()
) * 60 * 1.851999999962112
) AS distance
FROM geoname
HAVING distance <= FIND_IN_SET(distance, @area)
ORDER BY distance ASC;
查询 B 的预期结果。 对于 3 个兴趣点 - 塔姆沃思、伯明翰和罗阿诺克 - 这是我希望看到的结果。
| point_of_interest | suburb | country | distance |
|-------------------|----------------------|---------|--------------------|
| Tamworth | East Tamworth | AU | 0.9548077598752538 |
| Tamworth | North Tamworth | AU | 1.4707125875055387 |
| Tamworth | West Tamworth | AU | 1.915025922482298 |
| Birmingham | Aston | GB | 2.347111909955497 |
| Birmingham | Hockley | GB | 2.3581405942861164 |
| Birmingham | Edgbaston | GB | 2.568384753388139 |
| Roanoke | Cumberland Forest | US | 36.66226789588173 |
| Roanoke | Mountain Top Estates | US | 37.02185777044897 |
| Roanoke | Highland Park | US | 40.174566427830094 |
非常感谢有关如何使用 MySQL 执行此操作的建议。
您只需要执行自连接。 Joining 表格是 SQL 的一个 非常 的基础部分——你 真的 在试图理解它之前应该阅读它进一步回答。
SELECT poi.asciiname,
suburb.asciiname,
suburb.country,
DEGREES(
ACOS(
SIN(RADIANS( poi.latitude))
* SIN(RADIANS(suburb.latitude))
+ COS(RADIANS( poi.latitude))
* COS(RADIANS(suburb.latitude))
* COS(RADIANS(poi.longitude - suburb.longitude))
)
) * 60 * 1.852 AS distance
FROM geoname AS poi
JOIN geoname AS suburb
WHERE poi.asciiname IN ('Tamworth', 'Birmingham', 'Roanoke')
AND poi.population > 0
AND poi.fcode = 'PPL'
AND suburb.fcode IN ('PPLX', 'PPPL')
HAVING distance <= 60
ORDER BY poi.asciiname, distance
在 sqlfiddle 上查看。
您会注意到我使用 MySQL 的 IN()
运算符作为 value = A OR value = B OR ...
的 shorthand。
您还会注意到我使用了 MySQL 的 DEGREES()
and RADIANS()
函数,而不是尝试显式执行此类转换。
然后您将纬度的分钟数乘以 1.851999999962112
的系数,这很奇怪:它非常接近 1.852
,这是一海里的精确公里数(历史上定义为一分钟的纬度),但奇怪的是略有不同——我假设你打算用它来代替。
最后,您得到了作为字符串过滤结果集中距离的文字值,即 '60'
,而显然这是一个数值,应该不加引号。
Using Spatial Data Types.
首先,如果您有大量地理空间数据,您应该使用 mysql 的地理空间扩展而不是这样的计算。然后,您可以创建空间索引来加快许多查询的速度,而不必像上面那样编写冗长的查询。
使用与 ST_Distance 的比较或创建具有感兴趣半径的几何体以及 ST_within 可能会给您带来良好的结果,并且可能比当前的速度快得多。然而,实现这一目标的最佳和最快方法 ST_Dwithin 尚未在 mysql.
中实现
这些数据类型在 mysql 5.7 及更高版本中可用,但如果您使用的是旧版本,那么升级您的数据库是完全值得的。
新的 table 结构。
CREATE TABLE `geoname2` (
`geonameid` INT(11) NOT NULL,
`asciiname` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`country` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`pt` POINT,
`fcode` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`population` INT(11) NULL DEFAULT NULL,
`area` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`geonameid`),
INDEX `asciiname` (`asciiname`),
INDEX `country` (`country`),
INDEX `fcode` (`fcode`),
INDEX `population` (`population`),
INDEX `area` (`area`),
SPATIAL INDEX `pt` (`pt`)
)COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;
请注意,latitude
和 longitude
字段已被 pt
替换,它们的索引已被单个索引替换。
新查询A
SELECT asciiname as suburb, 'Tamworth' as point_of_interest, country,
ST_DISTANCE(`pt`, POINT(@lat,@lng)) as distance
FROM geoname2
WHERE (fcode='PPLX' OR fcode='PPPL') AND ST_DISTANCE(`pt`, POINT(@lat,@lng)) <= 1
ORDER BY distance ASC;
显然要简单得多。它可能也更快但是只有 14 条记录要测试很难得出任何结论,没有索引将用于这么小的 tables.
请注意,ST_DISTANCE 结果以度为单位返回,通常假设 1 度大约为 60 英里或 111 公里(您在计算中已这样做)
顺便说一句,在现有的设置中,您确实有一个关于纬度和经度的索引,但请注意 mysql 每个 table 只能使用一个索引,所以如果您不采用地理空间查询您可能希望将其转换为 latitude,longitude
.
上的单个复合索引
完整查询。
现在可以按如下方式修改上述查询,以提供新形式的 'query B'。
SELECT DISTINCT g1.asciiname, g2.asciiname ,ST_DISTANCE(g1.pt, g2.pt) *111 as distance FROM geoname2 g1
INNER JOIN (SELECT `pt`, asciiname
FROM geoname2
WHERE (fcode='PPLX' OR fcode='PPPL') AND
ST_DISTANCE(`pt`, POINT(@lat,@lng)) <= 1) as g2
WHERE ST_DISTANCE(g1.pt,g2.pt) < 1
AND g1.asciiname != g2.asciiname ORDER BY distance ASC;
再次注意,我假设 1 度(彼此接近约 111 公里)
我使用查询 A.
获得了距单个位置指定距离内的郊区列表我正在尝试调整查询 A 以获取 location1 周围的郊区列表,然后获取 location2 周围的郊区列表等等(我将其称为 Queries B ).本质上,查询 B 与查询 A 执行相同的操作,但对每个单独的位置重复它。 我的问题 - 我怎样才能只使用 MySQL 来做到这一点。非常感谢有关如何执行此操作的建议。
这是我正在处理的数据示例。 SQLFiddle here
CREATE TABLE `geoname` (
`geonameid` INT(11) NOT NULL,
`asciiname` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`country` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`latitude` DECIMAL(10,7) NULL DEFAULT NULL,
`longitude` DECIMAL(10,7) NULL DEFAULT NULL,
`fcode` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`population` INT(11) NULL DEFAULT NULL,
`area` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`geonameid`),
INDEX `asciiname` (`asciiname`),
INDEX `country` (`country`),
INDEX `latitude` (`latitude`),
INDEX `longitude` (`longitude`),
INDEX `fcode` (`fcode`),
INDEX `population` (`population`),
INDEX `area` (`area`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
INSERT INTO geoname(geonameid, asciiname, country, latitude, longitude, fcode, population, area) VALUES
(2147497, 'Tamworth', 'AU', -31.0904800, 150.9290500, 'PPL', 47597, 72),
(8597559, 'Tamworth', 'AU', -21.0457400, 143.6685200, 'PPL', 0, 0),
(8805708, 'Tamworth', 'AU', -21.0471300, 143.6692000, 'HMSD', 0, 0),
(2655603, 'Birmingham', 'GB', 52.4814200, -1.8998300, 'PPL', 984333, 599),
(4782167, 'Roanoke', 'US', 37.2709700, -79.9414300, 'PPL', 97032, 321),
(10114336, 'East Tamworth', 'AU', -31.0854800, 150.9372100, 'PPLX', 2621, 0),
(10114337, 'North Tamworth', 'AU', -31.0786200, 150.9221900, 'PPPL', 0, 0),
(2143940, 'West Tamworth', 'AU', -31.1023600, 150.9144700, 'PPLX', 0, 0),
(2656867, 'Aston', 'GB', 52.5000000, -1.8833300, 'PPLX', 0, 0),
(2646814, 'Hockley', 'GB', 52.5000000, -1.9166700, 'PPLX', 13919, 0),
(2650236, 'Edgbaston', 'GB', 52.4623000, -1.9211500, 'PPLX', 0, 0),
(4754994, 'Cumberland Forest', 'US', 37.1401300, -80.3217100, 'PPLX', 0, 0),
(4774999, 'Mountain Top Estates', 'US', 37.1376300, -80.3247700, 'PPPL', 0, 0),
(4764119, 'Highland Park', 'US', 37.2237400, -80.3917200, 'PPLX', 0, 0);
我试过的
查询 A- 获取单个兴趣点周围的郊区
SELECT @lat := latitude, @lng :=longitude FROM geoname WHERE asciiname = 'Tamworth' and country='AU' and population>0 and fcode='PPL';
SELECT
name as suburb, 'Tamworth' as point_of_interest, country,
(
(
ACOS(SIN(@lat * PI() / 180) * SIN(latitude * PI() / 180) + COS(@lat * PI() / 180) * COS(latitude * PI() / 180) * COS((
@lng - longitude
) * PI() / 180)) * 180 / PI()
) * 60 * 1.851999999962112
) AS distance
FROM geoname
WHERE fcode='PPLX' OR fcode='PPPL'
HAVING distance <= '60'
ORDER BY distance ASC;
结果
上面的查询 returns 一个兴趣点的位置。
+---------------------------------+
| @lat | @lng |
+---------------------------------+
| 52.6339900 | -1.6958700 |
+---------------------------------+
以及塔姆沃思周边郊区的列表。
| point_of_interest | suburb | country | distance |
|-------------------|----------------------|---------|--------------------|
| Tamworth | East Tamworth | AU | 0.9548077598752538 |
| Tamworth | North Tamworth | AU | 1.4707125875055387 |
| Tamworth | West Tamworth | AU | 1.915025922482298 |
我尝试使用 MySQL 用户变量 GROUP_CONCAT()
和 FIND_IN_SET()
创建 查询 B。我的想法是我可以像使用数组一样循环遍历这些值。如果你愿意,我可以 post 我最后一次尝试,但我什至离解决方案还差得很远(不是因为缺乏尝试)。
更新:这是我最后的尝试之一。
SELECT @lat := GROUP_CONCAT(latitude), @lng :=GROUP_CONCAT(longitude), @city :=GROUP_CONCAT(asciiname), @area :=GROUP_CONCAT(area) FROM geoname WHERE (asciiname = 'Tamworth' or asciiname = 'Birmingham' or asciiname = 'Roanoke') and population>0 and fcode='PPL';
SELECT
FIND_IN_SET(asciiname, @city) as point_of_interest, asciiname as suburb, country,
(
(
ACOS(SIN(FIND_IN_SET(latitude, @lat) * PI() / 180) * SIN(latitude * PI() / 180) + COS(FIND_IN_SET(latitude, @lat) * PI() / 180) * COS(latitude * PI() / 180) * COS((
FIND_IN_SET(longitude, @lng) - longitude
) * PI() / 180)) * 180 / PI()
) * 60 * 1.851999999962112
) AS distance
FROM geoname
HAVING distance <= FIND_IN_SET(distance, @area)
ORDER BY distance ASC;
查询 B 的预期结果。 对于 3 个兴趣点 - 塔姆沃思、伯明翰和罗阿诺克 - 这是我希望看到的结果。
| point_of_interest | suburb | country | distance |
|-------------------|----------------------|---------|--------------------|
| Tamworth | East Tamworth | AU | 0.9548077598752538 |
| Tamworth | North Tamworth | AU | 1.4707125875055387 |
| Tamworth | West Tamworth | AU | 1.915025922482298 |
| Birmingham | Aston | GB | 2.347111909955497 |
| Birmingham | Hockley | GB | 2.3581405942861164 |
| Birmingham | Edgbaston | GB | 2.568384753388139 |
| Roanoke | Cumberland Forest | US | 36.66226789588173 |
| Roanoke | Mountain Top Estates | US | 37.02185777044897 |
| Roanoke | Highland Park | US | 40.174566427830094 |
非常感谢有关如何使用 MySQL 执行此操作的建议。
您只需要执行自连接。 Joining 表格是 SQL 的一个 非常 的基础部分——你 真的 在试图理解它之前应该阅读它进一步回答。
SELECT poi.asciiname,
suburb.asciiname,
suburb.country,
DEGREES(
ACOS(
SIN(RADIANS( poi.latitude))
* SIN(RADIANS(suburb.latitude))
+ COS(RADIANS( poi.latitude))
* COS(RADIANS(suburb.latitude))
* COS(RADIANS(poi.longitude - suburb.longitude))
)
) * 60 * 1.852 AS distance
FROM geoname AS poi
JOIN geoname AS suburb
WHERE poi.asciiname IN ('Tamworth', 'Birmingham', 'Roanoke')
AND poi.population > 0
AND poi.fcode = 'PPL'
AND suburb.fcode IN ('PPLX', 'PPPL')
HAVING distance <= 60
ORDER BY poi.asciiname, distance
在 sqlfiddle 上查看。
您会注意到我使用 MySQL 的 IN()
运算符作为 value = A OR value = B OR ...
的 shorthand。
您还会注意到我使用了 MySQL 的 DEGREES()
and RADIANS()
函数,而不是尝试显式执行此类转换。
然后您将纬度的分钟数乘以 1.851999999962112
的系数,这很奇怪:它非常接近 1.852
,这是一海里的精确公里数(历史上定义为一分钟的纬度),但奇怪的是略有不同——我假设你打算用它来代替。
最后,您得到了作为字符串过滤结果集中距离的文字值,即 '60'
,而显然这是一个数值,应该不加引号。
Using Spatial Data Types.
首先,如果您有大量地理空间数据,您应该使用 mysql 的地理空间扩展而不是这样的计算。然后,您可以创建空间索引来加快许多查询的速度,而不必像上面那样编写冗长的查询。
使用与 ST_Distance 的比较或创建具有感兴趣半径的几何体以及 ST_within 可能会给您带来良好的结果,并且可能比当前的速度快得多。然而,实现这一目标的最佳和最快方法 ST_Dwithin 尚未在 mysql.
中实现这些数据类型在 mysql 5.7 及更高版本中可用,但如果您使用的是旧版本,那么升级您的数据库是完全值得的。
新的 table 结构。
CREATE TABLE `geoname2` (
`geonameid` INT(11) NOT NULL,
`asciiname` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`country` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`pt` POINT,
`fcode` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`population` INT(11) NULL DEFAULT NULL,
`area` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`geonameid`),
INDEX `asciiname` (`asciiname`),
INDEX `country` (`country`),
INDEX `fcode` (`fcode`),
INDEX `population` (`population`),
INDEX `area` (`area`),
SPATIAL INDEX `pt` (`pt`)
)COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;
请注意,latitude
和 longitude
字段已被 pt
替换,它们的索引已被单个索引替换。
新查询A
SELECT asciiname as suburb, 'Tamworth' as point_of_interest, country,
ST_DISTANCE(`pt`, POINT(@lat,@lng)) as distance
FROM geoname2
WHERE (fcode='PPLX' OR fcode='PPPL') AND ST_DISTANCE(`pt`, POINT(@lat,@lng)) <= 1
ORDER BY distance ASC;
显然要简单得多。它可能也更快但是只有 14 条记录要测试很难得出任何结论,没有索引将用于这么小的 tables.
请注意,ST_DISTANCE 结果以度为单位返回,通常假设 1 度大约为 60 英里或 111 公里(您在计算中已这样做)
顺便说一句,在现有的设置中,您确实有一个关于纬度和经度的索引,但请注意 mysql 每个 table 只能使用一个索引,所以如果您不采用地理空间查询您可能希望将其转换为 latitude,longitude
.
完整查询。
现在可以按如下方式修改上述查询,以提供新形式的 'query B'。
SELECT DISTINCT g1.asciiname, g2.asciiname ,ST_DISTANCE(g1.pt, g2.pt) *111 as distance FROM geoname2 g1
INNER JOIN (SELECT `pt`, asciiname
FROM geoname2
WHERE (fcode='PPLX' OR fcode='PPPL') AND
ST_DISTANCE(`pt`, POINT(@lat,@lng)) <= 1) as g2
WHERE ST_DISTANCE(g1.pt,g2.pt) < 1
AND g1.asciiname != g2.asciiname ORDER BY distance ASC;
再次注意,我假设 1 度(彼此接近约 111 公里)