4

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

4

4 回答 4

6

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

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;

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

于 2009-08-25T20:50:10.377 回答
1

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

select count(*) from tree_node
    where left < myLeft
    and right > myRight
于 2009-08-25T20:53:42.100 回答
1

处理树的一种常用方法是,除了 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 的支持,并且非常适合索引。

于 2009-08-25T21:05:40.187 回答
-1

这不是一个非常聪明的解决方案,但可以在没有任何额外列且没有递归功能的情况下工作。

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

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。

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

于 2019-12-04T22:07:19.050 回答