Ви майже там. Існує невелика хитрість, що полягає у використанні окремого оператора Postgres , який поверне першу відповідність кожної комбінації - як ви замовляєте ST_Distance, це ефективно поверне найближчу точку від кожного сеналу до кожного порту.
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");
Якщо ви знаєте, що мінімальна відстань у кожному випадку не більше деякої суми x, (і у вас є просторовий індекс на ваших таблицях), ви можете прискорити це, поставивши WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
, наприклад, якщо, як відомо, всі мінімальні відстані не більше 10 км, то:
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");
Очевидно, це потрібно використовувати обережно, так як якщо мінімальна відстань більша, ви просто не отримаєте рядок для комбінації сеналу та порту.
Примітка: Порядок за наказом повинен відповідати окремому за замовленням, що має сенс, оскільки окреме приймає першу окрему групу на основі певного впорядкування.
Передбачається, що у вас обох таблиць є просторовий індекс.
ЗРІД 1 . Існує ще один варіант, який полягає у використанні операторів <-> та <#> Postgres (обчислення центральної точки та граничної рамки відповідно), які дозволяють ефективніше використовувати просторовий індекс та не вимагають злому ST_DWithin, щоб уникнути n ^ 2 порівняння. Є хороша стаття в блозі, яка пояснює, як вони працюють. Загальне, що слід зазначити, ці два оператори працюють у пункті ЗАМОВЛЕННЯ ПО.
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 . Оскільки це питання привертає багато уваги, а k-найближчі сусіди (kNN), як правило, є складною проблемою (з точки зору алгоритмічного виконання) в ГІС, здається, варто трохи розширити вихідну сферу застосування цього питання.
Стандартний спосіб пошуку x найближчих сусідів одного об’єкта - це використання ЛІТЕРАЛЬНОГО ПРИЄДНАННЯ (концептуально подібного до кожного циклу). Безсоромно запозичивши відповідь дбастона , ви зробите щось на кшталт:
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
Отже, якщо ви хочете знайти найближчі 10 портів, упорядкованих за відстані, вам просто потрібно змінити пункт LIMIT у бічному підзапиті. Це набагато складніше обійтися без ЛІТЕРАЛЬНИХ ПРИЄДНАНЬ та передбачає використання логіки типу ARRAY. Незважаючи на те, що цей підхід працює добре, він може бути надзвичайно прискорений, якщо ви знаєте, що вам потрібно лише шукати на певну відстань. У цьому випадку ви можете використовувати ST_DWithin (знаки.geom, ports.geom, 1000) у підзапиті, який через спосіб індексації працює з оператором <-> - одна з геометрій повинна бути константою, а не посилання на стовпчик - може бути набагато швидшим. Так, наприклад, щоб отримати три найближчих порти, в межах 10 км, ви можете написати щось подібне.
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;
Як завжди, використання буде змінюватися залежно від вашого розповсюдження даних та запитів, тому EXPLAIN - ваш найкращий друг.
Нарешті, є незначна приналежність, якщо використовувати лівий замість CROSS JOIN LATERAL, що вам слід додати НАДАЛЬНО після псевдоніму бічних запитів, наприклад,
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;