2011-01-19 13 views
3

我需要寫一個查詢,它允許我從一個提供的位置查找範圍內的所有位置(英里)。兩個座標之間的距離,我如何簡化這個和/或使用不同的技術?

表是這樣的:

id | name | lat | lng 

所以我一直在做研究,發現:this my sql presentation

我已經有100行測試它在桌子上,將有大量的多! - 必須可擴展。

我嘗試一些更簡單的像這首:

//just some test data this would be required by user input  
set @orig_lat=55.857807; set @orig_lng=-4.242511; set @dist=10; 

SELECT *, 3956 * 2 * ASIN(
      SQRT(POWER(SIN((orig.lat - abs(dest.lat)) * pi()/180/2), 2) 
       + COS(orig.lat * pi()/180) * COS(abs(dest.lat) * pi()/180) 
       * POWER(SIN((orig.lng - dest.lng) * pi()/180/2), 2))) 
      AS distance 
    FROM locations dest, locations orig 
WHERE orig.id = '1' 
HAVING distance < 1 
ORDER BY distance; 

這返回的行圍繞50ms的這是非常好的! 但是,隨着行數的增加,這會顯着減慢。

EXPLAIN顯示它只使用明顯的PRIMARY鍵。


然後看完文章linked above。我想是這樣的:

// defining variables - this when made into a stored procedure will call 
// the values with a SELECT query. 
set @mylon = -4.242511; 
set @mylat = 55.857807; 
set @dist = 0.5; 

-- calculate lon and lat for the rectangle: 
set @lon1 = @[email protected]/abs(cos(radians(@mylat))*69); 
set @lon2 = @[email protected]/abs(cos(radians(@mylat))*69); 
set @lat1 = @mylat-(@dist/69); 
set @lat2 = @mylat+(@dist/69); 

-- run the query: 

SELECT *, 3956 * 2 * ASIN(
      SQRT(POWER(SIN((@mylat - abs(dest.lat)) * pi()/180/2) ,2) 
       + COS(@mylat * pi()/180) * COS(abs(dest.lat) * pi()/180) 
       * POWER(SIN((@mylon - dest.lng) * pi()/180/2), 2))) 
      AS distance 
    FROM locations dest 
WHERE dest.lng BETWEEN @lon1 AND @lon2 
    AND dest.lat BETWEEN @lat1 AND @lat2 
HAVING distance < @dist 
ORDER BY distance; 

此查詢的時間約爲240MS,這是不是太糟糕,但比過去的要慢。但我可以想象更多的行數會更快。然而,EXPLAIN將可能的密鑰顯示爲lat,lngPRIMARY並且使用PRIMARY

我該如何做得更好?

我知道我可以將lat lng存儲爲POINT();但我也沒有找到太多的文件,這表明它是更快還是更準確?

任何其他的想法會被高興地接受!

非常感謝!

-Stefan


UPDATE:

喬納森萊弗勒指出,我做了,我沒有注意到幾個錯誤:

我只把ABS()在其中一個緯度值上。在沒有需要的情況下,我在第二個WHERE子句中使用了一個id搜索。在第一個查詢純粹是實驗性的,第二個查詢更有可能達到產量。

經過這些更改EXPLAIN顯示現在使用的密鑰是lng列和平均響應時間180ms這是一個改進。

+0

Stefan,我期待着做這樣的事情..你可以發佈你的最終存儲過程嗎?我以前從來沒有寫過存儲過程,想到的第一個問題是,你的代碼看起來像它有靜態參數..我如何將myLat,myLon和距離傳遞給存儲過程,距離「miles 「 – erik 2014-06-12 19:16:40

回答

2

任何其他的想法,將愉快地接受了!

如果你想要速度(和簡單),你會想要從你的數據庫一些體面的地理空間支持。它引入了地理空間數據類型,地理空間索引和(很多)用於處理/構建/分析地理空間數據的功能。

MySQL implements a part of the OpenGIS specifications雖然它是/是(上次我檢查它是)非常非常粗糙的邊緣/過早(對任何實際工作都沒有用處)。

