You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 03:55:03