所以这是我迄今为止所学和做的。感谢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统计信息(而不是每个语句的语句) –