あなたはもうすぐそこにいます。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");
当然、これは注意して使用する必要があります。最小距離が大きい場合、そのセナルとポートの組み合わせに対して行が取得されないためです。
注:順序ごとの順序は、個別の順序と一致する必要があります。これは、distinctは何らかの順序に基づいて最初の別個のグループを取るためです。
両方のテーブルに空間インデックスがあることを前提としています。
編集1。別のオプションがあります。これは、Postgresの<->および<#>演算子を使用することです(それぞれ中心点と境界ボックスの距離計算)。これは、空間インデックスをより効率的に使用し、nを避けるためにST_DWithinハックを必要としません^ 2比較。それらがどのように機能するかを説明する良いブログ記事があります。一般的な注意事項は、これら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。この質問は多くの注目を集めており、GISではk最近傍(kNN)は一般に(アルゴリズムの実行時間に関して)難しい問題であるため、この質問の元の範囲をいくらか拡大する価値があります。
1つのオブジェクトのxの最近傍を見つける標準的な方法は、LATERAL JOINを使用することです(概念的にはfor eachループに似ています)。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)を使用できます。これは、インデックス作成が<->演算子で機能する方法のため、ジオメトリの1つが定数ではなく定数である必要があります列参照-より高速になる場合があります。したがって、たとえば、10 km以内の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はあなたの親友です。
最後に、CROSS JOIN LATERALの代わりにLEFTを使用する場合、ラテラルクエリエイリアスの後にON TRUEを追加する必要があるという、ちょっとした落とし穴があります。
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;