5

我有一个由大约 70,000 行和两列(两者VARCHAR(16))组成的表:idparent_id

我想填充一个“深度”列,显示特定记录与“根”节点的距离。

例如

id,parent_id,depth
A,NULL,0
B,A,1
C,A,1
D,B,2
E,D,3

等等

我首先根据类似问题的答案编写查询:

WITH myCTE(id, depth) AS
(
    SELECT id, 0 FROM objects where id = 'A'
    UNION ALL
    SELECT objects.id, depth + 1 FROM myCTE JOIN objects ON objects.parent_id = myCTE.id
)
SELECT id, depth FROM myCTE

使用我的数据集(约 80,000 行)执行上述操作需要将近两个小时!

然后,我将查询编写为循环并获得了更好的性能:

ALTER TABLE objects ADD depth INT NULL
DECLARE @counter int
DECLARE @total int
SET @counter = 0
UPDATE objects SET depth = 0 WHERE id = 'A'

SELECT @total = COUNT(*) FROM objects WHERE depth IS NULL

WHILE (@total > 0)
BEGIN
    UPDATE objects SET depth = @counter + 1 WHERE parent_id IN (
        SELECT id FROM objects WHERE depth = @counter
    )
    SELECT @total = COUNT(*) FROM objects WHERE depth IS NULL
    SET @counter = @counter + 1
END

上面的代码只需要几分钟(它的好处是将结果添加到现有表中)

我的问题是我的结果是否是典型的使用 CTE 来解决这个问题,或者是否有一些我忽略的东西可以解释它?索引,也许?(我现在桌子上没有)

4

2 回答 2

8

你需要一个关于parent_id. CTE 的递归部分将始终使用嵌套循环连接,并且不受连接提示的影响(结果被添加到堆栈假脱机,并且行按 LIFO 顺序一一处理)

如果没有索引,parent_id则需要在嵌套循环的内侧多次扫描表。性能将随着行数呈指数下降。

您的没有递归的查询将能够使用不同的连接类型(散列或合并),这些连接类型仅针对每个递归级别扫描表两次。在这种情况下很可能是哈希连接,因为您没有可以避免排序的有用索引。

于 2013-02-05T11:57:44.043 回答
0

您是否考虑过使用 HierarchyID 数据类型?它会让你的生活变得更轻松。

CREATE TABLE Groups.tblHierarchyNode
(
        NodeID              Int IDENTITY (0,1),
        NodeHID             HierarchyID NOT NULL,   -- DB Hierarchy ID of where I am in a tree
        HierarchyLevel      AS NodeHID.GetLevel(),  -- Numerical level of where I am in tree
)

我现在将它用于我的很多分层表。您必须更聪明地处理表格数量,但报告是轻而易举的事情,就像在层次结构中上下移动、获取祖先、后代等一样。

于 2013-02-05T16:04:42.133 回答