2017-07-26 106 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時,雖然性能指標完全相同。 –

相關問題