Você está quase lá. Há um pequeno truque que é usar o operador distinto do Postgres , que retornará a primeira correspondência de cada combinação - conforme você solicita por ST_Distance, efetivamente ele retornará o ponto mais próximo de cada senal a cada 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 você sabe que a distância mínima em cada caso não passa de uma quantidade x (e você tem um índice espacial em suas tabelas), você pode acelerar isso colocando um WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
, por exemplo, se todas as distâncias mínimas forem conhecidas como não mais que 10 km, então:
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, isso precisa ser usado com cautela, como se a distância mínima fosse maior, você simplesmente não terá linha para essa combinação de senal e porto.
Nota: A ordem por ordem deve corresponder ao distinto no pedido, o que faz sentido, pois o distinto é pegar o primeiro grupo distinto com base em alguns pedidos.
Supõe-se que você tenha um índice espacial em ambas as tabelas.
EDIT 1 . Existe outra opção, que é usar os operadores <-> e <#> do Postgres (cálculos de distância do ponto central e da caixa delimitadora, respectivamente) que fazem um uso mais eficiente do índice espacial e não exigem o hack ST_DWithin para evitar n ^ 2 comparações. Há um bom artigo de blog explicando como eles funcionam. O aspecto geral a ser observado é que esses dois operadores trabalham na 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;
EDIT 2 . Como essa pergunta recebeu muita atenção e o k-vizinhos mais próximos (kNN) geralmente é um problema difícil (em termos de tempo de execução algorítmico) no GIS, parece valer a pena expandir um pouco o escopo original dessa questão.
A maneira padrão de encontrar os x vizinhos mais próximos de um objeto é usar um LATERAL JOIN (conceitualmente semelhante a um para cada loop). Tomando emprestado descaradamente a resposta do dbaston , você faria 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
Portanto, se você deseja encontrar as 10 portas mais próximas, ordenadas por distância, basta alterar a cláusula LIMIT na subconsulta lateral. Isso é muito mais difícil sem LATERAL JOINS e envolve o uso da lógica do tipo ARRAY. Embora essa abordagem funcione bem, ela pode ser acelerada enormemente se você souber que precisa apenas procurar a uma determinada distância. Nesse caso, você pode usar ST_DWithin (signs.geom, ports.geom, 1000) na subconsulta, que devido à maneira como a indexação funciona com o operador <-> - uma das geometrias deve ser uma constante, e não uma referência de coluna - pode ser muito mais rápido. Assim, por exemplo, para obter as 3 portas mais próximas, em 10 km, você pode escrever algo como o seguinte.
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 sempre, o uso varia de acordo com a distribuição e as consultas de dados, portanto EXPLAIN é seu melhor amigo.
Por fim, existe uma pequena pegadinha, se você usar ESQUERDA em vez de CROSS JOIN LATERAL, precisará adicionar ON TRUE após o alias das consultas laterais, por exemplo,
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;