假设有一个名为tree_node
的表,它有一个名为id
的主键,并且有一个名为parent_id
的可为空的列,并且该表嵌入了一个树结构(或森林),如何在SQL中以有效的方式计算树/林中节点的深度?
答案 0 :(得分: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;
其他选项是在存储过程中进行递归,在应用程序中执行递归并限制递归量并使用大量连接。 (对于非平凡的深度,最后一个选项并不真正有效)
答案 1 :(得分:2)
处理树的常用方法是,除了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,并且非常适合索引。
答案 2 :(得分:1)
如果深度是无限的,则问题是递归的,并且实际上并不是一个简单而有效的解决方案。 (可能有简单的xor高效解决方案。)如果您可以控制模式,并且需要定期访问此类数据,那么您可以将表重构为嵌套集,每个元素都有左右包含边界。这将允许您在具有基本条件的单个查询中计算节点的深度,大约:
select count(*) from tree_node
where left < myLeft
and right > myRight
答案 3 :(得分:0)
这不是一个非常聪明的解决方案,但是可以在没有任何其他列且没有递归功能的情况下使用。
您可以像这样进行左联接:
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。
这绝对不是一个很好的解决方案,但应该适用于所有数据库。