10,301,390のGPSレコード、都市、国、IPアドレスブロックを含むテーブルがあります。緯度と経度を含むユーザーの現在地を持っています。私はこのクエリを作成しました:
_SELECT
*, point(45.1013021, 46.3021011) <@> point(latitude, longitude) :: point AS distance
FROM
locs
WHERE
(
point(45.1013021, 46.3021011) <@> point(latitude, longitude)
) < 10 -- radius
ORDER BY
distance LIMIT 1;
_
このクエリは、必要なものを提供してくれましたが、遅いです。緯度と経度を指定して1つのレコードを取得するには、2〜3秒かかりました。
latitude
列とlongitude
列でBツリーインデックスを試してみましたが、Gist( point(latitude, longitude));
も試してみましたが、それでもクエリが遅くなります。
このクエリを高速化するにはどうすればよいですか?
_ORDER BY
_が原因で速度が低下しているようですが、最短距離を取得したいので、問題は残ります。
関数ll_to_earth
の使用に基づいてGistインデックスの使用を検討できます。このインデックスは、高速の「近くの」検索を可能にします。
CREATE INDEX
ON locs USING Gist (ll_to_earth(lat, lng));
このインデックスを取得したら、別の方法でクエリを実行する必要があります。
(lat、lng)のペアをearth
タイプに変換し、インデックス付けされた値(同じタイプ)と比較する必要があります。クエリには2つの条件が必要です。1つは「近似」結果用、もう1つは「正確」条件用です。最初のものは前のインデックスを使用することができます:
SELECT
*
FROM
locs
WHERE
/* First condition allows to search for points at an approximate distance:
a distance computed using a 'box', instead of a 'circumference'.
This first condition will use the index.
(45.1013021, 46.3021011) = (lat, lng) of search center.
25000 = search radius (in m)
*/
earth_box(ll_to_earth(45.1013021, 46.3021011), 25000) @> ll_to_earth(lat, lng)
/* This second condition (which is slower) will "refine"
the previous search, to include only the points within the
circumference.
*/
AND earth_distance(ll_to_earth(45.1013021, 46.3021011),
ll_to_earth(lat, lng)) < 25000 ;
このコードを使用するには、2つの拡張機能が必要です(ほとんどのPostgreSQLディストリビューションに含まれています)。
CREATE EXTENSION IF NOT EXISTS cube ;
CREATE EXTENSION IF NOT EXISTS earthdistance;
これは彼らのためのドキュメントです:
earth_box
とearth_distance
に関する情報がここにあります。このモジュールは、地球が球形であることを前提としています。これは、大部分のアプリケーションにとって十分な近似です。Free World Cities Database から取られた220万行で構成されるテーブルを使用したテストでは、前のクエリに対する次の回答が得られます(これは正確には同じではありません)。
"ru","andra-ata","Andra-Ata","24",,44.9509,46.3327
"ru","andratinskiy","Andratinskiy","24",,44.9509,46.3327
"ru","chernozemelskaya","Chernozemelskaya","24",,44.9821,46.0622
"ru","gayduk","Gayduk","24",,44.9578,46.5244
"ru","imeni beriya","Imeni Beriya","24",,45.0208,46.3906
"ru","imeni kirova","Imeni Kirova","24",,45.2836,46.4847
"ru","kumskiy","Kumskiy","24",,44.9821,46.0622
"ru","kumskoy","Kumskoy","24",,44.9821,46.0622
"ru","lopas","Lopas","17",,44.937,46.1833
"ru","pyatogo dekabrya","Pyatogo Dekabrya","24",,45.1858,46.1656
"ru","svetlyy erek","Svetlyy Erek","24",,45.0079,46.4408
"ru","ulan tuk","Ulan Tuk","24",,45.1542,46.1097
タイミングについて「桁違い」の考えを持つために:pgAdmin IIIは、この答えを得るのに22ミリ秒かかると私に言っています。 (「すぐに使える」パラメータを使用したPostgreSQL 9.6.1、Mac OS 10.12、Core i7、SSDを搭載したMac)
1,000万行を使用している場合。おそらく、ステップアップしてPostGISにアップグレードする必要があります。
geometery(point)::geography
を使用できます。または、lat/longに保存する場合はST_MakePoint
を使用できますST_DWithin
を使用します。この関数willインデックスを使用します(作成する場合)。ST_Distance
のみを計算しますこれは、ST_DWithinのsigです。
boolean ST_DWithin(geometry g1, geometry g2, double precision distance_of_srid);
boolean ST_DWithin(geography gg1, geography gg2, double precision distance_meters);
boolean ST_DWithin(geography gg1, geography gg2, double precision distance_meters, boolean use_spheroid);
回転楕円体または球に沿った距離を測定できます。
SELECT geom, ST_Distance(geom, point)
WHERE ST_DWithin( geom, pointgiven, limit to check in km )
ORDER BY geom <=> point ASC
LIMIT 1;