Ci sei quasi. C'è un piccolo trucco che consiste nell'utilizzare l' operatore distinto di Postgres , che restituirà la prima partita di ogni combinazione - come stai ordinando da ST_Distance, effettivamente restituirà il punto più vicino da ogni senale a ciascuna porta.
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");
Se sai che la distanza minima in ciascun caso non è superiore a una certa quantità x, (e hai un indice spaziale sui tuoi tavoli), puoi accelerare inserendo un WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
, ad esempio, se si sa che tutte le distanze minime sono non più di 10 km, quindi:
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");
Ovviamente, questo deve essere usato con cautela, come se la distanza minima sia maggiore, semplicemente non otterrai alcuna fila per quella combinazione di senale e porto.
Nota: l'ordine per ordine deve corrispondere al distinto sull'ordine, il che ha senso, poiché distinto sta prendendo il primo gruppo distinto in base ad un certo ordinamento.
Si presume che tu abbia un indice spaziale su entrambe le tabelle.
MODIFICA 1 . Esiste un'altra opzione, che consiste nell'utilizzare gli operatori <-> e <#> di Postgres (rispettivamente i calcoli della distanza del punto centrale e del riquadro di selezione) che fanno un uso più efficiente dell'indice spaziale e non richiedono l'hack ST_DWithin per evitare n ^ 2 confronti. C'è un buon articolo sul blog che spiega come funzionano. La cosa generale da notare è che questi due operatori lavorano nella clausola ORDER BY.
SELECT senal.id,
(SELECT port.id
FROM entrance_halls as port
ORDER BY senal.geom <#> port.geom LIMIT 1)
FROM traffic_signs as senal;
MODIFICA 2 . Poiché questa domanda ha ricevuto molta attenzione e i vicini più vicini a k (kNN) sono generalmente un problema difficile (in termini di runtime algoritmico) in GIS, sembra utile ampliare un po 'l'ambito originale di questa domanda.
Il modo standard per trovare i vicini x più vicini di un oggetto è usare un LATERAL JOIN (concettualmente simile a un per ogni loop). Prendendo in prestito spudoratamente dalla risposta di dbaston , faresti qualcosa del tipo:
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
Quindi, se vuoi trovare le 10 porte più vicine, ordinate per distanza, devi semplicemente cambiare la clausola LIMIT nella sottoquery secondaria. Questo è molto più difficile da fare senza LATERAL JOINS e implica l'utilizzo della logica di tipo ARRAY. Mentre questo approccio funziona bene, può essere accelerato enormemente se sai che devi solo cercare a una data distanza. In questo caso, è possibile utilizzare ST_DWithin (signs.geom, doors.geom, 1000) nella sottoquery, che a causa del modo in cui l'indicizzazione funziona con l'operatore <-> - una delle geometrie dovrebbe essere una costante, piuttosto che un riferimento di colonna - potrebbe essere molto più veloce. Quindi, per esempio, per ottenere i 3 porti più vicini, entro 10 km, potresti scrivere qualcosa di simile al seguente.
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;
Come sempre, l'utilizzo varierà a seconda della distribuzione dei dati e delle query, quindi EXPLAIN è il tuo migliore amico.
Infine, c'è un gotcha minore, se si utilizza LEFT anziché CROSS JOIN LATERAL in quanto è necessario aggiungere ON TRUE dopo l'alias delle query laterali, ad es.
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;