2012-02-26 65 views
4

我正在使用以下sql代码来查找与设置坐标最接近的'ALL'poi,但我想要查找具体的poi而不是全部其中。当我尝试使用where子句时,我得到一个错误,它不起作用,这是我目前卡住的地方,因为我只使用一个表来处理所有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; 
+1

不工作怎么样?你能在你的问题中粘贴错误信息吗? – mazaneicha 2012-02-26 14:05:31

+0

来优化此类查询的速度/性能阅读此文章:http://stackoverflow.com/a/5749614/43959 – Kaii 2012-02-26 22:58:44

回答

5

的问题是,不能在一个selectwhere子句引用一个别名的列(distance在这种情况下)。当试图处理NewCol + 1并在where声明试图处理NewCol = 2select声明:例如,你可以这样做:

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

这将都失败。

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

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下面提到这将导致全表扫描。根据您要处理的数据量,您可能想要避免这种情况,并选择第一个选项,该选项应该更快。

+0

你应该提到,这总是需要一个完整的表扫描,并可能执行不好的巨大数据集 – Kaii 2012-02-26 14:14:32

+0

张贴我自己的答案更清楚地指出这一点。您的任一解决方案都会表现同样糟糕。没有索引可以用于这种复杂的计算。 – Kaii 2012-02-27 10:16:38

+0

我已经想通了,我为了更快的性能,感谢您的支持。 – 2012-02-28 09:16:15

3

为什么你不能使用你的别名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相同的性能结果)

所有这些将执行几乎同样不好:首先获取每一行,计算的距离,并最终过滤的距离,然后将结果发送到客户端。

您可以通过将简单WHERE子句距离近似与在HAVING条款更精确的欧几里德式(过滤行第一抓取)获益良多(!)更好的性能。

可以使用基于简单的X和Y距离(边框)一 WHERE条款符合 @distance = 10条件
  1. 查找行 - 这是一个便宜操作。
  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

+0

我已经考虑过我的sql代码了,谢谢! :D – 2012-02-28 09:16:58

+0

@HenryDang很高兴听到我能帮助你。如果您喜欢答案,请点击左侧的复选标记以接受它:-) – Kaii 2012-02-28 10:54:35