Sie sind fast da. Es gibt einen kleinen Trick, der darin besteht, den eindeutigen Operator von Postgres zu verwenden , der das erste Match jeder Kombination zurückgibt. Wenn Sie mit ST_Distance bestellen, gibt er effektiv den nächstgelegenen Punkt von jedem Senal zu jedem Port zurück.
SELECT
DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY") as dist
FROM traffic_signs As senal, entrance_halls As port
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");
Wenn Sie wissen, dass der Mindestabstand jeweils nicht mehr als einen Betrag x beträgt (und Sie einen räumlichen Index auf Ihren Tabellen haben), können Sie dies beschleunigen, indem Sie WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
z. B. ein setzen, wenn alle Mindestabstände bekannt sind nicht mehr als 10km, dann:
SELECT
DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY") as dist
FROM traffic_signs As senal, entrance_halls As port
WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", 10000)
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");
Offensichtlich muss dies mit Vorsicht angewendet werden, da bei einem größeren Mindestabstand einfach keine Zeile für diese Kombination aus Senal und Port angezeigt wird.
Hinweis: Die Reihenfolge nach Reihenfolge muss mit der eindeutigen Reihenfolge übereinstimmen. Dies ist sinnvoll, da die erste eindeutige Gruppe basierend auf einer bestimmten Reihenfolge eindeutig ist.
Es wird davon ausgegangen, dass Sie für beide Tabellen einen räumlichen Index haben.
EDIT 1 . Es gibt eine weitere Option, nämlich die Verwendung der Postgres-Operatoren <-> und <#> (Mittelpunkt- bzw. Begrenzungsrahmen-Abstandsberechnungen), die den räumlichen Index effizienter nutzen und keinen ST_DWithin-Hack zur Vermeidung von n erfordern ^ 2 Vergleiche. Es gibt einen guten Blog-Artikel, der erklärt, wie sie funktionieren. Allgemein ist zu beachten, dass diese beiden Operatoren in der ORDER BY-Klausel arbeiten.
SELECT senal.id,
(SELECT port.id
FROM entrance_halls as port
ORDER BY senal.geom <#> port.geom LIMIT 1)
FROM traffic_signs as senal;
BEARBEITEN 2 . Da diese Frage viel Beachtung gefunden hat und k-Nearest Neighbours (kNN) im Allgemeinen ein schwieriges Problem (in Bezug auf die algorithmische Laufzeit) in GIS darstellt, scheint es sinnvoll, den ursprünglichen Umfang dieser Frage etwas zu erweitern.
Die Standardmethode, um die x nächsten Nachbarn eines Objekts zu finden, ist die Verwendung eines LATERAL JOIN (konzeptionell a für jede Schleife ähnlich). Wenn Sie sich schamlos von dbastons Antwort leihen , würden Sie etwas tun wie:
SELECT
signs.id,
closest_port.id,
closest_port.dist
FROM traffic_signs
CROSS JOIN LATERAL
(SELECT
id,
ST_Distance(ports.geom, signs.geom) as dist
FROM ports
ORDER BY signs.geom <-> ports.geom
LIMIT 1
) AS closest_port
Wenn Sie also die nächsten 10 Ports nach Entfernung geordnet finden möchten, müssen Sie lediglich die LIMIT-Klausel in der seitlichen Unterabfrage ändern. Dies ist ohne LATERAL JOINS viel schwieriger und erfordert die Verwendung von ARRAY-Typ-Logik. Obwohl dieser Ansatz gut funktioniert, kann er enorm beschleunigt werden, wenn Sie wissen, dass Sie nur bis zu einer bestimmten Entfernung suchen müssen. In diesem Fall können Sie ST_DWithin (signs.geom, ports.geom, 1000) in der Unterabfrage verwenden. Aufgrund der Funktionsweise der Indizierung mit dem Operator <-> sollte eine der Geometrien eine Konstante sein und nicht ein Spaltenreferenz - kann viel schneller sein. Um beispielsweise die 3 nächstgelegenen Häfen innerhalb von 10 km zu finden, können Sie Folgendes schreiben.
SELECT
signs.id,
closest_port.id,
closest_port.dist
FROM traffic_signs
CROSS JOIN LATERAL
(SELECT
id,
ST_Distance(ports.geom, signs.geom) as dist
FROM ports
WHERE ST_DWithin(ports.geom, signs.geom, 10000)
ORDER BY ST_Distance(ports.geom, signs.geom)
LIMIT 3
) AS closest_port;
Wie immer hängt die Verwendung von Ihrer Datenverteilung und Ihren Abfragen ab. EXPLAIN ist also Ihr bester Freund.
Schließlich gibt es ein kleines Problem, wenn Sie LEFT anstelle von CROSS JOIN LATERAL verwenden und nach dem Query- Alias ON TRUE hinzufügen müssen , z.
SELECT
signs.id,
closest_port.id,
closest_port.dist
FROM traffic_signs
LEFT JOIN LATERAL
(SELECT
id,
ST_Distance(ports.geom, signs.geom) as dist
FROM ports
ORDER BY signs.geom <-> ports.geom
LIMIT 1
) AS closest_port
ON TRUE;