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

Posted

技术标签:

【中文标题】SQL查询位置半径内的总点数【英文标题】:SQL Query For Total Points Within Radius of a Location 【发布时间】:2009-11-17 20:54:44 【问题描述】:

我有一个包含美国所有邮政编码的数据库表,其中包括每个邮政编码的城市、州、纬度和经度。我还有一个点的数据库表,每个点都有一个与之相关的纬度和经度。我希望能够使用 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 查询之前运行解释时,输出如下:

+----+--------------------+----------+------+------------------------+------------------------+---------+-------+-------+---------------------------------+
| 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        | 
+----+--------------------+----------+------+------------------------+------------------------+---------+-------+-------+---------------------------------+

我知道我可以遍历所有邮政编码并计算给定半径内的匹配点数,但点表将一直在增长,我宁愿在邮政编码数据库中没有过时的点总数。我希望那里的 MySQL 大师可以告诉我我的方式的错误。提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

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

有两种通用方法可能有助于解决这种情况

让距离公式更简单 从给定城市过滤掉 100k 半径范围内不太可能的候选对象

在进入这两个改进途径之前,您应该确定这 100 英里距离所需的精度水平,还应该指出数据库覆盖的地理区域(这只是美国大陆等。

这样做的原因是,虽然大圆公式在数值上更精确,但在计算上却非常昂贵。另一个提高性能的途径是存储各种“网格坐标”以添加(或代替)纬度/经度坐标。

编辑: 关于更简单(但不太精确)公式的一些想法: 由于我们处理的是相对较小的距离(我猜在北纬 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)) 

【讨论】:

@mjv 还有更多可行的建议,例如如何使距离公式更简单或过滤掉不太可能的候选人的最佳方法?感谢您的帮助! @mjv 您能否提供有关“网格坐标”的更多详细信息。它们长什么样?它们如何帮助提高性能? @Russel:查看关于更简单 [squared] 距离公式的编辑,以及关于使用网格系统的编辑,该系统将允许 SQL 使用索引来预过滤点以考虑精确距离计算。 @mjv 谢谢 - 我认为这越来越接近了。我已将查询更新为: select z.city,z.state,z.latitude,z.longitude,count(p.id) as 'points' from zipcodes z join points p on p.latitude > (z.latitude -(100/69.0)) AND p.latitude (z.longitude-(100/46.0)) AND p.longitude @Russell,当然,点上的状态+纬度+经度索引会大有帮助。另一个考虑因素是预先计算 100 英里范围(100/69.0 等),尽管我怀疑这会产生重大影响。【参考方案2】:

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

【讨论】:

感谢德文的建议。使用这种方法,我假设我需要为每个城市/州进行数据库查询,以找出相当于 10,000 次查询的界限。无论如何,您能想到将其组合成一个查询吗?【参考方案3】:
SELECT * FROM tblLocation 
    WHERE 2 > POWER(POWER(Latitude - 40, 2) + POWER(Longitude - -90, 2), .5)

其中 2 > 部分是平行线数,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

【讨论】:

我假设这将是子查询的替代品? 哦,是的,我什至没有注意到问题的第二部分。对不起!我添加了另一部分来计算每个邮政编码中的位置 我想我遗漏了 Jrud 的东西,因为我没有看到您的第二个查询将如何返回 40 和 -90 纬度/经度位置的 2 个平行内的点数。似乎它只会从同一张表中返回特定城市附近的城市数量 - 而不是 2 个单独的表。 我可能无法准确了解您的积分表和邮政编码表的链接方式。你知道每个邮政编码的纬度/经度坐标是什么吗?或者你知道这些点的邮政编码吗? 我知道 zipcodes 表中每个 zipcode 的 lat/lon 和 points 表中每个点的 lat/lon。从那里我需要每个特定邮政编码在给定半径内的总点数 - 全部在 1 个查询中。

以上是关于SQL查询位置半径内的总点数的主要内容,如果未能解决你的问题,请参考以下文章

SQL查询和计算;正确格式化查询以仅选择 id

Twitter - 查询特定地理位置半径内的推文

从 Laravel 中的坐标获取半径内的用户

查询 CouchDB 获取一定半径内的 poi

SQL查询一个表的总记录数的方法

使用 MySQL 查找距离内的点