查询树中某节点所有的后代节点

本文介绍两种查询数据库中树型结构的方法:递归函数和MySQL函数。递归函数通过递归调用自身获取子节点,而MySQL函数则一次性获取所有子孙节点的ID。

在数据库的设计里面,经常使用parentId字段来构建树型的表结构,如部门、知识点等,数据举例:物电学院->自动化系->08自动化->08自动化一班,类似于这样的数据结构。如何查询某个节点下的所有子孙节点,在程序设计中有两种方法。以如下表结构做说明:

CREATE TABLE `basic_depart` (
  `departId` char(32) CHARACTER SET latin1 NOT NULL COMMENT '部门ID',
  `parentId` char(32) CHARACTER SET latin1 DEFAULT NULL COMMENT '父ID',
  `nodeType` tinyint(4) DEFAULT NULL COMMENT '0:无下级;1:有下级',
  `name` varchar(50) DEFAULT NULL COMMENT '部门名称',
  `telephone` varchar(20) DEFAULT NULL COMMENT '联系电话',
  `sortOrder` tinyint(4) DEFAULT NULL COMMENT '排序',
  `remark` varchar(1000) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`departId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

1、写一个递归函数来递归查询

public List<Depart> getDepartTree(String parentId) throws SQLException {
  List<Depart> departList = dao.queryList("depart.getChildDepart", parentId);

  if (departList != null && departList.size() > 0) {
   Iterator<Depart> departIterator = departList.iterator();
   while (departIterator.hasNext()) {
    Depart depart = departIterator.next();
    depart.setSubDepartList(getDepartTree(depart.getDepartId()));
   }
  }

  return departList;
 }

  

递归函数比较简单,记住递归的2个要素:递归结束的条件和递归公式,一般不会有啥问题。

 

2、参考网上一哥们的思路,写一个求出所有子孙节点ID和自己的ID的mysql function

DROP FUNCTION IF EXISTS oes.depart_getChilds;
CREATE FUNCTION oes.`depart_getChilds`(rootId varchar(32)) RETURNS varchar(10000) CHARSET latin1
BEGIN
 DECLARE sTemp VARCHAR(10000);
  DECLARE sTempChd VARCHAR(10000);

  SET sTemp = rootId;
  SET sTempChd = rootId;
   
  WHILE sTempChd is not null DO
    SELECT group_concat(departId) INTO sTempChd FROM basic_depart where FIND_IN_SET(parentId,sTempChd) > 0;
    IF sTempChd is not null THEN 
     SET sTemp = concat(sTemp,',',sTempChd);
    END IF;
  END WHILE;
  RETURN sTemp;

END;

 

然后在程序或者ibatis里面设置查询条件类似下面:

select * from basic_depart where find_in_set(departId,depart_getChilds('root')) 

 

这里稍微说一下,我数据库的全套charset和collation都是UTF8。

 1.depart_getChilds函数RETURNS varchar(10000) CHARSET latin1,为什么要给返回值定义为charset latin1呢,因为貌似find_in_set函数的2个参数要求是latin1编码的(更正:不是find_in_set函数的问题,是departId字段的collation的设置问题)。不如此,就会报incorrect mix of collation的错误,为这个,浪费了我一个下午的时间。

 2.本来我想写一个通用的mysql函数,处理所有树型结构的table,把table的名称,parentId字段名称,Id字段名称通通作为参数传递给getChilds函数。结果天不遂人愿,mysql不支持在function和procedure里面使用动态sql,要命3000

 3.为什么要第2个方法呢,一个是简单,一个是因为做ibatis的sqlmap的时候用得上,比如:

 

<select id="getList" resultMap="questionRM">
  select * from ed_question where parentId is null
  <dynamic prepend="and">
   <isNotEmpty property="question.knowledgeId" prepend="and">
    find_in_set(knowledgeId , knowledge_getChilds(#question.knowledgeId#))
   </isNotEmpty>
   <isNotEmpty property="question.quesTypeId" prepend="and">
    quesTypeId = #question.quesTypeId#
   </isNotEmpty>
   <isNotEmpty property="question.status" prepend="and">
    status = #question.status#
   </isNotEmpty>
   <isNotEmpty property="question.difficulty" prepend="and">
    difficulty = #question.difficulty#
   </isNotEmpty>
   <isNotEmpty property="question.title" prepend="and">
    title like concat('%',#question.title#,'%')
   </isNotEmpty>
  </dynamic>
  order by createTime desc
 </select>

 

<think> 我们正在使用AntV X6图编辑库,需要获取某个节点的所有后代节点(包括子节点、孙节点等所有后代)。 在X6中,可以通过图实例的`getSuccessors`方法实现,该方法默认返回直接子节点。但通过设置`options`参数可以获取所有后代节点。 具体实现: 1. 使用`graph.getSuccessors(node, { deep: true })`,其中`deep: true`表示递归获取所有后代节点。 2. 注意:`getSuccessors`返回的是所有后代节点(包括子节点、孙节点等),但不包括自身。 示例代码: ```javascript const graph = new Graph({ ... }); // 假设有一个节点nodeA const nodeA = graph.getCellById('nodeA'); // 获取nodeA的所有后代节点 const descendants = graph.getSuccessors(nodeA, { deep: true }); // 输出后代节点ID console.log(descendants.map(node => node.id)); ``` 注意事项: - 如果节点没有后代,则返回空数组。 - 此方法返回的是节点数组(Cell[])。 另外,如果需要获取所有祖先节点(父节点、祖父节点等),可以使用`graph.getPredecessors(node, { deep: true })`。 下面是一个完整的验证demo: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>获取后代节点示例</title> <script src="https://unpkg.com/@antv/x6/dist/x6.js"></script> </head> <body> <div id="container" style="width: 800px; height: 600px; border: 1px solid #e8e8e8;"></div> <script> const graph = new X6.Graph({ container: document.getElementById('container'), grid: true, }); // 创建节点 const nodeA = graph.addNode({ id: 'A', x: 100, y: 100, width: 80, height: 40, label: '节点A' }); const nodeB = graph.addNode({ id: 'B', x: 300, y: 100, width: 80, height: 40, label: '节点B' }); const nodeC = graph.addNode({ id: 'C', x: 500, y: 100, width: 80, height: 40, label: '节点C' }); const nodeD = graph.addNode({ id: 'D', x: 300, y: 200, width: 80, height: 40, label: '节点D' }); const nodeE = graph.addNode({ id: 'E', x: 500, y: 200, width: 80, height: 40, label: '节点E' }); // 创建边 graph.addEdge({ source: 'A', target: 'B' }); graph.addEdge({ source: 'A', target: 'C' }); graph.addEdge({ source: 'B', target: 'D' }); graph.addEdge({ source: 'B', target: 'E' }); // 获取节点A的所有后代节点 const descendants = graph.getSuccessors(nodeA, { deep: true }); console.log('节点A的后代节点ID:', descendants.map(node => node.id)); // 预期输出:['B', 'C', 'D', 'E'] 或顺序可能不同 // 获取节点B的所有后代节点 const descendantsOfB = graph.getSuccessors(nodeB, { deep: true }); console.log('节点B的后代节点ID:', descendantsOfB.map(node => node.id)); // 预期输出:['D', 'E'] // 获取节点C的后代节点(应该没有后代) const descendantsOfC = graph.getSuccessors(nodeC, { deep: true }); console.log('节点C的后代节点ID:', descendantsOfC.map(node => node.id)); // 预期输出:[] </script> </body> </html> ``` 在控制台查看输出,验证结果。 注意:`getSuccessors`方法返回的后代节点不包括传入的节点自身。 §§相关问题§§ 1. AntV X6中如何获取一个节点的所有祖先节点? 2. 在X6中有没有方法可以同时获取节点后代节点和边? 3. 如何判断两个节点之间是否存在连接路径? 4. 在X6中如何高效地查找某个节点的所有兄弟节点? 5. 如何获取从某个节点出发的所有路径(包括分支)? </think> ### AntV X6 获取节点所有后代节点的方法 在 AntV X6 中获取节点的所有后代节点(包括子节点、孙节点等所有后代),可以使用 `graph.getSuccessors()` 方法配合 `deep: true` 参数。以下是完整实现方案: #### 核心方法与验证 Demo ```html <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>获取后代节点示例</title> <script src="https://unpkg.com/@antv/x6/dist/x6.js"></script> </head> <body> <div id="container" style="width:800px;height:600px;border:1px solid #e8e8e8"></div> <script> const graph = new X6.Graph({ container: document.getElementById('container'), grid: true, }); // 创建测试节点 const nodeA = graph.addNode({ id: 'A', x: 100, y: 100, width: 80, height: 40, label: '根节点' }); const nodeB = graph.addNode({ id: 'B', x: 250, y: 50, width: 80, height: 40, label: '子节点1' }); const nodeC = graph.addNode({ id: 'C', x: 250, y: 150, width: 80, height: 40, label: '子节点2' }); const nodeD = graph.addNode({ id: 'D', x: 400, y: 30, width: 80, height: 40, label: '孙节点1' }); const nodeE = graph.addNode({ id: 'E', x: 400, y: 100, width: 80, height: 40, label: '孙节点2' }); // 创建节点间的连接关系 graph.addEdge({ source: 'A', target: 'B' }); graph.addEdge({ source: 'A', target: 'C' }); graph.addEdge({ source: 'B', target: 'D' }); graph.addEdge({ source: 'B', target: 'E' }); // 获取节点所有后代的方法 function getAllDescendants(node) { return graph.getSuccessors(node, { deep: true, // 递归获取所有后代 inbound: false, // 仅获取出口连接的后代 outbound: true // 沿输出方向遍历 }); } // 测试获取节点A的所有后代 const descendants = getAllDescendants(nodeA); console.log("根节点后代节点:", descendants.map(n => n.id)); // 输出: ['B', 'C', 'D', 'E'] (顺序可能不同) // 高亮显示所有后代节点 descendants.forEach(node => { node.attr('body/fill', '#ffeb3b'); // 黄色高亮 }); // 获取节点B的后代 const bDescendants = getAllDescendants(nodeB); console.log("节点B的后代:", bDescendants.map(n => n.id)); // 输出: ['D', 'E'] </script> </body> </html> ``` #### 方法详解 1. **`graph.getSuccessors()` 关键参数**: ```javascript graph.getSuccessors(node, { deep: true, // 递归获取所有后代节点 inbound: false, // 排除输入方向 outbound: true // 沿输出方向遍历 }) ``` 2. **其他相关方法**: - 获取节点:`graph.getPredecessors(node)` - 获取直接子节点(不递归):`graph.getSuccessors(node)` - 获取所有祖先节点:`graph.getPredecessors(node, { deep: true })` 3. **结果处理技巧**: ```javascript // 获取后代节点ID数组 const descendantIds = descendants.map(node => node.id); // 过滤特定类型的后代 const rectangleDescendants = descendants.filter( node => node.shape === 'rect' ); ``` #### 循环依赖处理方案 当图中存在循环引用时,需要添加防重复访问机制: ```javascript function getDescendantsSafely(node, visited = new Set()) { if (visited.has(node.id)) return []; // 防止循环引用 visited.add(node.id); const directChildren = graph.getSuccessors(node); let allDescendants = [...directChildren]; directChildren.forEach(child => { const grandChildren = getDescendantsSafely(child, visited); allDescendants = allDescendants.concat(grandChildren); }); return allDescendants; } ``` #### 使用场景示例 1. **影响分析**:删除节点前检查所有受影响的后代节点 2. **子图导出**:导出特定节点及其所有后代组成的子图 3. **权限控制**:检查用户是否有权访问所有后代节点 4. **布局计算**:计算以某节点为根的子树尺寸
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值