2017-07-26 68 views
0

我有一个数据模型,它由'Claims'组成(使事情变得简单),只有一个OpenAmount字段。还有两个表,'ClaimCoupling'和'ClaimEntryReference'。查询以递归方式获取所有引用的实体

ClaimCoupling表直接引用回Claim表,而ClaimEntryReference实际上是可以通过多个声明预订的接收金额的预订(请参阅ClaimEntry_ID)。看到这个图;

enter image description here

为了简单起见,我删除了所有金额由这不是我目前正在与挣扎。

我想要的是一个查询,它将启动索赔表,并使用OpenAmount获取所有索赔,这是一个<> 0.但是,我希望能够打印出OpenAmount如何实现的准确报告这意味着我需要打印出与此索赔相关的任何索赔。为了使它更加有趣,同样的事情适用于预订,如果对X和Y索赔进行预订,并且只有X有未结数额,我想同时提取X和Y,因此我可以显示已预订的付款作为一个整体。

我试图用递归CTE做到这一点,但是这个(正确地)在circulair引用上爆炸了。我想我会解决这个问题有一个简单的WHERE语句,我会说,只有递归增加其尚未CTE的一部分,但是这是不允许的记录....

WITH coupledClaims AS (
    --Get all unique combinations 
    SELECT cc.SubstractedFromClaim_ID AS Claim_ID, 
      cc.AddedToClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc 
    UNION 
    SELECT cc.AddedToClaim_ID AS Claim_ID, 
      cc.SubstractedFromClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc 
), 
MyClaims as 
(
    SELECT * FROM Claim WHERE OpenAmount <> 0 
    UNION ALL 
    SELECT c.* FROM coupledClaims JOIN MyClaims mc ON coupledClaims.claim_id = mc.ID JOIN claim c ON c.ID = coupledClaims.linked_Claim_ID 
    WHERE c.ID NOT IN (SELECT ID FROM MyClaims) 
) 
SELECT * FROM MyClaims 

与用于倒过来摆弄后太久了,我决定用一个实际的循环来做... @@ Rowcount,只需手动将它们添加到一个表变量,但是当我写这个解决方案时(我确信我可以开始工作),我想我因为我不喜欢在TSQL中编写循环,因为我总觉得它很丑并且效率低下。

请参阅下面的数据模型和一些测试数据的sql小提琴(我注释掉了递归部分,否则我不被允许创建链接);

http://sqlfiddle.com/#!6/129ad5/7/0

我希望这里有人会处理这个问题(可能是我做得不对的递归CTE)的好方法。为了完成这是在MS SQL 2016完成的。

+1

检测和处理数据中的循环的一种方法是[here](https://stackoverflow.com/questions/15080922/infinite-loop-cte-with-option-maxrecursion-0/15081353#15081353)。当你通过数据递归时,每一行都记录探索到的路径。任何复制路径上已有元素的新行都会被忽略。 – HABO

+0

这是处理循环的相当聪明的方式,说实话。我可能会花一些时间把它放进去,然后检查两种解决方案的性能,因为如果我没有弄错的话,它实际上会循环一次,然后再检测它。 –

+0

好的,所以我按照你的建议重新建立了查询,并发现这是表现明智的做法,它会让你自己做递归。这可能是由于将ID转换为varchar,然后连接字符串。公平地说,由于存在很多不同的语句,我很难分析它,并且我一直无法获得整个查询的IO/CPU统计信息(而不是每个语句的语句) –

回答

0

所以这是我迄今为止所学和做的。感谢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,但看起来不是一切(感谢上帝;-))表现明智坚持与手动递归似乎是一个更好的路线。

+0

对于咯咯,可能值得运行基准再次使用'VarChar(8000)'(或其他合适的大小)而不是'VarChar(max)'。数据库引擎处理它们的方式很不一样 – HABO

+0

对于我所做的lulz,遗憾的是,8000是唯一合适的尺寸。如果我将它设置为更低,则会收到以下消息; '类型在递归查询的列“级别”的锚和递归部分之间不匹配。'。当在8000时,虽然性能指标完全相同。 –

相关问题