Sql server SQL Server:如何将CTE递归限制为刚刚递归添加的行? 简单的例子

Sql server SQL Server:如何将CTE递归限制为刚刚递归添加的行? 简单的例子,sql-server,common-table-expression,Sql Server,Common Table Expression,让我们尝试一个更简单的示例,这样人们就可以了解这些概念,并有一个实用的示例,您可以将其复制并粘贴到SQL查询分析器中: CREATE TABLE ##Nodes ( NodeID varchar(50) PRIMARY KEY NOT NULL, ParentNodeID varchar(50) NULL ) INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', null) INSERT INTO ##Nodes (NodeID,

让我们尝试一个更简单的示例,这样人们就可以了解这些概念,并有一个实用的示例,您可以将其复制并粘贴到SQL查询分析器中:

CREATE TABLE ##Nodes
(
 NodeID varchar(50) PRIMARY KEY NOT NULL,
 ParentNodeID varchar(50) NULL
)

INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', null)
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('B', 'A')
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('C', 'B')
想象一个具有继承权的节点表:

A
 - B
    - C
我们可以在查询分析器中开始测试:

CREATE TABLE ##Nodes
(
 NodeID varchar(50) PRIMARY KEY NOT NULL,
 ParentNodeID varchar(50) NULL
)

INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', null)
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('B', 'A')
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('C', 'B')
所需输出:

ParentNodeID    NodeID    GenerationsRemoved
============    ======    ==================
NULL            A         1
NULL            B         2
NULL            C         3
A               B         1
A               C         2
B               C         1
现在,建议的CTE表达式的输出不正确:

WITH NodeChildren AS
(
   --initialization
   SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
   FROM ##Nodes
   WHERE ParentNodeID IS NULL

   UNION ALL

   --recursive execution
   SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
   FROM NodeChildren AS P
      INNER JOIN ##Nodes AS N
      ON P.NodeID = N.ParentNodeID
)
SELECT ParentNodeID, NodeID, GenerationsRemoved
FROM NodeChildren
实际产出

ParentNodeID    NodeID    GenerationsRemoved
============    ======    ==================
NULL            A         1
NULL            B         2
NULL            C         3
注意:如果SQL Server 2005†CTE不能像我在2000年以前做的那样,那没关系,这就是答案。无论谁给出“这不可能”的答案都将赢得赏金。但我会等几天,以确保每个人都同意,这是不可能的,然后我无可挽回地给250的声誉,一个不解决我的问题

挑剔者角落

†不是2008年

‡不诉诸UDF*,这是我们已经拥有的解决方案

*除非您能在原始问题中找到改进UDF性能的方法


原始问题 我有一个节点表,每个节点都有一个指向另一个节点(或null)的父节点

举例说明:

1 My Computer
    2 Drive C
         4 Users
         5 Program Files
         7 Windows
             8 System32
    3 Drive D
         6 mp3
我想要一个返回所有父子关系以及它们之间的代数的表

对于所有直接父关系:

ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        1            1
1             2            1
2             4            1
2             5            1
2             7            1
1             3            1
3             6            1
7             8            1
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        2            2
(null)        3            2
1             4            2
1             5            2
1             7            2
1             6            2
2             8            2
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        4            3
(null)        5            3
(null)        7            3
(null)        6            3
1             8            3
但还有祖父母关系:

ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        1            1
1             2            1
2             4            1
2             5            1
2             7            1
1             3            1
3             6            1
7             8            1
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        2            2
(null)        3            2
1             4            2
1             5            2
1             7            2
1             6            2
2             8            2
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        4            3
(null)        5            3
(null)        7            3
(null)        6            3
1             8            3
还有曾祖父祖母的关系:

ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        1            1
1             2            1
2             4            1
2             5            1
2             7            1
1             3            1
3             6            1
7             8            1
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        2            2
(null)        3            2
1             4            2
1             5            2
1             7            2
1             6            2
2             8            2
ParentNodeID  ChildNodeID  GenerationsRemoved
============  ===========  ===================
(null)        4            3
(null)        5            3
(null)        7            3
(null)        6            3
1             8            3
因此,我可以计算出基本的CTE初始化:

WITH (NodeChildren) AS
{
   --initialization
   SELECT ParentNodeID, NodeID AS ChildNodeID, 1 AS GenerationsRemoved
   FROM Nodes
} 
现在的问题是递归部分。当然,显而易见的答案是行不通的:

WITH (NodeChildren) AS
{
   --initialization
   SELECT ParentNodeID, ChildNodeID, 1 AS GenerationsRemoved
   FROM Nodes

   UNION ALL

   --recursive execution
   SELECT parents.ParentNodeID, children.NodeID, parents.Generations+1
   FROM NodeChildren parents
    INNER JOIN NodeParents children
    ON parents.NodeID = children.ParentNodeID
} 

