sql-server - SQL Server : How to limit CTE recursion to rows just recursivly added?

标签 sql-server common-table-expression

更简单的例子

让我们尝试一个更简单的示例,以便人们可以围绕概念进行思考,并有一个实际示例,您可以将其复制并粘贴到 SQL 查询分析器中:

想象一个 节点 表,具有层次结构:

A
 - B
    - C

我们可以在 Query Analizer 中开始测试:
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 性能的方法

原始问题

我有一个节点表,每个节点都有一个指向另一个节点(或空)的父节点。

举例说明:
1 My Computer
    2 Drive C
         4 Users
         5 Program Files
         7 Windows
             8 System32
    3 Drive D
         6 mp3

我想要一个返回所有父子关系的表,以及它们之间的代数

对于所有直接 parent 关系:
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

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

还有曾祖 parent 关系:
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.

生成整个递归列表所需的所有信息都存在于初始 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:
CREATE FUNCTION [dbo].[fn_NodeChildren] ()
RETURNS @Result TABLE (
    ParentNodeID int NULL,
    ChildNodeID int NULL,
    Generations int NOT NULL) 
AS  
/*This UDF returns all "ParentNode" - "Child Node" combinations
    ...even multiple levels separated
BEGIN 
    DECLARE @Generations int
    SET @Generations = 1

    --Insert into the Return table all "Self" entries
    INSERT INTO @Result
    SELECT ParentNodeID, NodeID, @Generations
    FROM Nodes
    WHILE @@rowcount > 0 
    BEGIN
        SET @Generations = @Generations + 1
        --Add to the Children table: 
        --  children of all nodes just added 
        -- (i.e. Where @Result.Generation = CurrentGeneration-1)
        INSERT @Result
        SELECT CurrentParents.ParentNodeID, Nodes.NodeID, @Generations
        FROM Nodes
            INNER JOIN @Result CurrentParents
            ON Nodes.ParentNodeID = CurrentParents.ChildNodeID
        WHERE CurrentParents.Generations = @Generations - 1
    END
    RETURN
END

防止它爆炸的魔法是限制 where 子句:
哪里 CurrentParents.Generations - @Generations-1

你如何防止递归 CTE 永远递归?

最佳答案

试试这个:

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

基本上从初始化查询中删除“只显示绝对 parent ”;这样,它会从它们中的每一个开始生成结果,然后从那里开始下降。我还在“WHERE P.GenerationsRemoved <= 10”中添加了一个无限递归捕获(用最多 100 的任意数字替换 10 以满足您的需要)。然后添加排序,使其看起来像您想要的结果。

关于sql-server - SQL Server : How to limit CTE recursion to rows just recursivly added?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/634971/

相关文章:

sql - 如何在 PostgreSQL 中转换这些数据

mysql - 基本WITH [别名] AS [查询] 错误

sql - 存储过程 OUTPUT 参数始终返回 NULL

c# - 通过 Entity Framework 执行 SProc 时未获取毫秒数据

php - 出现一般错误 : 4004 General SQL Server error:

SQL 多次更新单行(逐行)

sql - Postgresql:如何在 JOIN 中使用 WITH 子查询

sql - 针对具有重命名列的表的向后兼容 SQL 查询

java - JOOQ 使用转换器将字符串转换为枚举

sql-server - 为什么 SUM(real) 在 SQL Server 中返回 float 数据类型?