PostGisPostgreSql將使這很輕鬆易讀:

(這個發現從表B這是接近,然後從點1000米表A id爲123的所有點)

select 
    myvalue 
from 
    tablea, tableb 
where 
    st_dwithin(tablea.the_geom, tableb.the_geom, 1000) 
and 
    tablea.id = 123 
0

的幾點思考提高性能。它不會從可維護性的角度來簡化事情(使事情更復雜),但它可能有助於可伸縮性。

  1. 你既然知道半徑,可以爲邊框,可以讓數據庫以優化查詢,以消除一些行,而不必做TRIG Calcs(計算)增加的條件。

  2. 你可以預先計算一些儲存位置的緯度/經度的三角函數值,並將其存儲在表中。這會在插入記錄時改變一些性能成本,但如果查詢超過插入數量,這將是很好的。看到這個答案對於這種方法的一個想法:

    Query to get records based on Radius in SQLite?

  3. 你可以看看像geohashing

當在數據庫中使用的,geohashed數據的結構具有兩個優點。 ,,,,其次,這個索引結構可以用於快速和髒的鄰近搜索 - 最接近的點通常是最接近的地理雜湊。

你可以對如何實現一些想法,所以搜索: https://stackoverflow.com/search?q=geohash

2

第一個查詢忽略你設置的參數 - 使用的,而不是爲@dist距離1,用表的別名orig代替的參數@orig_lat@orig_lon

你就必須查詢做表本身,這是很少一個好主意,如果你能避免它之間的笛卡爾乘積。由於過濾條件orig.id = 1,這意味着只有一行origdest(包括與dest.id = 1;您應該可能有一個條件AND orig.id != dest.id)中的每一行加入,您可以避開它。您還有一個HAVING子句,但沒有GROUP BY子句,這表示有問題。 HAVING子句與任何聚合無關,但HAVING子句(主要)用於比較聚合值。除非我的記憶讓我失望,COS(ABS(x))=== COS(x),所以你可以通過刪除ABS()來簡化事情。如果不這樣做,目前還不清楚爲什麼一個緯度需要ABS而另一個不需要 - 對稱性在球面三角測量中至關重要。

你有一定數量的幻數 - 值69可能是度數(經度,赤道)的英里數,而3956是地球半徑。

如果給定的位置接近極點,我很懷疑計算的盒子。在極端情況下,您可能需要允許任何經度。

第二個查詢中的條件dest.id = 1是奇數;我相信它應該被省略,但它的存在會加快速度,因爲只有一行符合該條件。所以額外的時間令人費解。但是使用主鍵索引是合適的。

您應該將HAVING子句中的條件移動到WHERE子句中。

但我不知道這是真正幫助...

+0

那麼斑點,沒有意識到第二個abs()和WHERE子句錯誤!謝謝,我已經更新了相應的帖子。 – 2011-01-20 08:58:08

1

的NGS在線逆測地計算器是傳統的基準裝置來計算地球橢球體的任何兩個地點之間的距離:

http://www.ngs.noaa.gov/cgi-bin/Inv_Fwd/inverse2.prl

但上面的計算器仍然存在問題。特別是在兩個近對極位置之間,計算出的距離可能會顯示幾十公里的誤差!數字麻煩的來源被確定很久以前撒迪厄斯·文森(92頁):

http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf

在任何情況下,preferrable由Charles Karney使用可靠和非常精確的在線計算器:

http://geographiclib.sourceforge.net/cgi-bin/Geod

0

如果您只對相當小的距離感興趣,則可以通過矩形網格近似地理網格。

SELECT *, SQRT(POWER(RADIANS(@mylat - dest.lat), 2) + 
       POWER(RADIANS(@mylon - dst.lng)*COS(RADIANS(@mylat)), 2) 
      )*@radiusOfEarth AS approximateDistance 
… 

你能做出這樣更有效的通過存儲弧度,而不是(或除了)度數據庫。如果您的疑問可能會穿越180°子午線,那麼需要額外的注意,但許多應用程序無需處理這些位置。您也可以嘗試改變POWER(X)X*X,這可能會更快的計算。

相關問題