Msg 253, Level 16, State 1, Line 1
Recursive member of a common table expression 'NodeChildren' has multiple recursive references.
生成整个递归列表所需的所有信息都存在于inital CTE表中。但如果不允许,我会尝试:

WITH (NodeChildren) AS
{
   --initialization
   SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
   FROM Nodes

   UNION ALL

   --recursive execution
   SELECT parents.ParentNodeID, Nodes.NodeID, parents.Generations+1
   FROM NodeChildren parents
    INNER JOIN Nodes
    ON parents.NodeID = nodes.ParentNodeID
} 
但这失败了,因为它不仅连接递归元素,而且不断递归地反复添加相同的行:

Msg 530, Level 16, State 1, Line 1
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
在SQL Server 2000中,我使用用户定义函数(UDF)模拟了一个CTE:

防止爆炸的魔法是限制条件: 其中CurrentParents.Generations-@Generations-1


如何防止递归CTE永远递归

旁白:你们有SQL Server 2008吗?这可能适用于。

如果我理解您的意图,您可以通过这样做获得结果:

DECLARE @StartID INT;
SET @StartID = 1;
WITH CTE (ChildNodeID, ParentNodeID, [Level]) AS
(
  SELECT  t1.ChildNodeID, 
          t1.ParentNodeID, 
          0
  FROM tblNodes AS t1
  WHERE ChildNodeID = @StartID
  UNION ALL
  SELECT  t1.ChildNodeID, 
          t1.ParentNodeID, 
          t2.[Level]+1
  FROM tblNodes AS t1
    INNER JOIN CTE AS t2 ON t1.ParentNodeID = t2.ChildNodeID    
)
SELECT t1.ChildNodeID, t2.ChildNodeID, t1.[Level]- t2.[Level] AS GenerationsDiff
FROM CTE AS t1
  CROSS APPLY CTE t2

这将返回所有节点之间的生成差异,您可以根据具体需要对其进行修改。

好吧,您的答案不太明显:-)

这部分被称为递归CTE的“锚定”部分——但它实际上应该只从表中选择一行或几行——这将选择所有内容

我想你这里缺少的只是一个合适的WHERE子句:

