Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/google-chrome/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 查找不同表中对象之间的最小距离_Sql_Oracle_Oracle Sqldeveloper - Fatal编程技术网

Sql 查找不同表中对象之间的最小距离

Sql 查找不同表中对象之间的最小距离,sql,oracle,oracle-sqldeveloper,Sql,Oracle,Oracle Sqldeveloper,我有两个表,都包含XY坐标的对象: 表A: ID_A | X | Y -----|------|------ 100 | 32.2 | 25.6 101 | 36.2 | 22.1 102 | 31.7 | 39.2 103 | 42.7 | 15.6 104 | 24.5 | 29.9 表B: ID_B | X | Y -----|------|------ 200 | 55.3 | 25.1 201 | 21.5 | 54.2 202 | 67.3 | 66.6

我有两个表,都包含XY坐标的对象:

表A:

ID_A | X    | Y
-----|------|------
100  | 32.2 | 25.6
101  | 36.2 | 22.1
102  | 31.7 | 39.2
103  | 42.7 | 15.6
104  | 24.5 | 29.9
表B:

ID_B | X    | Y
-----|------|------
200  | 55.3 | 25.1
201  | 21.5 | 54.2
202  | 67.3 | 66.6
203  | 23.5 | 55.4
204  | 41.1 | 24.5
205  | 42.4 | 62.6
206  | 26.8 | 23.6
207  | 63.2 | 25.6
208  | 35.6 | 11.1
209  | 74.2 | 22.2
210  | 12.2 | 33.3
211  | 15.7 | 44.4
对于表A中的每个对象,我想在表B中找到最近的对象(对象之间的距离最小)。 所以结果应该是这样的(这里的距离是随机的…):

对象之间的距离:

SQRT( (A.X-B.X)*(A.X-B.X) + (A.Y-B.Y)*(A.Y-B.Y) )
所以我提出了这个问题:

SELECT DISTINCT A.ID_A
     , FIRST_VALUE (B.ID_B) OVER (PARTITION BY A.ID_A ORDER BY SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y)) ASC) AS ID_B
     , FIRST_VALUE (SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y))) OVER (PARTITION BY A.ID_A ORDER BY SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y)) ASC) AS DISTANCE
FROM TableA A, TableB B
它的工作原理应该是这样的,但问题是两个表都有大量的行(超过500k),而且这个查询相当慢(可能效率非常低)

如何优化此查询?(我正在使用Oracle SQL)
提前感谢。

如果您的表没有相应/匹配的行,请不要使用JOIN。使用两个单独的查询。否则,输出将包含500K*500K行。在我的示例中,我假设您的表是相关的,我所做的只是试图提供帮助

请参见下面的外部连接

除非您在最后一个查询示例中将其复制到post时出错,否则您的查询将运行很长时间,因为您将结果加倍,而忘记连接表a和表b。得到的是笛卡尔积:

SELECT DISTINCT A.ID_A
 , FIRST_VALUE (B.ID_B) OVER (PARTITION BY A.ID_A ORDER BY SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y)) ASC) AS ID_B
 , FIRST_VALUE (SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y))) OVER (PARTITION BY A.ID_A ORDER BY SQRT((A.X-B.X)*(A.X-B.X)+(A.Y-B.Y)*(A.Y-B.Y)) ASC) AS DISTANCE
 FROM TableA A, TableB B
WHERE a.id = b.id -- You missed this
/
除此之外,您正在使用DISTINCT。尝试添加join和drop distinct,看看它们的区别。选择所有行并记录执行/经过的时间。基于emp表避免区分的一般示例:

-- Distinct - runs longer --
SELECT DISTINCT d.deptno, dname FROM scott.dept D, scott.emp E WHERE D.deptno = E.deptno
/  
-- Same as Distinct - faster --
SELECT deptno, dname FROM scott.dept D 
 WHERE EXISTS (SELECT 'X' FROM scott.emp E WHERE E.deptno = D.deptno)
/
外部连接。下面的查询将返回表(dept)表和B中的所有行,即使B在表A中没有相应的行。运行查询并查看deptno=40。emp tablr中没有行,empname显示null。表A(在我的示例中是scott.dept)的行数似乎比表B(在我的示例中是emp.dept)的行数少。所以,我认为外部连接是答案:

SELECT d.deptno, e.ename
  FROM scott.dept d LEFT OUTER JOIN scott.emp e
    ON d.deptno = e.deptno
ORDER BY d.deptno
/

正如前面提到的dasblinkenlight一样,由于距离平方最短的行也将是距离最短的行,因此不需要计算每个行组合的平方根

我认为你最好的办法是尽量减少计算的总次数,所以这样做可能会加快速度:

SELECT ID_A,ID_B,SQRT(DISTANCE_SQUARED) DISTANCE FROM (
  SELECT ID_A,ID_B,DISTANCE_SQUARED,MIN(DISTANCE_SQUARED) OVER (PARTITION BY ID_A) MIN_DS FROM (
    SELECT A.ID_A,B.ID_B,
    POWER(A.X-B.X,2)+POWER(A.Y-B.Y,2) DISTANCE_SQUARED
    FROM
    TABLE_A A,
    TABLE_B B
  )
)
WHERE DISTANCE_SQUARED=MIN_DS
这可能会返回多个匹配项(如果表_B中的多行与表_a中的行具有相同的距离)。。。不确定这是否可以接受


