获取树层次结构的CTE递归

时间:2013-08-07 15:04:10

标签: sql-server common-table-expression

我需要以特定的方式获得树的有序层次结构。有问题的表看起来有点像这样(所有ID字段都是uniqueidentifiers,为了示例,我简化了数据):

EstimateItemID    EstimateID    ParentEstimateItemID     ItemType
--------------    ----------    --------------------     --------
       1              A                NULL              product
       2              A                  1               product
       3              A                  2               service
       4              A                NULL              product
       5              A                  4               product
       6              A                  5               service
       7              A                  1               service
       8              A                  4               product

树形结构的图形视图(*表示“服务”):

           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. each branch must be grouped
2. records with ItemType 'product' and parent are the top node 
3. records with ItemType 'product' and non-NULL parent grouped after top node 
4. records with ItemType 'service' are bottom node of a branch

所以,在这个例子中,我需要结果的顺序是:

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

我需要在查询中添加什么才能完成此任务?

3 个答案:

答案 0 :(得分:75)

试试这个:

;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 - 按父节点排序的行

如果您希望每个级别按ItemType排序子节点,则可以使用Level列的SUBSTRINGPath ....

此处SQLFiddle包含数据样本

答案 1 :(得分:14)

这是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;

答案 2 :(得分:0)

我认为你需要在CTE的结果中添加以下内容......

  1. BranchID =唯一标识分支的某种标识符。原谅我没有更具体,但我不确定是什么标识了一个分支满足您的需求。您的示例显示了一个二叉树,其中所有分支都回流到根目录。
  2. ItemTypeID其中(例如)0 =产品,1 =服务。
  3. 父母=识别父母。
  4. 如果输出中存在这些,我认为您应该能够将查询的输出用作查询中的另一个CTE或FROM子句。按BranchID,ItemTypeID,Parent排序。