2013-02-05 45 views
5

我有一個由大約70,000行和兩列(均爲VARCHAR(16))組成的表格:idparent_idCTE與T-SQL循環用於確定對象層次結構的深度

我想填充一個「深度」列,顯示特定記錄離「根」節點的距離。

例如

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

我開始寫基於this answer查詢到類似的問題:

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 

隨着我的數據集(〜8萬行)以上需要近兩個小時執行!

然後我寫我的查詢作爲一個循環,並得到了更好的性能:

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解決這個問題的典型結果,還是我忽略了一些可能解釋它的問題?索引,也許? (我現在沒有在桌面上)

+0

哇。根據我的經驗,這聽起來很不典型。必須打開執行計劃才能看到兩者之間的比較? – Matt

+1

@Matt - 對於即使是中等大小的表而言,CTE的遞歸部分能夠通過索引查找或[性能可降級嚴重]得到滿足,這一點至關重要(http://dba.stackexchange.com/q/15596/ 3690) –

回答

8

您需要一個索引parent_id。 CTE的遞歸部分將始終使用嵌套循環連接,並且不受加入提示的影響(結果被添加到stack spool並且行按LIFO順序逐個處理)

沒有parent_id上的索引,它將需要在嵌套循環的內側多次掃描表格。性能將隨着行數呈指數級下降。

沒有遞歸的查詢將能夠使用不同的連接類型(散列或合併),只對每個遞歸級別掃描表兩次。在這種情況下,最有可能使用散列連接,因爲沒有可避免排序的有用索引。

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 
) 

我現在使用它作爲我的許多層次表。你必須在餐桌人口上變得更聰明一點,但報告是一件輕而易舉的事情,在層次結構中上下移動,獲得祖先,後代等等。