基于Cypher查询双关系Block树并格式化返回JSON结果
构建符合要求的Block树形JSON结构
核心思路
利用Cypher递归查询+自定义函数(或APOC工具),将NEXT_BLOCK关系转换为同级数组元素,BLOCK_CHILD关系转换为节点的children数组(包含子节点的完整NEXT_BLOCK链)。
方案一:自定义递归函数(Neo4j 4.4+)
这种方式代码简洁易维护,适合支持自定义函数的Neo4j版本。
1. 创建转换函数
// 转换单个节点:处理BLOCK_CHILD为children数组 CREATE OR REPLACE FUNCTION convertBlock(node:Block) :: MAP RETURN { id: node.id, content: node.content, // 替换为你的节点实际属性 type: node.type, children: CASE WHEN EXISTS((node)-[:BLOCK_CHILD]->()) THEN [ (node)-[:BLOCK_CHILD]->(child) | convertBlockChain(child) ] ELSE [] END } // 转换NEXT_BLOCK链:将同级节点按顺序转为数组 CREATE OR REPLACE FUNCTION convertBlockChain(startNode:Block) :: LIST<MAP> RETURN [convertBlock(startNode)] + CASE WHEN EXISTS((startNode)-[:NEXT_BLOCK]->()) THEN convertBlockChain( (startNode)-[:NEXT_BLOCK]->(nextNode) ) ELSE [] END
2. 调用函数生成树形结构
// 匹配根节点(根据实际情况调整条件,比如指定根节点ID) MATCH (root:Block) WHERE NOT EXISTS((root)<-[:NEXT_BLOCK|BLOCK_CHILD]-()) RETURN convertBlockChain(root) AS tree
方案二:APOC递归查询(兼容旧版本Neo4j)
如果你的Neo4j版本不支持自定义函数,用APOC的递归查询来实现:
// 匹配根节点 MATCH (root:Block) WHERE NOT EXISTS((root)<-[:NEXT_BLOCK|BLOCK_CHILD]-()) // 递归收集所有节点并构建节点映射 CALL apoc.path.subgraphNodes(root, { relationshipFilter: 'NEXT_BLOCK|BLOCK_CHILD>', uniqueness: 'NODE_GLOBAL' }) YIELD node WITH root, collect(node) AS allNodes WITH root, apoc.map.fromList([n.id, n IN allNodes | n]) AS nodeMap // 递归构建结构的核心逻辑 CALL apoc.cypher.run(' WITH $nodeMap, $rootId // 定义递归处理NEXT链的函数 CALL { WITH $nodeMap MATCH (n) WHERE id(n) = $rootId RETURN n.id AS currentId } WITH $nodeMap, currentId CALL apoc.cypher.run(\' WITH $nodeMap, $currentId MATCH (n) WHERE id(n) = $currentId WITH $nodeMap, n, $nodeMap[$currentId] AS nodeData // 处理子节点:递归构建子节点的NEXT链 WITH $nodeMap, n, nodeData, CASE WHEN EXISTS((n)-[:BLOCK_CHILD]->()) THEN [(n)-[:BLOCK_CHILD]->(child) | apoc.cypher.run(\"WITH $nodeMap, $childId CALL apoc.cypher.run(\\'WITH $nodeMap, $currentId MATCH (n) WHERE id(n) = $currentId WITH $nodeMap, n, $nodeMap[$currentId] AS nodeData WITH $nodeMap, nodeData, [ (nodeData)-[:NEXT_BLOCK]->(next) | next.id ] AS nextIds UNWIND nextIds AS nextId CALL apoc.cypher.run(\\\\'WITH $nodeMap, $currentId MATCH (n) WHERE id(n) = $currentId RETURN {id:n.id, content:n.content, type:n.type, children: CASE WHEN EXISTS((n)-[:BLOCK_CHILD]->()) THEN [(n)-[:BLOCK_CHILD]->(c) | apoc.cypher.run(\\\\\\\\'WITH $nodeMap, $childId CALL apoc.cypher.run(\\\\\\\\\\\\'WITH $nodeMap, $currentId RETURN {id:n.id, content:n.content, type:n.type} AS node\\\\\\\\\\\\', {nodeMap:$nodeMap, currentId:$childId}) YIELD value RETURN value.node\\\\\\\\', {nodeMap:$nodeMap, childId: (n)-[:BLOCK_CHILD]->(c).id}) YIELD value RETURN collect(value.node) END} AS node\\\\', {nodeMap:$nodeMap, currentId:nextId}) YIELD value WITH {id:nodeData.id, content:nodeData.content, type:nodeData.type, children: [(nodeData)-[:BLOCK_CHILD]->(c) | apoc.cypher.run(\\\\'WITH $nodeMap, $childId CALL apoc.cypher.run(\\\\\\\\'WITH $nodeMap, $currentId RETURN {id:n.id, content:n.content, type:n.type} AS node\\\\\\\\', {nodeMap:$nodeMap, childId:c.id}) YIELD value RETURN collect(value.node) END} AS currentNode RETURN [currentNode] + collect(value.node)\", {nodeMap:$nodeMap, childId:child.id}) YIELD value RETURN value[0]] ELSE [] END AS children // 构建当前节点结构 WITH {id:nodeData.id, content:nodeData.content, type:nodeData.type, children:children} AS currentNode // 处理下一个同级节点 WITH currentNode, [ (nodeData)-[:NEXT_BLOCK]->(next) | next.id ] AS nextIds UNWIND nextIds AS nextId // 递归获取下一个节点的结构 CALL apoc.cypher.run(\'WITH $nodeMap, $currentId MATCH (n) WHERE id(n) = $currentId WITH $nodeMap, n, $nodeMap[$currentId] AS nodeData WITH $nodeMap, nodeData, [ (nodeData)-[:NEXT_BLOCK]->(next) | next.id ] AS nextIds UNWIND nextIds AS nextId CALL apoc.cypher.run(\\\\'WITH $nodeMap, $currentId MATCH (n) WHERE id(n) = $currentId RETURN {id:n.id, content:n.content, type:n.type, children: CASE WHEN EXISTS((n)-[:BLOCK_CHILD]->()) THEN [(n)-[:BLOCK_CHILD]->(c) | apoc.cypher.run(\\\\\\\\'WITH $nodeMap, $childId CALL apoc.cypher.run(\\\\\\\\\\\\'WITH $nodeMap, $currentId RETURN {id:n.id, content:n.content, type:n.type} AS node\\\\\\\\\\\\', {nodeMap:$nodeMap, currentId:$childId}) YIELD value RETURN value.node\\\\\\\\', {nodeMap:$nodeMap, childId:c.id}) YIELD value RETURN collect(value.node) END} AS node\\\\', {nodeMap:$nodeMap, currentId:nextId}) YIELD value WITH {id:nodeData.id, content:nodeData.content, type:nodeData.type, children: CASE WHEN EXISTS((nodeData)-[:BLOCK_CHILD]->()) THEN [(nodeData)-[:BLOCK_CHILD]->(c) | apoc.cypher.run(\\\\'WITH $nodeMap, $childId CALL apoc.cypher.run(\\\\\\\\'WITH $nodeMap, $currentId RETURN {id:n.id, content:n.content, type:n.type} AS node\\\\\\\\', {nodeMap:$nodeMap, $childId:c.id}) YIELD value RETURN collect(value.node) END} AS currentNode RETURN [currentNode] + collect(value.node)\', {nodeMap:$nodeMap, currentId:nextId}) YIELD value RETURN [currentNode] + collect(value[0]) AS finalTree \', {nodeMap:$nodeMap, currentId:currentId}) YIELD value RETURN value.finalTree AS tree ', {nodeMap: nodeMap, rootId: id(root)}) YIELD value RETURN value.tree AS tree
关键注意事项
- 属性替换:所有代码中的
content、type等属性,需要替换为你的Block节点实际拥有的属性,或者用...node {.*}来获取所有属性(注意排除不需要的系统属性)。 - 根节点匹配:如果你的根节点不是无入边的,需要修改
MATCH (root:Block)后的WHERE条件,比如WHERE root.id = 'your-root-id'。 - 性能优化:对于大规模树结构,建议使用
uniqueness: 'NODE_GLOBAL'避免重复遍历,同时可以限制递归深度(在APOC路径查询中添加maxLevel参数)。
内容的提问来源于stack exchange,提问作者jli
相关产品推荐
相关产品推荐

