sql - 根据相关节点从层次结构中选择行

标签 sql sql-server hierarchy

我有一个自引用表 Foo

[Id] int NOT NULL,
[ParentId] int NULL,   --Foreign key to [Id]
[Type] char(1) NOT NULL
[Id]是聚簇主键,索引位于 [ParentId][Type]列。

假设层次结构的最大深度为 1(子节点不能有子节点)。

我想获得满足以下条件的所有 Foo 行:
  • 类型是 A
  • 拥有 在其家谱中
  • 拥有 C D 在其家谱中

  • 以下使用 JOIN 的查询返回所需的结果,但性能很差
    SELECT DISTINCT [Main].*
    
    FROM Foo AS [Main]
    
    --[Main] may not be root node
    LEFT OUTER JOIN Foo AS [Parent]
        ON [Parent].[Id] = [Main].[ParentId]
    
    --Must have a B in tree
    INNER JOIN Foo AS [NodeB]
        ON (
            [NodeB].[Pid] = [Main].[Pid]            --Sibling
                OR [NodeB].[ParentId] = [Main].[Id] --Child
                OR [NodeB].[Id] = [Parent].[Id]     --Parent
        )
            AND [NodeB].[Type] = 'B'
    
    --Must have a C or D in tree
    INNER JOIN Foo AS [NodeCD]
        ON (
            [NodeCD].[Pid] = [Main].[Pid]            --Sibling
                OR [NodeCD].[ParentId] = [Main].[Id] --Child
                OR [NodeCD].[Id] = [Parent].[Id]     --Parent
        )
            AND [NodeCD].[Type] IN ('C', 'D')
    
    WHERE [Main].[Type] = 'A'
    

    从实际执行计划限制为仅查看 650,000 行中的前 10,000
    Query execution plan

    如果我从查询中删除 --Parent 行
    OR [NodeB].[Id] = [Parent].[Id]  --Parent
    OR [NodeCD].[Id] = [Parent].[Id] --Parent
    

    然后执行几乎是瞬间的,但它错过了 A 是 child 并且只有一个兄弟的情况
    Misses this:    Catches this:
    B               B
    ├A              ├A
    └C              ├B
                    └C
    

    我试图想出一个 CTE 来做到这一点,因为它在性能方面似乎更有希望,但我一直无法弄清楚如何排除那些不满足标准的树。

    到目前为止的 CTE
    WITH [Parent] AS 
    (
    SELECT  *
    FROM    [Foo]
    WHERE   [ParentId] IS NULL
    
    UNION ALL
    SELECT  [Child].*
    FROM    Foo AS [Child]
    JOIN    [Parent]
    ON      [Child].[ParentId] = [Parent].Id
    WHERE   [Child].[Type] = 'P'
    
    UNION ALL
    SELECT  [ChildCD].*
    FROM    Foo AS [ChildCD]
    JOIN    [Parent]
    ON      [ChildCD].[ParentId] = [Parent].Id
    WHERE   [ChildCD].[Type] IN ('C', 'D')
    )
    
    SELECT  *
    FROM [Parent]
    WHERE [Type] = 'I';
    

    但是,如果我尝试添加 Sibling-Child-Parent OR 语句,则会达到最大递归级别 100。

    SQL Fiddle with test data

    最佳答案

    被检查的节点是根节点的情况与它是子节点的情况有足够的区别,因此您最好分别查询两者并形成 UNION ALL两组中。但是,您可以使用公用表表达式进行简化,该表达式标识包含您要查找的节点的那些树。总的来说,这可能是这样的:

    WITH [TargetFamilies] AS (
        SELECT
          COALESCE(ParentId, Id) AS FamilyId
        FROM Foo
        GROUP BY COALESCE(ParentId, Id)
        HAVING 
          COUNT(CASE Type WHEN 'B' THEN 1 END) > 0
          AND COUNT(CASE Type WHEN 'C' THEN 1 WHEN 'D' THEN 1 END) > 0
    )
    
    -- root nodes
    SELECT [Main].*
    FROM
      Foo AS [Main]
      JOIN [TargetFamilies] ON [Main].Id = [TargetFamilies].FamilyId
    WHERE
      [Main].Type = 'A'
    
    UNION ALL
    
    -- child nodes
    SELECT 
      [Main].*
    FROM
      Foo AS [Main]
      JOIN [TargetFamilies] ON [Main].ParentId = [TargetFamilies].FamilyId
    WHERE
      [Main].Type = 'A'
    

    关于sql - 根据相关节点从层次结构中选择行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29496352/

    上一篇:ANT:执行命令

    下一篇:sql - 截断表权限

    相关文章:

    sql-server - SSRS 报告定义比服务器更新

    sql - 对象名称无效,但对象显然有效

    php - 具有多个父级的多对多层次结构 - PHP、MySQL

    sql - sysdate 的 to_date 函数存在问题

    sql-server - TSQL如何指定PIVOT的列列表范围?

    java - 如何对不同类类型的对象数组进行排序?

    ruby - 对 Ancestry 类型数据库 : MongoDB, Redis 等的建议?

    java - Java 时间戳/日期错误

    mysql - 为什么这个查询运行得这么慢?

    sql - 撤消对 SQL Server 2005 数据库的更改