Ya casi estás ahí. Hay un pequeño truco que consiste en utilizar el operador distintivo de Postgres , que devolverá la primera coincidencia de cada combinación; como está ordenando por ST_Distance, efectivamente devolverá el punto más cercano de cada sello a cada puerto.
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");
Si sabe que la distancia mínima en cada caso no es más que cierta cantidad x (y tiene un índice espacial en sus tablas), puede acelerar esto poniendo WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
, por ejemplo, si se sabe que todas las distancias mínimas son no más de 10 km, entonces:
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");
Obviamente, esto debe usarse con precaución, ya que si la distancia mínima es mayor, simplemente no obtendrá una fila para esa combinación de senal y puerto.
Nota: El orden por orden debe coincidir con el distinto en orden, lo que tiene sentido, ya que distinto toma el primer grupo distinto según algún orden.
Se supone que tiene un índice espacial en ambas tablas.
EDITAR 1 . Hay otra opción, que es utilizar los operadores <-> y <#> de Postgres (cálculos de distancia entre el punto central y el cuadro delimitador, respectivamente) que hacen un uso más eficiente del índice espacial y no requieren el truco ST_DWithin para evitar n ^ 2 comparaciones. Hay un buen artículo de blog que explica cómo funcionan. Lo más importante a tener en cuenta es que estos dos operadores funcionan en la cláusula 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;
EDITAR 2 . Como esta pregunta ha recibido mucha atención y los vecinos más cercanos (kNN) son generalmente un problema difícil (en términos de tiempo de ejecución algorítmico) en SIG, parece que vale la pena ampliar un poco el alcance original de esta pregunta.
La forma estándar de encontrar los vecinos más cercanos x de un objeto es usar una UNIÓN LATERAL (conceptualmente similar a una para cada bucle). Tomando descaradamente la respuesta de Dbaston , harías algo como:
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
Entonces, si desea encontrar los 10 puertos más cercanos, ordenados por distancia, simplemente tiene que cambiar la cláusula LIMIT en la subconsulta lateral. Esto es mucho más difícil de hacer sin LATERAL JOINS e implica el uso de la lógica de tipo ARRAY. Si bien este enfoque funciona bien, se puede acelerar enormemente si sabe que solo tiene que buscar a una distancia determinada. En este caso, puede usar ST_DWithin (signs.geom, ports.geom, 1000) en la subconsulta, que debido a la forma en que funciona la indexación con el operador <->, una de las geometrías debería ser una constante, en lugar de una referencia de columna: puede ser mucho más rápido. Entonces, por ejemplo, para obtener los 3 puertos más cercanos, dentro de los 10 km, podría escribir algo como lo siguiente.
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;
Como siempre, el uso variará dependiendo de su distribución de datos y consultas, por lo que EXPLAIN es su mejor amigo.
Finalmente, hay un problema menor, si usa IZQUIERDA en lugar de CROSS JOIN LATERAL en el que debe agregar ON TRUE después del alias de consultas laterales, por ejemplo,
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;