SQL查询位置半径内的总点数

时间:2009-11-17 20:54:44

标签: mysql geolocation query-optimization

我有一个美国所有邮政编码的数据库表,其中包括城市,州,纬度和地区。每个邮政编码的经度。我还有一个数据库表,每个点都有纬度和数据。与他们相关的经度。我希望能够使用1个MySQL查询为我提供zipcodes表中所有唯一城市/州组合的列表,以及该城市/州的给定半径内的总点数。我可以使用以下查询获取唯一的城市/州名单:

select city,state,latitude,longitude
from zipcodes 
group by city,state order by state,city;

我可以使用以下查询获得纬度为“$ lat”和经度为“$ lon”的特定城市100英里范围内的点数:

select count(*) 
from points 
where (3959 * acos(cos(radians($lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians($lon)) + sin(radians($lat)) * sin(radians(latitude)))) < 100;

我无法做到的是弄清楚如何以不杀死我的数据库的方式组合这些查询。这是我悲伤的尝试之一:

select city,state,latitude,longitude,
    (select count(*) from points
     where status="A" AND 
          (3959 * acos(cos(radians(zipcodes.latitude)) * cos(radians(latitude)) * cos(radians(longitude) - radians(zipcodes.longitude)) + sin(radians(zipcodes.latitude)) * sin(radians(latitude)))) < 100) as 'points' 
from zipcodes 
group by city,state order by state,city;

表格目前有以下索引:

Zipcodes - `zip` (zip)
Zipcodes - `location` (state,city)
Points - `status_length_location` (status,length,longitude,latitude)

当我在上一次MySQL查询之前运行explain时,这里是输出:

+----+--------------------+----------+------+------------------------+------------------------+---------+-------+-------+---------------------------------+
| id | select_type        | table    | type | possible_keys          | key                    | key_len | ref   | rows  | Extra                           |
+----+--------------------+----------+------+------------------------+------------------------+---------+-------+-------+---------------------------------+
|  1 | PRIMARY            | zipcodes | ALL  | NULL                   | NULL                   | NULL    | NULL  | 43187 | Using temporary; Using filesort | 
|  2 | DEPENDENT SUBQUERY | points   | ref  | status_length_location | status_length_location | 2       | const | 16473 | Using where; Using index        | 
+----+--------------------+----------+------+------------------------+------------------------+---------+-------+-------+---------------------------------+

我知道我可以循环遍历所有的zipcode并计算给定半径内匹配点的数量但是points表会一直在增长,而我宁愿在zipcodes数据库中没有过时的总点数。我希望那里的MySQL大师可以向我展示我的方式的错误。在此先感谢您的帮助!

3 个答案:

答案 0 :(得分:7)

MySQL Guru与否,问题是除非你找到一种过滤各行的方法,否则需要在每个点和每个城市之间计算距离......

有两种可能有助于这种情况的一般方法

  • 使距离公式更简单
  • 将不太可能的候选人过滤到给定城市的100k半径

在进入这两条改进途径之前,你应该决定这100英里距离所需的精度水平,你也应该指出数据库涵盖哪个地理区域(这只是美国大陆等等。 / p>

这样做的原因是,虽然数字更精确,但大圆公式在计算上非常昂贵。性能改进的另一个途径是将“网格坐标”存储在附加(或代替)Lat / Long坐标中。

修改
关于更简单(但不太精确)公式的一些想法
由于我们处理相对较小的距离,(我猜测在Lat和30之间的纬度为30到48度),我们可以使用欧氏距离(或者更好的是欧氏距离的平方)而不是更复杂的球形三角公式。
取决于预期的精度水平,对于完整经度的线性距离,可以接受单个参数,在所考虑的区域内取平均值(例如大约46 法规英里) 。然后公式将成为

  LatDegInMi = 69.0
  LongDegInMi = 46.0
  DistSquared = ((Lat1 - Lat2) * LatDegInMi) ^2 + ((Long1 - Long2) * LongDegInMi) ^2

根据网格信息进行过滤以限制行数的列的想法。 系统中的每个“点”,无论是城市,还是其他点(?交付地点,商店位置......等等)都会被分配两个整数坐标,这些坐标定义了点所在的25英里* 25英里的平方。距参考点(给定城市)100英里内的任何点的坐标在x方向上最多+/- 4,在y方向上最多+/- 4。然后我们可以编写类似于以下

的查询
SELECT city, state, latitude, longitude, COUNT(*)
FROM zipcodes Z
JOIN points P 
  ON P.GridX IN (
    SELECT GridX - 4, GridX - 3, GridX - 2, GridX - 1, GridX, GridX +1, GridX + 2 GridX + 3, GridX +4
   FROM zipcode ZX WHERE Z.id = ZX.id)
  AND
   P.GridY IN (
    SELECT GridY - 4, GridY - 3, GridY - 2, GridY - 1, GridY, GridY +1, GridY + 2 GridY + 3, GridY +4
   FROM zipcode ZY WHERE Z.id = ZY.id)
WHERE P.Status = A
   AND ((Z.latitude - P.latitude) * LatDegInMi) ^2 
      + ((Z.longitude - P.longitude) * LongDegInMi) ^2 < (100^2)
GROUP BY city,state,latitude,longitude;

请注意,LongDegInMi可以是硬编码的(对于美国本土的所有位置都相同),或者来自zipcodes表中的相应记录。类似地,LatDegInMi可以是硬编码的(几乎不需要使它变化,因为它不同于它相对恒定)。

这个更快的原因是对于zipcodes表和points表之间的笛卡尔积中的大多数记录,我们根本不计算距离。我们根据索引值(GridX和GridY)消除它们。

这让我们想到了要生成哪些SQL索引的问题。当然,我们可能需要:    - GridX + GridY +状态(在点表上)    - GridY + GridX +状态(可能)    - 邮政编码表上的城市+州+纬度+经度+ GridX + GridY

网格的替代方案是根据给定城市的纬度和经度“约束”我们将考虑的纬度和经度极限。即JOIN条件变为范围而不是IN:

JOIN points P 
  ON    P.latitude > (Z.Latitude - (100 / LatDegInMi)) 
    AND P.latitude < (Z.Latitude + (100 / LatDegInMi)) 
    AND P.longitude > (Z.longitude - (100 / LongDegInMi)) 
    AND P.longitude < (Z.longitude + (100 / LongDegInMi)) 

答案 1 :(得分:0)

SELECT * FROM tblLocation 
    WHERE 2 > POWER(POWER(Latitude - 40, 2) + POWER(Longitude - -90, 2), .5)

其中2> part将是平行线的数量,40和-90是测试点的纬度/经度

抱歉,我没有使用您的表名或结构,我只是将其复制到我的一个数据库中的存储过程中。

如果我想查看邮政编码中的点数,我想我会做这样的事情:

SELECT 
    ParcelZip, COUNT(LocationID) AS LocCount 
FROM 
    tblLocation 
WHERE 
    2 > POWER(POWER(Latitude - 40, 2) + POWER(Longitude - -90, 2), .5)
GROUP BY 
    ParcelZip

获取范围内所有位置的总数将如下所示:

SELECT 
    COUNT(LocationID) AS LocCount 
FROM 
    tblLocation 
WHERE 
    2 > POWER(POWER(Latitude - 40, 2) + POWER(Longitude - -90, 2), .5)

交叉连接在这里效率可能不高,因为我们讨论的是大量记录,但这应该在一个查询中完成:

SELECT 
    ZipCodes.ZipCode, COUNT(PointID) AS LocCount 
FROM
    Points
CROSS JOIN 
    ZipCodes
WHERE 
    2 > POWER(POWER(Points.Latitude - ZipCodes.Latitude, 2) + POWER(Points.Longitude - ZipCodes.Longitude, 2), .5)
GROUP BY 
    ZipCodeTable.ZipCode

答案 2 :(得分:0)

当我进行这些类型的搜索时,我的需求允许一些近似值。所以我使用你在第二个查询中的公式来首先计算“边界” - 在允许半径的极值处的四个纬度/经度值,然后取这些边界并进行简单查询以找到其中的匹配(小于最大纬度,长度,大于最小纬度,长度)。所以我最终得到的是位于由半径定义的圆内的正方形内的所有东西。