1

我使用Queries A获得了距单个位置指定距离内的郊区列表。

我正在尝试调整查询 A 以获取 location1 周围的郊区列表,然后获取 location2 周围的郊区列表,依此类推(我将其称为Queries B)。本质上,查询 B 与查询 A 执行相同的操作,但针对每个单独的位置重复此操作。我的问题 - 我怎样才能只使用 MySQL 来做到这一点。非常感谢有关如何执行此操作的建议。


这是我正在使用的数据示例。SqlFiddle在这里

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;

结果

上面的查询返回兴趣点的一个位置。

+---------------------------------+
|     @lat       |      @lng      |
+---------------------------------+
| 52.6339900     |   -1.6958700   |
+---------------------------------+

以及 Tamworth 周边的郊区列表。

    | 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 用户变量创建查询 BGROUP_CONCAT() ,并且FIND_IN_SET(). 这个想法是我可以像使用数组一样循环遍历这些值。如果您愿意,我可以发布我的最后一次尝试,但我什至没有接近解决方案(不是因为缺乏尝试)。

更新:这是我最后的尝试之一。

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 个兴趣点——Tamworth、Birmingham 和 Roanoke——这是我希望看到的。

| 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 执行此操作的建议。

4

2 回答 2

3

您只需要执行自联接。 连接表是 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 ....

您还会注意到我使用了 MySQL 的DEGREES()RADIANS()函数,而不是尝试显式地执行此类转换。

然后,您将纬度分钟乘以1.851999999962112,这很奇怪:它非常接近1.852,这是海里的精确公里数(历史上定义为一分钟纬度),但奇怪的是略有不同 - 我已经假设您打算使用它。

最后,您拥有将结果集中的距离过滤为字符串的文字值,即'60',而显然这是一个数值,应该不加引号。

于 2016-08-01T12:09:40.877 回答
1

使用空间数据类型

首先,如果你有很多地理空间数据,你应该使用 mysql 的地理空间扩展而不是这样的计算。然后,您可以创建可以加快许多查询的空间索引,并且您不必像上面那样编写冗长的查询。

使用与 ST_Distance 的比较或创建具有兴趣半径的几何图形以及 ST_within 可能会给您带来良好的结果,并且可能比当前快很多。然而,实现这一目标的最好和最快的方法是,ST_Dwithin 尚未在 mysql 中实现。

这些数据类型在 mysql 5.7 及更高版本中可用,但如果您使用的是旧版本,则完全值得努力升级您的数据库。

新的表结构。

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;

请注意,latitudeandlongitude字段已被替换,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 条记录要测试,很难得出任何结论,这样的小表不会使用索引。

请注意,ST_DISTANCE 结果以度为单位返回,通常假设 1 度约为 60 英里或 111 公里(您在计算中已经这样做了)

顺便说一句,在现有设置中,您确实有一个关于纬度和经度的索引,但请注意 mysql 每个表只能使用一个索引,因此如果您不采用地理空间查询,您可能希望将其转换为latitude,longitude.

完整的查询。

现在可以将上述查询修改如下,以提供新形式的“查询 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 公里)

于 2016-08-04T15:39:37.160 回答