거의 다 왔습니다. ST_Distance에서 주문할 때 각 조합의 첫 번째 일치 항목을 반환하는 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)
, 예를 들어 모든 최소 거리가 10km를 넘지 않아야합니다.
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");
분명히, 최소 거리가 더 큰 것처럼, 단순히 senal과 port의 조합에 대한 행을 얻지 못하는 것처럼 조심스럽게 사용해야합니다.
참고 : 주문 별 주문은 주문에 따라 다릅니다. 이는 distinct가 일부 주문에 따라 첫 번째 고유 한 그룹을 취하므로 의미가 있습니다.
두 테이블 모두에 공간 인덱스가 있다고 가정합니다.
편집 1 . 공간 인덱스를보다 효율적으로 사용하고 n을 피하기 위해 ST_DWithin 핵을 필요로하지 않는 Postgres의 <-> 및 <#> 연산자 (각각 중심점 및 경계 상자 거리 계산)를 사용하는 또 다른 옵션이 있습니다 ^ 2 비교. 그들이 어떻게 작동하는지 설명하는 좋은 블로그 기사가 있습니다. 일반적으로이 두 연산자는 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;
편집 2 . 이 질문은 많은 주목을 받았으며 kNN (k-nearest neighbors)은 일반적으로 GIS에서 어려운 알고리즘 문제 (알고리즘 런타임으로)이므로이 질문의 원래 범위를 다소 확장하는 것이 좋습니다.
하나의 객체에서 가장 가까운 x 개의 이웃을 찾는 표준 방법은 LATERAL JOIN을 사용하는 것입니다 (개념적으로 각 루프와 유사 함). dbaston의 답변 에서 뻔뻔스럽게 빌리면 다음과 같은 작업을 수행합니다.
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 절을 변경하기 만하면됩니다. LATERAL JOINS 없이는 훨씬 어렵고 ARRAY 유형 논리를 사용합니다. 이 방법은 잘 작동하지만 주어진 거리까지만 검색하면된다는 것을 알고 있다면 엄청나게 빨라질 수 있습니다. 이 경우 하위 쿼리에서 ST_DWithin (signs.geom, ports.geom, 1000)을 사용할 수 있습니다.이 방법은 인덱싱이 <-> 연산자와 작동하는 방식 때문에 지오메트리 중 하나는 상수가 아니라 상수 여야합니다. 열 참조-훨씬 빠를 수 있습니다. 예를 들어, 10km 이내에 가장 가까운 3 개의 포트를 얻으려면 다음과 같이 작성할 수 있습니다.
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 이 가장 친한 친구입니다.
마지막으로 측면 쿼리 별칭 뒤에 TRUE 를 추가해야한다는 점에서 CROSS JOIN LATERAL 대신 LEFT 를 사용하는 경우 약간의 문제 가 있습니다 .
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;