【问题标题】:How can I extend this SQL query to find the k nearest neighbors?如何扩展此 SQL 查询以查找 k 个最近邻居?
【发布时间】:2011-02-01 03:37:23
【问题描述】:

我有一个充满二维数据的数据库 - 地图上的点。每条记录都有一个几何类型的字段。我需要做的是将一个点传递给一个存储过程,该过程返回 k 最近的点(k 也将传递给存储过程,但这很容易)。我在http://blogs.msdn.com/isaac/archive/2008/10/23/nearest-neighbors.aspx 找到了一个查询,它获取了一个最近的邻居,但我不知道如何扩展它以找到 k 个最近的邻居。

这是当前查询 - T 是表,g 是几何字段,@x 是要搜索的点,Numbers 是整数 1 到 n em>:

DECLARE @start FLOAT = 1000; 
WITH NearestPoints AS
(
     SELECT TOP(1) WITH TIES *,  T.g.STDistance(@x) AS dist
     FROM Numbers JOIN T WITH(INDEX(spatial_index)) 
     ON T.g.STDistance(@x) < @start*POWER(2,Numbers.n)
     ORDER BY n
)
SELECT TOP(1) * FROM NearestPoints
ORDER BY n, dist

内部查询选择最近的非空区域,外部查询然后选择该区域的顶部结果;外部查询可以很容易地更改为(例如)SELECT TOP(20),但如果最近的区域只包含一个结果,你就会被困住。

我想我可能需要递归搜索包含 k 记录的第一个区域,但不使用表变量(这会导致维护问题,因为您必须创建表结构并且它很容易改变 - 有很多字段),我看不出如何。

【问题讨论】:

  • 查找k条记录时,将INNER查询更改为TOP(1)以上对结果有什么影响? (当最近的区域只包含一个结果时)
  • 如果您更改内部查询以选择更多区域,您可以获得更多结果,但这并不能保证更多结果:其他区域可能只包含相同的单个结果(它们的大小呈指数增长) - 例如想象一下在附近有一个点但周围数百公里没有其他点的点周围搜索 - 前 n 个区域将只包含相同的 1 个点。
  • 是否找到了有效的解决方案?我正在寻找相同的解决方案。

标签: sql sql-server-2008 geospatial nearest-neighbor


【解决方案1】:

如果您从内部查询中删除 TOP (1) WITH TIES,并将外部查询设置为返回前 k 行,会发生什么情况?

我也很想知道这项修正案是否有帮助。它应该比使用TOP更有效:

DECLARE @start FLOAT = 1000
        ,@k INT = 20
        ,@p FLOAT = 2;

WITH NearestPoints AS
(
     SELECT *
            ,T.g.STDistance(@x) AS dist
            ,ROW_NUMBER() OVER (ORDER BY T.g.STDistance(@x)) AS rn
     FROM Numbers 
     JOIN T WITH(INDEX(spatial_index)) 
     ON   T.g.STDistance(@x) <  @start*POWER(@p,Numbers.n)
     AND (Numbers.n - 1 = 0 
          OR T.g.STDistance(@x) >= @start*POWER(@p,Numbers.n - 1)
         )
)
SELECT * 
FROM NearestPoints
WHERE rn <= @k;

注意 - 未经测试 - 我无法在此处访问 SQL 2008。

【讨论】:

  • @Smigs - 试试我所做的修改 - 也许在某处某处有对 int 的隐式转换(虽然我看不到)
  • 这是源查询中的错误 - POWER 返回其第一个参数的数据类型(常量 2 被解释为 INT)。修改了我的查询以反映这一点。
  • @Ed Harper,如果您将 k 设置为例如40,你得到40分回来。但如果点分布在多个区域中,它会返回重复项,因为区域 n 包含区域 n -1 中的所有点。当然这些可以在之后被过滤掉(记录有一个 ID 字段),但你最终会得到少于 k 个点!不过,它似乎确实比使用 TOP 更快!
  • @Smigs - 我认为原始查询不会像宣传的那样为 k 的任何值而不是 1 - 这是这种方法的一个基本问题。为避免这种情况,内部查询需要搜索排除所有先前已搜索的组的内容。这应该是可以的,但是我现在没时间看(本质上,join 中的ON 语句需要考虑下限和上限,而不仅仅是上限)。跨度>
  • @Smigs - 我做了另一个修改以反映我之前的笔记。
【解决方案2】:

引自Inside Microsoft® SQL Server® 2008: T-SQL Programming。第 14.8.4 节。

以下查询将返回 10 离@input 最近的兴趣点:

DECLARE @input GEOGRAPHY = 'POINT (-147 61)';
DECLARE @start FLOAT = 1000;
WITH NearestNeighbor AS(
  SELECT TOP 10 WITH TIES
    *, b.GEOG.STDistance(@input) AS dist
  FROM Nums n JOIN GeoNames b WITH(INDEX(geog_hhhh_16_sidx)) -- index hint
  ON b.GEOG.STDistance(@input) < @start*POWER(CAST(2 AS FLOAT),n.n)
  AND b.GEOG.STDistance(@input) >=
    CASE WHEN n = 1 THEN 0 ELSE @start*POWER(CAST(2 AS FLOAT),n.n-1) END
  WHERE n <= 20
  ORDER BY n
)
  SELECT TOP 10 geonameid, name, feature_code, admin1_code, dist
  FROM NearestNeighbor
  ORDER BY n, dist;

注意:此查询的 WHERE 仅部分 子句由空间支持 指数。但是,查询优化器 正确评估受支持的部分 (“=" 部分, 并且查询执行良好。改变 @start 的值有时可以 如果查询速度较慢,则加快查询速度 超出预期。

清单 2-1。创建和填充辅助数字表

SET NOCOUNT ON;
USE InsideTSQL2008;

IF OBJECT_ID('dbo.Nums', 'U') IS NOT NULL DROP TABLE dbo.Nums;

CREATE TABLE dbo.Nums(n INT NOT NULL PRIMARY KEY);
DECLARE @max AS INT, @rc AS INT;
SET @max = 1000000;
SET @rc = 1;

INSERT INTO Nums VALUES(1);
WHILE @rc * 2 <= @max
BEGIN
  INSERT INTO dbo.Nums SELECT n + @rc FROM dbo.Nums;
  SET @rc = @rc * 2;
END

INSERT INTO dbo.Nums
  SELECT n + @rc FROM dbo.Nums WHERE n + @rc <= @max;

【讨论】:

  • 我无法再使用工具来测试这个答案,所以我很犹豫是否将其标记为 Ed 的已接受答案——抱歉!
猜你喜欢
  • 2018-02-11
  • 2020-11-05
  • 2021-04-29
  • 2015-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-11
相关资源
最近更新 更多