CTE递归获取树层次结构

Posted

技术标签:

【中文标题】CTE递归获取树层次结构【英文标题】:CTE Recursion to get tree hierarchy 【发布时间】:2013-08-09 00:57:18 【问题描述】:

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

EstimateItemID EstimateID ParentEstimateItemID ItemType
-------------- ---------- -------- ------ --
       1 一个空产品
       2个1个产品
       3一2服务
       4 空产品
       5一4产品
       6一5服务
       7一1服务
       8一4产品

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

一种 ___/ \___ / \ 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' 和 parent 的记录是顶部节点 3. 具有 ItemType 'product' 和非 NULL 父级的记录分组在顶部节点之后 4. ItemType 'service' 的记录是分支的底部节点

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

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

我需要在查询中添加什么来完成此操作?

【问题讨论】:

【参考方案1】:

试试这个:

;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 对子节点进行排序,那么您可以使用LevelSUBSTRINGPathcolumn....

这里SQLFiddle 带有数据样本

【讨论】:

太棒了。这是几年前的,但今天发现它很有用。但是,恕我直言,我发现原始帖子中提供的示例很难转化为更常见的解决方案。因此,我使用更常见的数据、表名和字段重新发布了您的(伟大的)想法,以便其他人更容易理解。 有什么方法可以对级别为 0 的 ItemType 进行排序,并且层次结构应该保持原样? 在 SQL Server 2017(和 ssms17)中使用上面的代码,对于使用的第二级(级别 + 1),我收到错误“无效的列名‘级别’”。知道如何解决吗? @farshad, here is Micorsoft SQL Server 2017 Fiddle 有效 还有一点要提一下,如果你想上树或下树,把join中的父/子列切换到cte。 cte.Child = table.Parent 上升,cte.Parent = table.Child 下降【参考方案2】:

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

【讨论】:

【参考方案3】:

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

    BranchID = 某种唯一标识分支的标识符。请原谅我没有更具体,但我不确定是什么确定了满足您需求的分支。您的示例显示了一个二叉树,其中所有分支都流回根。 ItemTypeID,其中(例如)0 = 产品,1 = 服务。 Parent = 标识父级。

如果输出中存在这些,我认为您应该能够将查询的输出用作另一个 CTE 或查询中的 FROM 子句。按 BranchID、ItemTypeID、Parent 排序。

【讨论】:

分支的根将由 ParentEstimateItemID 为 NULL 的记录标识。因此,“1”下的所有内容都是分支 x,而 4 下的所有内容都是分支 y。我对 sql 的熟练程度并不高,并且正在学习 CTE,所以请原谅我。是否需要在第一个 SELECT 语句中添加您的积分?

以上是关于CTE递归获取树层次结构的主要内容,如果未能解决你的问题,请参考以下文章

如何在T-SQL中使用递归CTE获得完整的层次结构?

使用MySQL 8.0递归CTE查找层次结构表中的直接后代并传播给父级

如何获得递归 CTE 中生成的最后一条记录?

SQL Server CTE 递归查询全解

具有递归 CTE 的 Postgres:在保留树结构的同时按受欢迎程度对子节点进行排序/排序(父节点始终高于子节点)

PostgreSQL递归查询示例