すべての子供の合計を見つける再帰的CTE


16

これは、再帰T-SQLクエリ(おそらくCTE)を使用して検索したいアセンブリツリーです。以下の結果が期待されます。任意の部品について、アセンブリごとの合計金額を知りたい。

つまり、「Rivet」を検索すると、直接の子の数だけでなく、アセンブリ内の各レベルの合計数を知りたいのです。

Assembly (id:1)
    |
    |-Rivet
    |-Rivet
    |-SubAssembly (id:2)
    |   |
    |   |-Rivet
    |   |-Bolt
    |   |-Bolt
    |   |-SubSubAssembly (id:3)
    |      |
    |      |-Rivet
    |      |-Rivet
    |
    |-SubAssembly (id:4)
       |-Rivet
       |-Bolt

    DESIRED Results
    -------
    ID, Count
    1 , 6
    2 , 3
    3 , 2
    4 , 1

現在、直接の親を取得できますが、CTEを拡張してこの情報を上にロールアップできるようにする方法を知りたいです。

With DirectParents AS(
--initialization
Select InstanceID, ParentID
From Instances i 
Where i.Part = 'Rivet'

UNION ALL
--recursive execution
Select i.InstanceID, i.ParentID
From PartInstances i  INNER JOIN DirectParents p
on i.ParentID = p.InstanceID

)

select ParentID, Count(instanceid) as Totals
from DirectParents
group by InstanceID, ParentID

Results
-------
ID, Count
1 , 2
2 , 2
3 , 2
4 , 1

作成スクリプト

CREATE TABLE [dbo].[Instances] ( 
  [InstanceID] NVARCHAR (50) NOT NULL, 
  [Part] NVARCHAR (50) NOT NULL, 
  [ParentID] NVARCHAR (50) NOT NULL, );



INSERT INTO Instances 
Values 
  (1, 'Assembly', 0), 
  (50, 'Rivet', 1), 
  (50, 'Rivet', 1), 
  (2, 'SubAssembly', 1), 
  (50, 'Rivet', 2), 
  (51, 'Bolt', 2), 
  (51, 'Bolt', 2), 
  (3, 'SubSubAssembly', 2), 
  (50, 'Rivet', 3), 
  (50, 'Rivet', 3), 
  (4, 'SubAssembly2', 1), 
  (50, 'Rivet', 4), 
  (51, 'Bolt', 4)

回答:


14

この再帰CTE(SQL Fiddle)は、サンプルで機能するはずです。

WITH cte(ParentID) AS(
    SELECT ParentID FROM @Instances WHERE [Part] = 'Rivet'
    UNION ALL
    SELECT i.ParentID FROM cte c
    INNER JOIN @Instances i ON c.ParentID = i.InstanceID
    WHERE i.ParentID > 0
)
SELECT ParentID, count(*) 
FROM cte
GROUP BY ParentID
ORDER BY ParentID
;

出力

ParentID    Count
1           6
2           3
3           2
4           1

注:コメントでは、質問には単純化されたサンプルテーブルのみが含まれ、実際のデータには適切なインデックスがあり、重複とデータを適切に処理することを述べました。

使用データSQL Fiddle):

DECLARE @Instances TABLE( 
    [InstanceID] int NOT NULL
    , [Part] NVARCHAR (50) NOT NULL
    , [ParentID] int NOT NULL
);

INSERT INTO @Instances([InstanceID], [Part], [ParentID])
VALUES 
    (1, 'Assembly', 0)
    , (50, 'Rivet', 1)
    , (50, 'Rivet', 1)
    , (2, 'SubAssembly', 1)
    , (50, 'Rivet', 2)
    , (51, 'Bolt', 2)
    , (51, 'Bolt', 2)
    , (3, 'SubSubAssembly', 2)
    , (50, 'Rivet', 3)
    , (50, 'Rivet', 3)
    , (4, 'SubAssembly2', 1)
    , (50, 'Rivet', 4)
    , (51, 'Bolt', 4)
;

すばらしい回答ありがとうございます!すべてのインスタンス[アセンブリ、リベットなど]に対してこれを再帰的に実行する簡単なソリューションはありますか?
greenhoorn

0

「量」の意味と、テーブル(?)のPartInstancesと列のidcountがサンプルのどこから来ているのかわかりませんが、サンプルデータから推測したものを計算しました。

;with ins as (
select [InstanceID], [Part],[ParentID],0 lvl
from instances where ParentID=0
union all
select i.[InstanceID], i.[Part],i.[ParentID], lvl+1
from instances i 
inner join ins on i.parentid=ins.InstanceID
)
select InstanceID,part,COUNT(*) cnt
from ins
group by instanceid,part

これがあなたにいくつかのアイデアを与えることを願っています。

更新

これはテストの例であることを理解していますが、あなたのデータはからすべてを破壊します1NF。ほとんどの場合、テーブルは2つに分けて正規化する必要があります。


コードの最初のセクションの結果を参照してください。「リベット」を検索するとき、ツリー内の任意のピースを指定して、すべてのリベットの合計を取得しようとしています。instanceID 1の意味は6です。意味は、完全なツリー構造に合計6個のリベットが含まれていることを意味します。
markokstate
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.