如果不经常写入表,并且您需要经常运行此查询,您最好预先计算此信息并将其存储在另一个表中,例如table_C。当/如果向任一表添加或编辑一行时,您可以对照另一个表中的500k检查该行,并在必要时更新table_C,与其每次运行查询时都需要检查500k*500k行。

嗯,我想我更喜欢“预计算”CTE中的距离。我知道优化器应该能够缓存某些值,但我不确定它在这方面做得有多好。此外,这使得基于“距离”的维护更容易。不幸的是,您没有一个“最大距离”来最初排除某些值,这意味着这将总是稍微慢一些

WITH Distances (id_a, id_b, distance_squared, index) as 
                   (SELECT a.id_a, b.id_b, 
                           POWER((a.x - b.x), 2) + POWER((a.y - b.y), 2) d,
                           ROW_NUMBER() OVER(PARTITION BY a.id_a, ORDER BY d ASC)
                    FROM TableA a
                    CROSS JOIN TableB b)
SELECT id_a, id_b,
       SQRT(distance_squared)
FROM Distances
WHERE index = 1
使用
FIRST\u VALUE()
会导致“最小”值重复-删除这些值可以免除您对
独特的
的需要,这可能会有一些帮助


编辑: 如果您有“最大距离”,请尝试以下操作:

WITH Distances (id_a, id_b, distance_squared, index) as 
                   (SELECT a.id_a, b.id_b, 
                           POWER((a.x - b.x), 2) + POWER((a.y - b.y), 2) d,
                           ROW_NUMBER() OVER(PARTITION BY a.id_a, ORDER BY d ASC)
                    FROM TableA a
                    JOIN TableB b
                      ON (b.x > a.x - @distance AND b.x < a.x + @distance)
                         AND (b.y > a.y - @distance AND b.y < a.y + @distance)
                    WHERE d < POWER(@distance, 2))
SELECT id_a, id_b,
       SQRT(distance_squared) as distance
FROM Distances
WHERE index = 1
距离(id_a,id_b,距离的平方,索引)为
(选择a.id\u a、b.id\u b、,
功率((a.x-b.x),2)+功率((a.y-b.y),2)d,
行号()超过(按a.id分区,按d排序ASC)
从表a
加入表b
打开(b.x>a.x-@距离和b.xa.y-@距离和b.y
这可能能够在坐标值上使用索引,尽管我不确定(
TableB
侧,可能,
TableA
侧…不确定。必要时交换比较)。
这里要注意两件事:

  • 所有这些都是假设你在一个平面笛卡尔平面上操作。如果这是针对地球表面上的点,那么方程要复杂得多;如果你仔细看的话,这里有很多关于它们的问题/答案
  • 你仍然需要得到/使用平方根距离,因为否则你会有东西隐藏在方格的角落里,这实际上是距离之外(大约40%)

  • 查找最短距离平方与查找最短距离相同,因此您可以安全地从
    分区
    order by
    子句中删除
    SQRT
    。这将使计算速度加快一点。谢谢。它确实加速了事情的发展——有一点;)。。。嗯,考虑到
    TableA.id
    !=
    TableB.id
    ,连接条件根本没有帮助。事实上,他需要笛卡尔积,因为他得到的是不同点之间的最短距离——他没有任何其他东西可以连接它们!现在,可以删除
    DISTINCT
    ,因为给定的元组无论如何都应该是唯一的,这对某些人会有所帮助。但我怀疑这是完整的故事…@Clockworks-这一切都取决于用户。只有用户知道全部情况。外接也许是答案……没错,笛卡尔积就是我需要的。当我不使用DISTINCT时,我会得到多个相同的条目。您能详细阐述一下您对外部联接的看法吗?@user2051102-对不起,我对
    FIRST\u VALUE()
    的实际作用概念错误,这在回顾中是显而易见的。@Art:-1-鉴于示例明确显示“联接”行没有匹配的ID,我认为这不是“由用户决定的”。事实上,如果您注意到,
    TableB
    中没有一行的id与
    TableA
    id匹配。如果
    WITH Distances (id_a, id_b, distance_squared, index) as 
                       (SELECT a.id_a, b.id_b, 
                               POWER((a.x - b.x), 2) + POWER((a.y - b.y), 2) d,
                               ROW_NUMBER() OVER(PARTITION BY a.id_a, ORDER BY d ASC)
                        FROM TableA a
                        JOIN TableB b
                          ON (b.x > a.x - @distance AND b.x < a.x + @distance)
                             AND (b.y > a.y - @distance AND b.y < a.y + @distance)
                        WHERE d < POWER(@distance, 2))
    SELECT id_a, id_b,
           SQRT(distance_squared) as distance
    FROM Distances
    WHERE index = 1