【问题标题】:Tree depth in SQLSQL 中的树深度
【发布时间】:2010-11-22 18:47:11
【问题描述】:

假设有一个名为tree_node 的表,其主键名为id,并有一个名为parent_id 的可空列,并且该表嵌入了树结构(或森林),那么如何计算深度在 SQL 中以有效的方式在树/森林中的节点?

【问题讨论】:

    标签: sql optimization query-optimization


    【解决方案1】:

    您需要递归功能。使用递归查询,这将是:

    WITH RECURSIVE node_ancestors(node_id, parent_id) AS (
            SELECT id, id FROM tree_node WHERE id IN (1, 2, 3)
        UNION ALL
            SELECT na.node_id, tree_node.parent_id
                FROM node_ancestors AS na, tree_node
                WHERE tree_node.id = na.parent_id AND tree_node.parent_id IS NOT NULL
    )
    SELECT node_id, COUNT(parent_id) AS depth FROM node_ancestors GROUP BY node_id;
    

    其他选项是在存储过程中执行递归、在应用程序中执行递归、限制递归量和使用大量连接。 (最后一个选项对于非平凡的深度并不是很有效)

    【讨论】:

    • 不错的功能。不幸的是,并非所有 SQL 实现都存在。
    【解决方案2】:

    如果深度是无限的,那么问题就是递归的,并没有真正简单有效的解决方案。 (可能有简单的异或有效解决方案。)如果您可以控制架构,并且需要定期访问此类数据,那么您可以将表重构为嵌套集,每个元素都有左右包含边界。这将允许您在具有基本条件的单个查询中计算节点的深度,大致如下:

    select count(*) from tree_node
        where left < myLeft
        and right > myRight
    

    【讨论】:

      【解决方案3】:

      处理树的一种常用方法是,除了 KEY 和 PARENT 的常规列之外,还有一个 PATH 类型的列,其中包含一个字符串值,包含构成路径的节点的键,从根,包括节点本身,由不允许成为键本身的一部分的字符分隔。

      让我举个例子:

      KEY        PARENT         PATH
      1          -              *1*
        2        1              *1*2*
        3        1              *1*3*
          4      3              *1*3*4*
        5        1              *1*5*
          6      5              *1*5*6*
      

      这主要用于变化不大的树,例如部门层次结构或类似的。

      我知道这样的字符串并不完全遵循规范化理论,因为它似乎使多个规则(多键、多值字段等)无效,但它在许多情况下都非常有用,包括你要的。

      在您的情况下,您只需检索相关节点的 TREE 值,并根据最简单的方法,计算分隔符的数量,或使用替换函数删除它们,并计算长度差异.

      这里的 SQL 为您提供上述节点列表及其深度:

      select KEY, PARENT, LEN(PATH)-LEN(REPLACE(PATH, '*', ''))-1 as DEPTH
      from NODES
      

      请注意,这种方法不需要任何特殊语法或数据库引擎对递归 SQL 的支持,并且非常适合索引。

      【讨论】:

      • 除了它没有使多值字段无效,因为它仍然只有一个特定的路径,一个值。此外,在存储路径的同时,您可以计算一次深度并存储它,如果它是一个常见的需求。
      【解决方案4】:

      不是一个非常聪明的解决方案,但无需任何额外的列且无需递归功能即可工作。

      你可以像这样进行左连接:

      select  p10.ParentId as parent10_id,
              p9.ParentId as parent9_id,
              p8.ParentId as parent8_id,
              p7.ParentId as parent7_id,
              p6.ParentId as parent6_id,          
              p5.ParentId as parent5_id,
              p4.ParentId as parent4_id,
              p3.ParentId as parent3_id,
              p2.ParentId as parent2_id,
              p1.ParentId as ParentId,
              p1.id as id
      from        tree_node p1
      left join   tree_node p2 on p2.id = p1.ParentId 
      left join   tree_node p3 on p3.id = p2.ParentId 
      left join   tree_node p4 on p4.id = p3.ParentId  
      left join   tree_node p5 on p5.id = p4.ParentId  
      left join   tree_node p6 on p6.id = p5.ParentId
      left join   tree_node p7 on p7.id = p6.ParentId
      left join   tree_node p8 on p8.id = p7.ParentId
      left join   tree_node p9 on p9.id = p8.ParentId
      left join   tree_node p10 on p10.id = p9.ParentId
      order       by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
      

      并检查最后一列中的非空值。如果第 8 列是第一个只有空值的列,那么您的深度是 7。

      这绝对不是一个优雅的解决方案,但应该适用于所有数据库。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-16
        • 2012-11-23
        • 1970-01-01
        相关资源
        最近更新 更多