所以這是我迄今爲止所學和做的。感謝habo提及以下問題的意見; Infinite loop in CTE when parsing self-referencing table
首先,我決定至少'解決'我的問題,並寫了一些手動遞歸,這解決了我的問題,但不像CTE解決方案那樣'我'希望更容易閱讀如同執行手動遞歸解決方案。
手冊遞歸
/****************************/
/* CLAIMS AND PAYMENT LOGIC */
/****************************/
DECLARE @rows as INT = 0
DECLARE @relevantClaimIds as Table(
Debtor_ID INT,
Claim_ID int
)
SET NOCOUNT ON
--Get anchor condition
INSERT INTO @relevantClaimIds (Debtor_ID, Claim_ID)
select Debtor_ID, ID
from Claim c
WHERE OpenAmount <> 0
--Do recursion
WHILE @rows <> (SELECT COUNT(*) FROM @relevantClaimIds)
BEGIN
set @rows = (SELECT COUNT(*) FROM @relevantClaimIds)
--Subtracted
INSERT @relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN @relevantClaimIds rci on rci.Claim_ID = cc.AddedToClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join @relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Added
INSERT @relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN @relevantClaimIds rci on rci.Claim_ID = cc.SubstractedFromClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join @relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Payments
INSERT @relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM @relevantClaimIds f
join ClaimEntryReference cer on f.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join @relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
END
然後後,我收到並閱讀了評論我決定嘗試CTE解決方案,它看起來像這樣;
CTE遞歸
with Tree as
(
select Debtor_ID, ID AS Claim_ID, CAST(ID AS VARCHAR(MAX)) AS levels
from Claim c
WHERE OpenAmount <> 0
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.AddedToClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.SubstractedFromClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM Tree t
join ClaimEntryReference cer on t.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
)
select DISTINCT Tree.Debtor_ID, Tree.Claim_ID
from Tree
該解決方案確實是很多「短」,更容易對眼睛,但它實際上有更好的表現?
性能差異
手冊; CPU 16,讀取1793,持續時間13
CTE; CPU 47,讀取4001,持續時間48
結論
不知道它的原因在於在CTE溶液或它有完成之前做一個額外的迭代所需的VARCHAR投它的遞歸,但它實際上需要比手動遞歸更多的資源。
最終它可能與CTE,但看起來不是一切(感謝上帝;-))表現明智堅持與手動遞歸似乎是一個更好的路線。
檢測和處理數據中的循環的一種方法是[here](https://stackoverflow.com/questions/15080922/infinite-loop-cte-with-option-maxrecursion-0/15081353#15081353)。當你通過數據遞歸時,每一行都記錄探索到的路徑。任何複製路徑上已有元素的新行都會被忽略。 – HABO
這是處理循環的相當聰明的方式,說實話。我可能會花一些時間把它放進去,然後檢查兩種解決方案的性能,因爲如果我沒有弄錯的話,它實際上會循環一次,然後再檢測它。 –
好的,所以我按照你的建議重新建立了查詢,並發現這是表現明智的做法,它會讓你自己做遞歸。這可能是由於將ID轉換爲varchar,然後連接字符串。公平地說,由於存在很多不同的語句,我很難分析它,並且我一直無法獲得整個查詢的IO/CPU統計信息(而不是每個語句的語句) –