【翻譯】SQL最近位置查詢語(yǔ)句(MySQL、PostgreSQL狂丝、SQL Server)

前言

我已經(jīng)浪費(fèi)了太多的時(shí)間在尋找定位軟件上了换淆,因此這值得我去寫下如何去做。當(dāng)然几颜,在地球表面計(jì)算距離意味著計(jì)算大圓距離倍试,可以通過(guò)半正矢公式計(jì)算,也稱之為球面余弦定律公式蛋哭。問(wèn)題是:

給出一個(gè)具有經(jīng)緯度的位置表县习,其中哪個(gè)位置最靠近給出的定位?

位置數(shù)據(jù)表

你是否想問(wèn)在哪里我可以找到一張具有經(jīng)緯度的位置表谆趾?你可以在互聯(lián)網(wǎng)上搜索“郵政編碼免費(fèi)下載”或者“免費(fèi)郵編下載”躁愿。然后將其加載到MySQL表中。有很多不同類型的地理數(shù)據(jù)可以下載沪蓬,附帶經(jīng)緯度位置彤钟。

這是SQL Server數(shù)據(jù)的美國(guó)郵政編碼數(shù)據(jù)包,如果你正好需要的話跷叉。

本文中的邏輯適用于MySQL逸雹,MariaDB, PostgreSQL云挟,和微軟的SQL Server梆砸。Oracle的工作方式有一點(diǎn)不同;這里有一篇文章講述了如何在Oracle中實(shí)現(xiàn)园欣。

請(qǐng)慎重的使用郵政編碼數(shù)據(jù)作為確定位置的方法帖世。郵政編碼僅被設(shè)計(jì)用于幫助優(yōu)化郵政投遞。他們的數(shù)據(jù)用途有限俊庇,并且可能帶來(lái)錯(cuò)誤的結(jié)果狮暑。例如,這是一篇地理學(xué)家寫的關(guān)于美國(guó)密西根州弗林特市水危機(jī)的文章辉饱。在很長(zhǎng)一段時(shí)間搬男,弗林特市的孩子似乎沒(méi)有鉛中毒,因?yàn)檠芯繂T只看他們家的郵政編碼去找出他們住在哪里彭沼。但是他們鉛中毒了缔逛,別和密歇根州政府犯相同的錯(cuò)誤。

煩人卻必要的地理

緯度和經(jīng)度用度數(shù)表示。緯度描述了一個(gè)點(diǎn)在赤道以北或以南的距離褐奴。赤道上的點(diǎn)的緯度是零按脚。北極的正(北)緯度為90度,并且南極是負(fù)(南)緯度-90度敦冬。相應(yīng)的辅搬,北半球的位置有著正緯度,并且南半球的位置有著負(fù)的緯度脖旱。

經(jīng)度描述了一個(gè)點(diǎn)從本初子午線向東的距離:地球表面從一個(gè)點(diǎn)到另一個(gè)點(diǎn)的任意直線堪遂。位于美國(guó)紐約市的帝國(guó)大廈的經(jīng)度為負(fù)(西),具體來(lái)說(shuō)為-73.9857萌庆。印度阿格拉的泰姬陵經(jīng)度為正(東經(jīng))溶褪,具體為78.0422。英國(guó)倫敦附近的格林威治天文臺(tái)践险,根據(jù)定義猿妈,經(jīng)度為零。

因此巍虫,緯度是范圍內(nèi)的值[-90彭则,90]。經(jīng)度是范圍(-180垫言,180)內(nèi)的值贰剥。這些值有時(shí)以度、分和秒表示筷频,而不是以度和小數(shù)表示蚌成。如果你打算做計(jì)算,先把分和秒轉(zhuǎn)換成小數(shù)凛捏。

在拿破侖時(shí)代担忧,米是最早被定義的,所以從赤道到兩極有一千萬(wàn)米坯癣。原來(lái)緯度上的米數(shù)是10000000/90或111.111公里瓶盛。但是地球有點(diǎn)凸起,因此111.045公里/度被認(rèn)為是一個(gè)更好的近似值示罗。

在這里我們?yōu)榱朔奖阌?jì)算惩猫,我們假設(shè)地球是一個(gè)球體。雖然這不是真的蚜点。它在赤道上有點(diǎn)凸起轧房,但是定位問(wèn)題,我們假設(shè)是球體就足夠了绍绘。

這個(gè)公式(111.045公里/度)在你向北或者向南移動(dòng)的時(shí)候很好用奶镶。如果你在改變你的緯度而不是經(jīng)度迟赃。如果你在向東或者向西移動(dòng)、在改變你的經(jīng)度厂镇、在赤道上纤壁,它也能起作用。但是在赤道的南北邊捺信,經(jīng)度線越來(lái)越接近酌媒,所以如果你向西或向東移動(dòng)一個(gè)刻度,你移動(dòng)的距離就會(huì)小于111.045千米残黑。當(dāng)你往東或往西走一度時(shí)馍佑,你實(shí)際移動(dòng)的距離實(shí)際上是公里數(shù)斋否。

111.045 * cos(latitude)

