Anda hampir sampai. Ada sedikit trik untuk menggunakan operator berbeda Postgres , yang akan mengembalikan kecocokan pertama dari setiap kombinasi - karena Anda memesan oleh ST_Distance, secara efektif itu akan mengembalikan titik terdekat dari setiap senal ke setiap port.
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");
Jika Anda tahu bahwa jarak minimum dalam setiap kasus tidak lebih dari jumlah x, (dan Anda memiliki indeks spasial pada tabel Anda), Anda dapat mempercepat ini dengan meletakkan WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)
, misalnya, jika semua jarak minumum diketahui tidak lebih dari 10 km, maka:
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");
Jelas, ini perlu digunakan dengan hati-hati, karena jika jarak minimum lebih besar, Anda tidak akan mendapatkan baris untuk kombinasi senal dan port.
Catatan: Pesanan demi pesanan harus sesuai dengan pesanan berbeda, yang masuk akal, karena berbeda adalah mengambil grup berbeda pertama berdasarkan beberapa pemesanan.
Diasumsikan bahwa Anda memiliki indeks spasial di kedua tabel.
EDIT 1 . Ada opsi lain, yaitu menggunakan operator <-> dan <#> Postgres, (masing-masing, perhitungan titik pusat dan kotak batas) yang menggunakan indeks spasial secara lebih efisien dan tidak memerlukan peretasan ST_DWithin untuk menghindari n ^ 2 perbandingan. Ada artikel blog bagus yang menjelaskan cara kerjanya. Hal umum yang perlu diperhatikan adalah bahwa kedua operator ini bekerja di klausa 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 . Karena pertanyaan ini telah menerima banyak perhatian dan k-tetangga terdekat (kNN) umumnya merupakan masalah yang sulit (dalam hal run-time algoritmik) di GIS, tampaknya ada gunanya untuk memperluas sedikit pada lingkup asli dari pertanyaan ini.
Cara standar untuk menemukan x tetangga terdekat dari satu objek adalah dengan menggunakan LATERAL JOIN (secara konseptual mirip dengan a untuk setiap loop). Meminjam tanpa malu dari jawaban dbaston , Anda akan melakukan sesuatu seperti:
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
Jadi, jika Anda ingin menemukan 10 port terdekat, dipesan berdasarkan jarak, Anda cukup mengubah klausa LIMIT di sub-kueri lateral. Ini jauh lebih sulit untuk dilakukan tanpa GABUNGAN LATERAL dan melibatkan penggunaan logika tipe ARRAY. Meskipun pendekatan ini bekerja dengan baik, itu dapat dipercepat jika Anda tahu Anda hanya perlu mencari jarak yang diberikan. Dalam contoh ini, Anda dapat menggunakan ST_DWithin (signs.geom, ports.geom, 1000) dalam subquery, yang karena cara pengindeksan bekerja dengan operator <-> - salah satu geometri harus berupa konstanta, bukan sebuah referensi kolom - mungkin jauh lebih cepat. Jadi, misalnya, untuk mendapatkan 3 pelabuhan terdekat, dalam jarak 10 km, Anda dapat menulis sesuatu seperti berikut ini.
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;
Seperti biasa, penggunaan akan bervariasi tergantung pada distribusi dan kueri data Anda, jadi EXPLAIN adalah teman terbaik Anda.
Akhirnya, ada gotcha minor, jika menggunakan LEFT daripada CROSS JOIN LATERAL , Anda harus menambahkan ON TRUE setelah permintaan lateral alias, misalnya,
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;