web-dev-qa-db-ja.com

ツリー階層を取得するためのCTE再帰

特定の方法で、ツリーの順序付けられた階層を取得する必要があります。問題のテーブルは、このように見えます(すべてのIDフィールドはuniqueidentifiersです。例のためにデータを単純化しました)。

EstimateItemID EstimateID ParentEstimateItemID ItemType 
 -------------- ---------- ----------------- --- -------- 
 1 NULL製品
 2 A 1製品
 3 A 2サービス
 4 NULL製品
 5 A 4製品
 6 A 5サービス
 7 A 1サービス
 8 A 4製品

ツリー構造のグラフィカルビュー(*は「サービス」を示します):

 A 
 ___/\ ___ 
/\ 
 1 4 
/\/\ 
 2 7 * 5 8 
//
3* 6 * 

このクエリを使用して、階層を取得できます(「A」は一意の識別子であると偽装しますが、実際には存在しないことがわかります)。

DECLARE @EstimateID uniqueidentifier
SELECT @EstimateID = 'A'

;WITH temp as(
    SELECT * FROM EstimateItem
    WHERE EstimateID = @EstimateID

    UNION ALL

    SELECT ei.* FROM EstimateItem ei
    INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID
)

SELECT * FROM temp

これにより、EstimateID 'A'の子がテーブルに表示される順序で表示されます。すなわち:

EstimateItemID 
 -------------- 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8

残念ながら、必要なのは、次の制約に従う結果セットを持つ順序付けられた階層です。

 1。各ブランチはグループ化する必要があります
 2。 ItemTypeが 'product'で、親が最上位のレコードは
 3です。最上位ノード
の後にグループ化されたItemType 'product'および非NULLの親を持つレコード。 ItemTypeが 'service'のレコードは、ブランチの最下位ノードです。

したがって、この例では、結果が必要な順序は次のとおりです。

EstimateItemID 
 -------------- 
 1 
 2 
 3 
 7 
 4 
 5 
 8 
 6 

これを達成するには、クエリに何を追加する必要がありますか?

45
Woods8460

これを試して:

;WITH items AS (
    SELECT EstimateItemID, ItemType
    , 0 AS Level
    , CAST(EstimateItemID AS VARCHAR(255)) AS Path
    FROM EstimateItem 
    WHERE ParentEstimateItemID IS NULL AND EstimateID = @EstimateID

    UNION ALL

    SELECT i.EstimateItemID, i.ItemType
    , Level + 1
    , CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255))
    FROM EstimateItem i
    INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID
)

SELECT * FROM items ORDER BY Path

Pathを使用-親ノードでソートされた行a

各レベルでItemTypeで子ノードを並べ替える場合は、Level列のSUBSTRINGおよびPathで遊ぶことができます。..

ここ SQLFiddle サンプルデータ

77
Fabio

これは、上からのファビオの素晴らしいアイデアへのアドオンです。私が彼の元の投稿への返信で言ったように。他の人がフォローしやすいように、より一般的なデータ、テーブル名、およびフィールドを使用して彼のアイデアを再投稿しました。

ありがとう、ファビオ!ところで偉大な名前。

最初に使用するいくつかのデータ:

CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20));

INSERT INTO tblLocations (Code, ParentID, Name) VALUES
('A', NULL, 'West'),
('A', 1, 'WA'),
('A', 2, 'Seattle'),
('A', NULL, 'East'),
('A', 4, 'NY'),
('A', 5, 'New York'),
('A', 1, 'NV'),
('A', 7, 'Las Vegas'),
('A', 2, 'Vancouver'),
('A', 4, 'FL'),
('A', 5, 'Buffalo'),
('A', 1, 'CA'),
('A', 10, 'Miami'),
('A', 12, 'Los Angeles'),
('A', 7, 'Reno'),
('A', 12, 'San Francisco'),
('A', 10, 'Orlando'),
('A', 12, 'Sacramento');

再帰クエリ:

-- Note: The 'Code' field isn't used, but you could add it to display more info.
;WITH MyCTE AS (
  SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath
  FROM tblLocations T1
  WHERE ParentID IS NULL

  UNION ALL

  SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath
  FROM tblLocations T2
  INNER JOIN MyCTE itms ON itms.ID = T2.ParentID
)
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results.
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath
FROM  MyCTE 
ORDER BY TreePath;
14
ptownbro

CTEの結果に以下を追加する必要があると思います...

  1. BranchID =ブランチを一意に識別するある種の識別子。より具体的ではないことを許してください。しかし、あなたのニーズのためにブランチを特定するものがわかりません。この例では、すべてのブランチがルートに戻るバイナリツリーを示しています。
  2. ItemTypeIDここで(たとえば)0 =製品および1 =サービス。
  3. Parent =親を識別します。

それらが出力に存在する場合、クエリの出力を別のCTEまたはクエリのFROM句として使用できるはずです。 BranchID、ItemTypeID、Parentで並べ替えます。

0
DeadZone