2017-07-28 74 views
0

我有一棵樹,在樹中特定的節點可以出現在樹中的另一個節點。 (在我的例子中爲2):SQL服務器跳過重複的關係(父子)在遞歸

   1 
      /  \ 
     2    3 
    / \     \ 
    4  5     6 
            \ 
            2 
           / \ 
           4  5 

注意2是重複的。第一下1,和第二下6. 我的遞歸是:

with cte (ParentId, ChildId, Field1, Field2) AS (
    select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 
    from BillOfMaterials BOM 
    WHERE ParentId=x 

    UNION ALL 

    SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM 
    JOIN cte on BOM.ParentId = cte.ChildId 
) 
select * from cte; 

但問題是,在結果關係2-4和2-5是重複的(首先從關係1-2和第二從關係6 -2):

ParentId ChildId     OtherFields 
    1   2 
    1   3 
    2   4 /*from 1-2*/ 
    2   5 /*from 1-2*/ 
    3   6 
    6   2 
    2   4 /*from 6-2*/ 
    2   5 /*from 6-2*/ 

有什麼辦法可以跳過訪問重複的關係嗎?我沒有看到任何邏輯爲什麼遞歸運行在已經在結果中的行上。它會更快。類似的東西:

 with cte (ParentId, ChildId, Field1, Field2) AS (
      select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 
      from BillOfMaterials BOM 
      WHERE ParentId=x 

      UNION ALL 

      SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM 
      JOIN cte on BOM.ParentId = cte.ChildId 
------>  WHERE (select count(*) FROM SoFarCollectedResult WHERE ParentId=BOM.ParentId AND ChildId=BOM.ChildId) = 0 
     ) 
     select * from cte; 

我發現this thread,但它是8歲。
我使用SQL Server 2016

如果這是不可能的,那麼我的問題是如何從最終結果中刪除重複,但檢查不同的只是在的ParentId和childID的列?

編輯:

預期的結果是:

ParentId ChildId     OtherFields 
    1   2 
    1   3 
    2   4 
    2   5 
    3   6 
    6   2 
+0

如何區分6的1和2孩子的2個孩子? –

+0

您的數據有瑕疵。你不能像那樣把它們放在一起。你怎麼知道一組2,4屬於1,另一組屬於6以下。如果你需要這種關係,你需要重建你的數據結構,因爲作爲一個標準的父母孩子,它不會工作。 –

+0

這裏有一個基本問題。樹可以根據不同的方法遍歷。沒有真正意義上的「第一」 - 誰來說哪個節點出現應該得到孩子。節點不能'實例化',然後通過他們的祖先進行區分,這使得它們不再是節點。 – Greenspark

回答

0

從改變你上次查詢:

​​

要:

select * from cte group by ParentId, ChildId; 

這實際上將採取你現在所擁有的,但更進一步,並刪除已經出現的行,這將照顧你的重複問題。只是要確保所有*返回這裏是ParentIdChildId,是否應該回到你要麼需要將它們添加到GROUP BY或應用某種聚合到它,這樣它仍然可以組(最大,最小,計數等欄目。 ..)。

你應該有,你不能對總體或一組多個行,你可以編寫查詢這樣:

select * from cte where ID in (select MAX(ID) from cte group by ParentId, ChildId); 

ID將是CTE主表的id。這將需要當行相匹配的最大的ID,這通常是你最新的入門,如果你想最早進入只是改變MAX()MIN()

0

可以,用增加2點小動作的SQL。

但你需要一個序列號一個額外的ID列。
例如,通過身份或日期時間字段顯示記錄何時插入。
對於原因很簡單,就數據庫而言,還有當他們插入,除非你有一列,指出爲了在記錄中沒有任何訂單。

竅門1)僅將CTE記錄加入更高的Id。因爲如果他們低於那些是你不想加入的副本。

招數二)使用窗口函數ROW_NUMBER得到的只有那些最接近標識遞歸從

開始舉例:

declare @BillOfMaterials table (Id int identity(1,1) primary key, ParentId int, ChildId int, Field1 varchar(8), Field2 varchar(8)); 

insert into @BillOfMaterials (ParentId, ChildId, Field1, Field2) values 
(1,2,'A','1-2'), 
(1,3,'B','1-3'), 
(2,4,'C','2-4'), -- from 1-2 
(2,5,'D','2-5'), -- from 1-2 
(3,6,'E','3-6'), 
(6,2,'F','6-2'), 
(2,4,'G','2-4'), -- from 6-2 
(2,5,'H','2-5'); -- from 6-2 

;with cte AS 
(
    select Id as BaseId, 0 as Level, BOM.* 
    from @BillOfMaterials BOM 
    WHERE ParentId in (1) 

    UNION ALL 

    SELECT CTE.BaseId, CTE.Level + 1, BOM.* 
    FROM cte 
    JOIN @BillOfMaterials BOM on (BOM.ParentId = cte.ChildId and BOM.Id > CTE.Id) 
) 
select ParentId, ChildId, Field1, Field2 
from (
    select * 
    --, row_number() over (partition by BaseId, ParentId, ChildId order by Id) as RNbase 
    , row_number() over (partition by ParentId, ChildId order by Id) as RN 
    from cte 
) q 
where RN = 1 
order by ParentId, ChildId; 

結果:

ParentId ChildId Field1 Field2 
-------- ------- ------ ------ 
1  2  A  1-2 
1  3  B  1-3 
2  4  C  2-4 
2  5  D  2-5 
3  6  E  3-6 
6  2  F  6-2 

無論如何,作爲旁註,通常使用不同的父子關係表。
更多的時候,它只是一個具有唯一父 - 子組合的表,這些組合是外鍵給另一個表,其中Id是主鍵。所以其他字段保存在另一個表中。

+0

但是在這種情況下,2 - 4和2 - 5也是重複的。例如,如果節點2下面有10個級別,則遞歸必須全部下降10級。如果可能,我想避免這種情況。 – Makla

+0

@Makla你能給你的問題添加一個預期的結果嗎?就像你現在表達的那樣,結果只有2-4個,而且任何地方都不會有2-5個。 – LukStorms