你快到了。有一个小技巧是使用Postgres的distinct运算符,该运算符将返回每种组合的第一个匹配项-当您通过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 hack来避免n ^ 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。由于这个问题已经引起了广泛关注,并且k最近邻(kNN)通常是GIS中的一个难题(就算法运行时间而言),因此似乎有必要在此问题的原始范围上进行扩展。
查找一个对象的x个最近邻居的标准方法是使用LATERAL JOIN(概念上类似于每个循环的a)。从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),这是由于使用<->运算符进行索引的方式所致,其中一种几何形状应该是常量,而不是a列参考-可能要快得多。因此,例如,要获取10公里以内的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是您最好的朋友。
最后,如果使用LEFT而不是CROSS JOIN LATERAL,则有一个小问题,因为您必须在横向查询别名之后添加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;