如何用递归CTE判断记录从属关系,避免自循环赋值?
判断记录是否为另一记录后代的实用方案
这个场景太常见了!我之前做文档管理系统的时候就碰到过一模一样的需求——要防止用户把父节点改成自己的后代,造成循环引用。下面给你几个实用的方案,完全不用硬编码层级数:
1. 迭代追溯法(推荐,无栈溢出风险)
这是最稳妥的方式,不管你的层级有多深,都不会出现递归栈溢出的问题。核心逻辑就是从待检查的节点出发,循环向上遍历父节点链,每一步都判断当前父节点是不是目标节点,直到走到根节点为止。
举个Python伪代码的例子(你可以根据自己的语言调整):
def is_descendant(child_id, target_id, get_parent_id): """ 参数说明: - child_id: 需要检查是否为后代的节点ID - target_id: 要判断是否为祖先的节点ID - get_parent_id: 自定义函数,输入节点ID返回其父节点ID(根节点或无父节点返回None/根ID) """ current_node_id = child_id # 循环直到走到根节点(ID为1)或无父节点 while current_node_id is not None and current_node_id != 1: parent_id = get_parent_id(current_node_id) # 如果找到目标节点,说明是后代 if parent_id == target_id: return True current_node_id = parent_id # 走到根节点还没匹配上,说明不是后代 return False
使用的时候很简单:比如你要把节点X的父节点改成Y,先调用is_descendant(Y, X, get_parent_id),如果返回True,就说明Y是X的后代,这时候直接拒绝修改请求就行;反之就允许修改。
2. 递归法(代码简洁,适合层级较浅的场景)
如果你的数据层级不会特别深(比如几十层以内),递归写法会更简洁,逻辑和迭代法完全一致,只是用递归代替循环:
def is_descendant_recursive(child_id, target_id, get_parent_id): parent_id = get_parent_id(child_id) # 走到根节点,终止递归 if parent_id is None or parent_id == 1: return False # 找到目标节点,返回True if parent_id == target_id: return True # 递归检查父节点的父节点 return is_descendant_recursive(parent_id, target_id, get_parent_id)
注意:如果你的数据层级特别深(比如几百层),递归可能会触发栈溢出错误,这时候还是迭代法更靠谱。
3. 数据库层面优化(适合大数据量场景)
如果你的主题数据存在数据库里,可以给每个节点加一个path字段,提前存储节点的完整祖先路径。比如:
- 根节点(ID=1)的
path字段值为/1/ - 根节点的子节点(ID=2)的
path为/1/2/ - 节点2的子节点(ID=3)的
path为/1/2/3/ - 以此类推...
这样判断后代关系的时候,直接用SQL就能搞定,效率极高:
-- 判断child_id是否是target_id的后代 SELECT 1 FROM topics WHERE id = :target_id AND (SELECT path FROM topics WHERE id = :child_id) LIKE CONCAT(path, '%');
如果查询返回结果,就说明child_id是target_id的后代。
这个方法的优点是查询速度快,不用在应用层做循环/递归;缺点是修改父节点的时候,需要更新该节点所有后代的path字段,一定要用事务保证数据一致性。
内容的提问来源于stack exchange,提问作者user1032531
相关产品推荐
相关产品推荐

