【问题标题】:Using WHERE clause to find POI within a range of distance from Longitude and Latitude使用 WHERE 子句在经纬度范围内查找 POI
【发布时间】:2012-03-16 06:07:13
【问题描述】:

我正在使用以下 sql 代码来找出最接近设置坐标的“所有”poi,但我想找出特定的 poi 而不是全部。当我尝试使用 where 子句时,我得到一个错误并且它不起作用,这就是我目前卡住的地方,因为我只使用一个表来表示所有 poi 的所有坐标。

SET @orig_lat=55.4058;  
SET @orig_lon=13.7907; 
SET @dist=10;
SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2) 
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
FROM geo_kulplex.sweden_bobo
HAVING distance < @dist 
ORDER BY distance limit 10;

【问题讨论】:

  • 不工作怎么办?您可以在问题中粘贴错误消息吗?
  • 为了优化此类查询的速度/性能,请阅读这篇文章:stackoverflow.com/a/5749614/43959

标签: mysql sql gps geometry geo


【解决方案1】:

问题是您不能在select 或where 子句中引用别名列(在这种情况下为distance)。例如,您不能这样做:

select a, b, a + b as NewCol, NewCol + 1 as AnotherCol from table
where NewCol = 2

这两种情况都会失败:尝试处理 NewCol + 1 时的 select 语句以及尝试处理 NewCol = 2 时的 where 语句。

有两种方法可以解决这个问题:

1) 用计算值本身替换引用。示例:

select a, b, a + b as NewCol, a + b + 1 as AnotherCol from table
where  a + b = 2

2) 使用外部select 语句:

select a, b, NewCol, NewCol + 1 as AnotherCol from (
    select a, b, a + b as NewCol from table
) as S
where NewCol = 2

现在,鉴于您的计算列巨大且不太人性化 :) 我认为您应该选择最后一个选项来提高可读性:

SET @orig_lat=55.4058;  
SET @orig_lon=13.7907; 
SET @dist=10;

SELECT * FROM (
  SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2) 
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
  FROM geo_kulplex.sweden_bobo
) AS S
WHERE distance < @dist
ORDER BY distance limit 10;

编辑:正如@Kaii 下面提到的,这将导致全表扫描。根据您将要处理的数据量,您可能希望避免这种情况并选择第一个选项,它应该会执行得更快。

【讨论】:

  • 您应该提到,这总是需要全表扫描,并且可能在庞大的数据集上表现不佳
  • 发布了我自己的答案以更清楚地指出这一点。您的任何一个解决方案都会表现得同样糟糕。如此复杂的计算,没有任何指标可以使用。
  • 我已经想通了,我追求更快的性能,感谢您的支持。
【解决方案2】:

不能在WHERE子句中使用别名的原因是MySQL执行事物的顺序:

  1. FROM
  2. WHERE
  3. GROUP BY
  4. HAVING
  5. SELECT
  6. ORDER BY

执行WHERE 子句时,尚未计算列别名的值。这是一件好事,因为它会浪费很多性能。想象有许多 (1,000,000) 行 - 要在 WHERE 子句中使用您的计算,首先必须获取并计算这 1,000,000 行中的每一个,以便 WHERE 条件可以将计算结果与您的预期进行比较。

您可以通过以下任一方式显式执行此操作

  • 使用HAVING(这就是为什么HAVING 有另一个名字WHERE - 这是另一回事)
  • 使用 @MostyMostacho 所示的子查询(实际上会产生一些开销)
  • 将复杂的计算放在WHERE 子句中(将有效地给出与HAVING 相同的性能结果)

所有这些都会表现得几乎同样糟糕:首先获取每一行,计算距离,最后按距离过滤,然后将结果发送给客户端。

通过在HAVING 子句中将简单的用于距离近似的WHERE 子句(过滤行以首先获取)与更精确的欧几里得公式混合,您可以获得更好的性能。

  1. 使用基于简单 X 和 Y 距离(边界框)的 WHERE 子句找到可以匹配@distance = 10 条件的行——这是一个便宜操作。
  2. 在HAVING 子句中使用欧几里得距离公式过滤这些结果——这是一个昂贵的操作。

查看此查询以了解我的意思:

SET @orig_lat=55.4058;
SET @orig_lon=13.7907; 
SET @dist=10;
SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2)
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
FROM geo_kulplex.sweden_bobo
/* WHERE clause to pre-filter by distance approximation .. filter results 
   later with precise euclidian calculation. can use indexes. */
WHERE 
    /* i'm unsure about geo stuff ... i dont think you want a 
       distance of 10° here, please adjust this properly!! */
    latitude BETWEEN (@orig_lat - @dist) AND (@orig_lat + @dist)
    AND longitude BETWEEN (@orig_lon - @dist) AND (@orig_lon + @dist)
/* HAVING clause to filter result using the more precise euclidian distance */
HAVING distance < @dist 
ORDER BY distance limit 10;

对于那些对常数感兴趣的人:

  • 3956 是以英里为单位的地球半径,因此得到的距离以英里为单位测量
  • 6371 是以千米为单位的地球半径,所以用这个常数来测量距离以千米为单位

在wiki about the Haversine formula中查找更多信息

【讨论】:

  • @HenryDang 很高兴听到我可以帮助你。如果您喜欢答案,请单击左侧的复选标记以接受它:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多