Reihenfolge nach Entfernung

9

Wenn ich eine Frage habe, die Cafés in der Nähe zurückgibt:

SELECT * FROM cafes c WHERE (
   ST_DWithin(
   ST_GeographyFromText(
     'SRID=4326;POINT(' || c.longitude || ' ' || c.latitude || ')'
   ),
   ST_GeographyFromText('SRID=4326;POINT(-76.000000 39.000000)'),
     2000
   )
)

Wie wähle ich die Entfernung aus und ordne sie auch nach Entfernung?
Gibt es einen effizienteren Weg als diesen:

 SELECT id, 
 ST_Distance(ST_GeographyFromText('SRID=4326;POINT(-76.000000 39.000000)'),
             ST_GeographyFromText(
             'SRID=4326;POINT(' || c.longitude || ' ' || c.latitude || ')')      
             ) as distance 
 FROM cafes c
   WHERE (
   ST_DWithin(
     ST_GeographyFromText(
     'SRID=4326;POINT(' || c.longitude || ' ' || c.latitude || ')'
   ),
    ST_GeographyFromText('SRID=4326;POINT(-76.000000 39.000000)'),
   2000
 )
 ) order by distance
Gandalf StormCrow
quelle
Können Sie den Abstand in Ihrer WHEREKlausel verwenden? Möchten Sie es mit einer Schwelle vergleichen?
Dekso
Können Sie bitte näher darauf eingehen? Ich bin mir nicht sicher, ob ich verstehe, was Sie meinen. danke
Gandalf StormCrow
Ich dachte, dass ein Punkt innerhalb einer Geometrie oder nicht weiter als eine bestimmte Entfernung in einigen Fällen mehr oder weniger gleichwertig sein könnte.
Dekso

Antworten:

11

Erstens , die Verwendung

ST_SetSRID(ST_MakePoint(c.longitude, c.latitude),4326)::geography

anstatt

ST_GeographyFromText('SRID=4326;POINT(' || c.longitude || ' ' || c.latitude || ')')

Pro Dokumentation:

ST_MakePointNicht OGC-konform zu sein ist im Allgemeinen schneller und präziser als ST_GeomFromTextund ST_PointFromText. Es ist auch einfacher zu verwenden, wenn Sie Rohkoordinaten anstelle von WKT haben.

Als nächstes wird die Abfrage kürzer zu machen und geben Sie nur Parameter suchen einmal (ohne viel Einfluss auf die Leistung), verwenden Sie eine Unterabfrage (oder CTE):

SELECT id
     , ST_Distance(t.x
                 , ST_SetSRID(ST_MakePoint(c.longitude, c.latitude),4326)::geography) AS dist
FROM   cafes c
    , (SELECT ST_GeographyFromText('SRID=4326;POINT(-76.000000 39.000000)')) AS t(x)
WHERE  ST_DWithin(t.x
                , ST_SetSRID(ST_MakePoint(c.longitude, c.latitude),4326)::geography, 2000)
ORDER  BY dist;

Schließlich benötigen Sie einen GiST-Index , um dies für große Tabellen schnell zu machen. Per Dokumentation zuST_DWithin() :

Dieser Funktionsaufruf enthält automatisch einen Begrenzungsrahmenvergleich, bei dem alle für die Geometrien verfügbaren Indizes verwendet werden.

Sie könnten dies mit einem Funktionsindex für den Ausdruck am Anfang der Antwort arbeiten lassen. Aber ich würde zunächst eine Typenspalte speichern geography(nennen wir es thegeog) und einen einfachen GiST-Index wie folgt erstellen:

CREATE INDEX cafes_thegeog_gist ON cafes USING gist(thegeog);

Zu dieser viel einfacheren und schnelleren Abfrage gelangen:

SELECT id, ST_Distance(t.x, thegeog) AS distance 
FROM   cafes c
    , (SELECT ST_GeographyFromText('SRID=4326;POINT(-76.000000 39.000000)')) AS t(x)
WHERE  ST_DWithin(t.x, thegeog, 2000)
ORDER  BY distance;

Aktualisiert, um geographymit geographyübereinzustimmen, wie von @ LR1234567 im Kommentar hervorgehoben. Alternativ können Sie mit arbeiten geometry. Alle hier verwendeten Funktionen funktionieren für beide (außer fürST_MakePoint die angehängte Besetzung). Was ist der Unterschied?

Wenn Sie die n nächstgelegenen Cafés stattdessen alle innerhalb eines Radius erhalten möchten , ziehen Sie eine Suche nach dem nächsten Nachbarn in Betracht . Oft bequemer.

Erwin Brandstetter
quelle
2
Sie sollten Geometrie nicht mit Geografie mischen. Sie sollten Folgendes tun: ST_SetSRID (ST_MakePoint (c.longitude, c.latitude), 4326) :: geography - Beachten Sie, dass ST_MakePoint eine Geometrie zurückgibt.
LR1234567
@ LR1234567: Danke, dass du darauf hingewiesen hast. Ich habe die Antwort entsprechend aktualisiert.
Erwin Brandstetter
+1 + acc, ziemlich tolle Antwort Ich hoffe, das wird auch vielen Menschen helfen
Gandalf StormCrow
Hallo Erwin, ich habe alles so gemacht, wie du es geschrieben hast. Ich habe eine Geografie-Spalte (thegeog) erstellt, einen Index erstellt und alle Latlongs darin abgelegt. UPDATE cafes SET thegeog = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326);Muss ich den Teil noch verwenden, in select ST_GeographyFromText .... AS t(x)dem ich diese Spalte jetzt ausgefüllt habe? Kann ich diese Spalte nutzen und in der Nähe direkt danach fragen, anstatt lat / long anzugeben?
Gandalf StormCrow
@GandalfStormCrow: Ja, wie in meiner letzten Abfrage gezeigt.
Erwin Brandstetter