我們?cè)谝恍┯?guó)殖民地里使用英里梨水。海里是指緯度的一分鐘(1/60度)。所以每度有69法定英里或每度60海里茵臭。如果你正在處理這樣的應(yīng)用疫诽,如GPS控制耕牛隊(duì),你可能會(huì)發(fā)現(xiàn)它有助于知道有552浪(長(zhǎng)度單位旦委,相當(dāng)于220碼奇徒、201米或?英里)每度。一些以美國(guó)為中心的應(yīng)用程序擾亂了經(jīng)度缨硝。對(duì)西半球的位置來(lái)說(shuō)摩钙,它們是正的而不是負(fù)的。如果你在調(diào)試什么東西查辩,要注意這個(gè)

大圓距離公式

任意兩點(diǎn)沿(球面)地球表面的距離是多少胖笛?用度數(shù)表示,用他們的經(jīng)緯度表示宜岛?這是由球余弦定理长踊,或者半正矢公式決定的。這是MySQL語(yǔ)法中的:

DEGREES(ACOS(COS(RADIANS(lat1)) * COS(RADIANS(lat2)) *
             COS(RADIANS(long1) - RADIANS(long2)) +
             SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))))

它是地球表面的距離萍倡。當(dāng)這些地方是你的公寓和當(dāng)?shù)爻猩肀祝蛘呤前拇罄麃喯つ岷捅鶏u雷克雅未克的機(jī)場(chǎng)時(shí),它也同樣適用列敲。注意阱佛,這個(gè)結(jié)果是以度為單位的。這意味著如果我們想要以公里為單位的距離戴而,我們必須將它乘以111.045凑术,即每度公里的數(shù)值。

請(qǐng)注意MS SQL Server需要使用一個(gè)float或double來(lái)表示RADIANS填硕。RADIANS(30) 返回的是有問(wèn)題的值麦萤,但是RADIANS(30)能正常工作鹿鳖。一般來(lái)說(shuō),MS SQL Server不會(huì)可靠的強(qiáng)制整integer類型的值轉(zhuǎn)換為float或者double類型壮莹,所有請(qǐng)小心翅帜,不要在你需要使用float的時(shí)候使用integer類型。此外命满,請(qǐng)記住美國(guó)郵政編碼雖然看起來(lái)像數(shù)字涝滴,但是其實(shí)是字符串。我住的地方郵政編碼是'01950'胶台,這和1950是不一樣的歼疮。

查詢最近的位置

為了在數(shù)據(jù)庫(kù)中找到與給定點(diǎn)的最近的點(diǎn),我們可以這樣寫查詢诈唬。讓我們使用經(jīng)度為-70.81韩脏、緯度為42.81的點(diǎn)。這個(gè)MySQL查詢按照距離的順序查找離給定點(diǎn)最近的15個(gè)點(diǎn)铸磅。
可以在這邊測(cè)試:http://sqlfiddle.com/#!9/21e06/1

SELECT zip, primary_city, latitude, longitude,
      111.045* DEGREES(ACOS(COS(RADIANS(latpoint))
                 * COS(RADIANS(latitude))
                 * COS(RADIANS(longpoint) - RADIANS(longitude))
                 + SIN(RADIANS(latpoint))
                 * SIN(RADIANS(latitude)))) AS distance_in_km
 FROM zip
 JOIN (
     SELECT  42.81  AS latpoint,  -70.81 AS longpoint
   ) AS p ON 1=1
 ORDER BY distance_in_km
 LIMIT 15

注意使用連接將latpoint和longpoint放入查詢中赡矢。這樣編寫查詢很方便,因?yàn)楣街卸啻我昧薼atpoint和longpoint阅仔。(MySQL不需要使用ON 1=1吹散,但是PostgreSQL需要)
(在SQL Server中, 使用 SELECT TOP(15) zip … 來(lái)替換LIMIT 15.)

非常好,我們做到了八酒,對(duì)吧空民?別著急!這個(gè)查詢雖然是正確的,但是他很慢羞迷。

優(yōu)化

查詢速度很慢是因?yàn)樗仨殲槊總€(gè)可能的點(diǎn)對(duì)計(jì)算半正矢公式界轩。因此,它使你的MySQL服務(wù)器做了很多數(shù)學(xué)運(yùn)算闭树,并強(qiáng)制它掃描整個(gè)位置表耸棒。如何優(yōu)化?如果我們能在表中的緯度和經(jīng)度列上使用索引报辱,那就太好了与殃。為此,我們引入一個(gè)約束碍现。假設(shè)我們只關(guān)心郵政編碼表中距離(latpoint幅疼,longpoint)50公里以內(nèi)的點(diǎn)。讓我們找出如何使用索引來(lái)消除更遠(yuǎn)的點(diǎn)昼接。

請(qǐng)記住爽篷,根據(jù)本文前面的背景信息,緯度是111.045公里慢睡。所以逐工,如果緯度列上有一個(gè)索引铡溪,我們可以使用類似這樣的SQL子句來(lái)消除太北或太南的點(diǎn),這些點(diǎn)可能不在50公里之內(nèi)泪喊。