WITH (NodeChildren) AS
{
   --initialization
   SELECT ParentNodeID, ChildNodeID, 1 AS GenerationsRemoved
   FROM Nodes
   **WHERE ParentNodeID IS NULL**
然而,我担心你的要求,不仅是“直”的层次结构,而且还有祖父母子女行,可能不是那么容易满足。。。。通常,递归CTE只会显示一个级别及其直接下属(当然,在层次结构中也是如此),它通常不会跳过一个、两个甚至更多级别

希望这有点帮助

Marc

试试这个:

WITH Nodes AS
(
   --initialization
   SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
   FROM ##Nodes

   UNION ALL

   ----recursive execution
   SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
   FROM Nodes AS P
      INNER JOIN ##Nodes AS N
      ON P.NodeID = N.ParentNodeID
   WHERE P.GenerationsRemoved <= 10

)
SELECT ParentNodeID, NodeID, GenerationsRemoved
FROM Nodes
ORDER BY ParentNodeID, NodeID, GenerationsRemoved
将节点作为
(
--初始化
选择ParentNodeID、NodeID、1作为已删除的代
来自##节点
联合所有
----递归执行
选择P.ParentNodeID、N.NodeID、P.GenerationsRemoved+1
作为P从节点开始
作为N的内部联接##节点
在P.NodeID=N.ParentNodeID上

其中P.GenerationsRemoved

问题在于Sql Server默认递归限制(100)。如果您在顶部尝试您的示例,删除了锚限制(也添加了Order By):

这将产生所需的结果。您面临的问题是,您将重复100次以上的行数较多,这是一个默认限制。这可以通过添加
选项(最大递归x)来更改
在查询之后,其中x是一个介于1和32767之间的数字。x也可以设置为0,这不会设置任何限制,但可能会很快对服务器性能产生非常有害的影响。很明显,随着节点中的行数增加,递归数可能会很快增加,除非有限制,否则我将避免这种方法表中行的已知上限。为完整起见,最终查询应如下所示:

 WITH NodeChildren AS
    (
       --initialization
       SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
       FROM Nodes

       UNION ALL

       --recursive execution
       SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
       FROM NodeChildren AS P
          inner JOIN Nodes AS N
          ON P.NodeID = N.ParentNodeID
    )
    SELECT * 
    FROM NodeChildren
    ORDER BY ParentNodeID
    OPTION (MAXRECURSION 32767)

如果32767可以向下调整以适应您的场景

您是否尝试在CTE中构建路径并使用它来识别祖先

然后可以从祖先节点深度中减去后代节点深度,以计算GenerationsRemoved列,如下所示

DECLARE @Nodes TABLE
(
    NodeId varchar(50) PRIMARY KEY NOT NULL,
    ParentNodeId varchar(50) NULL
)

INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('A', NULL)
INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('B', 'A')
INSERT INTO @Nodes (NodeId, ParentNodeId) VALUES ('C', 'B')

DECLARE @Hierarchy TABLE
(
    NodeId varchar(50) PRIMARY KEY NOT NULL,
    ParentNodeId varchar(50) NULL,
    Depth int NOT NULL,
    [Path] varchar(2000) NOT NULL
)

WITH Hierarchy AS
(
    --initialization
    SELECT NodeId, ParentNodeId, 0 AS Depth, CONVERT(varchar(2000), NodeId) AS [Path]
    FROM @Nodes
    WHERE ParentNodeId IS NULL

    UNION ALL

    --recursive execution
    SELECT n.NodeId, n.ParentNodeId, p.Depth + 1, CONVERT(varchar(2000), p.[Path] + '/' + n.NodeId)
    FROM Hierarchy AS p
    INNER JOIN @Nodes AS n
    ON p.NodeId = n.ParentNodeId
)
INSERT INTO @Hierarchy
SELECT *
FROM Hierarchy

SELECT parent.NodeId AS AncestorNodeId, child.NodeId AS DescendantNodeId, child.Depth - parent.Depth AS GenerationsRemoved
FROM @Hierarchy AS parent
INNER JOIN @Hierarchy AS child
ON child.[Path] LIKE parent.[Path] + '/%'

这打破了Chris Shaffer答案的递归限制

我创建了一个带有循环的表:

CREATE TABLE ##Nodes
(
   NodeID varchar(50) PRIMARY KEY NOT NULL,
   ParentNodeID varchar(50) NULL
)

INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', 'C');
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('B', 'A');
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('C', 'B');
在存在潜在循环的情况下(即ParentNodeId不为NULL),删除的生成从2开始。然后,我们可以通过检查(p.ParentNodeId==N.NodeID)来标识循环,我们只是不添加它。之后,我们附加省略的生成remove=1

WITH ParentNodes AS
(
   --initialization
   SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
   FROM ##Nodes
   WHERE ParentNodeID IS NULL

   UNION ALL

   SELECT P.ParentNodeID, N.NodeID, 2 AS GenerationsRemoved
   FROM ##Nodes N
   JOIN ##Nodes P ON N.ParentNodeID=P.NodeID
   WHERE P.ParentNodeID IS NOT NULL

   UNION ALL

   ----recursive execution
   SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
   FROM ParentNodes AS P
     INNER JOIN ##Nodes AS N
     ON P.NodeID = N.ParentNodeID
   WHERE P.ParentNodeID IS NULL OR P.ParentNodeID <> N.NodeID

),
Nodes AS (
   SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved 
   FROM ##Nodes 
   WHERE ParentNodeID IS NOT NULL

   UNION ALL

   SELECT ParentNodeID, NodeID, GenerationsRemoved FROM ParentNodes
)
SELECT ParentNodeID, NodeID, GenerationsRemoved
FROM Nodes
ORDER BY ParentNodeID, NodeID, GenerationsRemoved
将ParentNodes作为 ( --初始化 选择ParentNodeID、NodeID、1作为已删除的代 来自##节点 其中ParentNodeID为NULL 联合所有 选择P.ParentNodeID、N.NodeID、2作为已删除的生成 从##节点N 连接N.ParentNodeID=P.NodeID上的##节点P 其中P.ParentNodeID不为NULL 联合所有 ----递归执行 选择P.ParentNodeID、N.NodeID、P.GenerationsRemoved+1 从ParentNodes作为P 作为N的内部联接##节点 在P.NodeID=N.ParentNodeID上 其中P.ParentNodeID为NULL或P.ParentNodeID N.NodeID ), 节点作为( 选择ParentNodeID、NodeID、1作为已删除的代 来自##节点 其中ParentNodeID不为NULL 联合所有 选择ParentNodeID、NodeID、GenerationsRemoved FROM ParentNodes ) 选择ParentNodeID、NodeID、GenerationsRemoved 从节点 按父项排序NodeID、NodeID、GenerationsRemoved
,其中cte为
(
选择a=65,L=1