2012-02-26 69 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