latitude BETWEEN latpoint - (50.0 / 111.045)
             AND latpoint + (50.0 / 111.045)             

這個(gè)WHERE語(yǔ)句允許MySQL在計(jì)算半正矢距離公式之前使用索引省略許多緯度點(diǎn)棕硫。它允許MySQL對(duì)緯度索引執(zhí)行范圍掃描。

最后袒啼,我們可以使用一個(gè)類似但更復(fù)雜的SQL子句來(lái)消除太東或太西的點(diǎn)哈扮。這個(gè)條款更復(fù)雜,因?yàn)榻?jīng)度是離我們移動(dòng)的赤道越遠(yuǎn)的距離越小蚓再。請(qǐng)看下面公式:

longitude BETWEEN longpoint - (50.0 / (111.045 * COS(RADIANS(latpoint))))
              AND longpoint + (50.0 / (111.045 * COS(RADIANS(latpoint))))

因此滑肉,將所有這些放在一起,這個(gè)查詢將查找(latpoint摘仅,longpoint)50公里范圍內(nèi)的最東邊15個(gè)點(diǎn)靶庙。

盡管這個(gè)查詢有點(diǎn)復(fù)雜,但它利用了緯度和經(jīng)度索引实檀,并且工作效率很高惶洲。

請(qǐng)注意,作為整個(gè)查詢的一部分膳犹,我們加入了這個(gè)子查詢。

SELECT  42.81  AS latpoint,  -70.81 AS longpoint,
        50.0 AS radius,      111.045 AS distance_unit

這樣做的目的是使應(yīng)用軟件更容易提供查詢所需的參數(shù)签则。Latpoint和Longpoint是您需要附近位置的特定位置须床。radius指定搜索應(yīng)該走多遠(yuǎn)。最后渐裂,如果你想用公里表示距離豺旬,距離單位應(yīng)該是111.045。如果你想用英里表示距離柒凉,應(yīng)該是69.0族阅。

極限對(duì)角線距離

但是,這個(gè)邊界查詢有可能返回距離(latpoint膝捞,longpoint)對(duì)角線超過(guò)50km的一些點(diǎn):它只檢查一個(gè)邊界矩形坦刀,而不是對(duì)角線距離。讓我們?cè)鰪?qiáng)查詢以消除超過(guò)50公里的點(diǎn)蔬咬。

使用英里而不是公里

最后鲤遥,許多人需要用英里而不是公里來(lái)計(jì)算他們的距離。這很簡(jiǎn)單林艘。只需將距離單位的值更改為69.0盖奈。

這是一個(gè)基于經(jīng)緯度的典型商店查找程序或位置查找程序的查詢。應(yīng)該能夠適應(yīng)你的使用狐援,沒(méi)有太多的麻煩钢坦。

將此查詢適應(yīng)其他位置表定義

當(dāng)然究孕,這個(gè)查詢是用一個(gè)特定的ZIP表定義(一個(gè)美國(guó)郵政編碼表)編寫的。該zip表包含名為zip爹凹、primary_city蚊俺、latitudelongitude等字段。請(qǐng)注意逛万,該表在查詢中由FROM zip AS z引用泳猬。所以它的別名是z

你的位置表很可能有不同的列宇植。重寫此查詢來(lái)適應(yīng)你的查詢應(yīng)該很簡(jiǎn)單得封。在查詢中查找稱為z.something的字段,并用表中的字段名替換這些字段指郁。例如忙上,如果你的表名為shop,并且有shopname闲坎、shoplatshoplong字段疫粥,那么你把z.shopname替換為z.primary_city,以此類推腰懂。你將通過(guò)在查詢中包含FROM SHOP as z來(lái)引用表梗逮。

原文地址:http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc
博客地址:https://thans.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绣溜,隨后出現(xiàn)的幾起案子慷彤,更是在濱河造成了極大的恐慌,老刑警劉巖怖喻,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件底哗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锚沸,警方通過(guò)查閱死者的電腦和手機(jī)跋选,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哗蜈,“玉大人前标,你說(shuō)我怎么就攤上這事√裉荆” “怎么了候生?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绽昼。 經(jīng)常有香客問(wèn)我唯鸭,道長(zhǎng),這世上最難降的妖魔是什么硅确? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任目溉,我火速辦了婚禮明肮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缭付。我一直安慰自己柿估,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布陷猫。 她就那樣靜靜地躺著秫舌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绣檬。 梳的紋絲不亂的頭發(fā)上足陨,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音娇未,去河邊找鬼墨缘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛零抬,可吹牛的內(nèi)容都是我干的镊讼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼平夜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蝶棋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起褥芒,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嚼松,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锰扶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寝受,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年坷牛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片很澄。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡京闰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甩苛,到底是詐尸還是另有隱情蹂楣,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布讯蒲,位于F島的核電站痊土,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏墨林。R本人自食惡果不足惜赁酝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一犯祠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酌呆,春花似錦衡载、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至菩收,卻和暖如春梨睁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坛梁。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工而姐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人划咐。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓拴念,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親褐缠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子政鼠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容