我有两个表,都包含具有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中最近的对象(对象之间的距离最小)。 所以结果应该是这样的(这里的距离是随机的......):
ID_A | ID_B | DISTANCE
-----|------|---------
100 | 203 | 12.5
101 | 203 | 11.1
102 | 211 | 16.5
103 | 205 | 14.2
104 | 209 | 17.7
物体之间的距离:
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) 提前谢谢。
答案 0 :(得分:1)
与提到的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
这可能会返回多个匹配项(如果TABLE_B中的多于一行与TABLE_A中的行具有相同的距离)...不确定这是否可接受。
如果表不是经常写入的,并且您需要经常运行此查询,那么最好先预先计算此信息并将其存储在另一个表中,例如TABLE_C。当/如果将行添加或编辑到任一表时,您可以检查另一个表中的500k的一行,并在必要时更新TABLE_C,而不是每次运行查询时都需要检查500k * 500k行。
答案 1 :(得分:1)
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_VALUE()
会导致“最小”值重复 - 删除它们会使您无需DISTINCT
,这可能有所帮助。
如果您有“最大距离”,请尝试以下操作:
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
这个可能能够在坐标值上使用索引,虽然我不确定(TableB
方,可能,TableA
方......不确定。必要时进行比较)
请注意两件事:
答案 2 :(得分:0)
如果您的表格没有对应/匹配行,请不要共同使用。使用两个单独的查询。否则您的输出将包含500K * 500K行。我假设你的表在我的例子中是相关的,我正在做的就是试图提供帮助。
请参阅下面的外部加入。
除非您在最终查询示例中将其复制到帖子中时出错,否则您的查询会运行很长时间,因为您将结果加倍,忘记连接表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。尝试添加连接和删除不同,看看差异。让所有行都被选中并记下执行/经过的时间。关于基于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)
/
外部加入。下面的查询将返回A表(dept)表和B中的所有行,即使B在表A中没有对应的行。运行查询并查看deptno = 40.它在emp tablr中没有行,并且为empname显示null。您的表A(在我的示例中为scott.dept)似乎比B(在我的示例中为emp)中的行少。所以,外部联接是我认为的答案:
SELECT d.deptno, e.ename
FROM scott.dept d LEFT OUTER JOIN scott.emp e
ON d.deptno = e.deptno
ORDER BY d.deptno
/