无需Neo4j插件,基于Connection节点求A到C的带权最短路径
无需Neo4j插件实现指定最短路径查询
当然可以做到!不用任何第三方插件,咱们完全可以用Cypher原生的递归查询来实现需求——既基于Connection的cost属性找到从A到C的最短路径,又能完整保留Connection节点与Foo的CONTAINS关系,不会因为转换为关系而丢失这层关联。
核心思路
咱们可以通过逐步扩展路径的方式,每一步记录当前所在的Stop节点、路径包含的所有节点/关系、以及累积的总成本,直到到达目标节点C。最后从所有到达C的路径中筛选出总成本最小的那条即可。
完整Cypher查询示例
// 第一步:初始化路径,从起点A出发获取所有第一跳的路径 MATCH (start:Stop {name: 'A'}) MATCH (start)<-[:BEGINS_AT]-(firstConn:Connection)-[:ENDS_AT]->(nextStop:Stop) OPTIONAL MATCH (foo:Foo)-[:CONTAINS]->(firstConn) // 保留Foo与Connection的关联关系 WITH nextStop AS currentStop, // 记录路径中的所有节点:起点A、当前Connection、下一站Stop,若有Foo则一并加入 [start, firstConn, nextStop] + CASE WHEN foo IS NOT NULL THEN [foo] ELSE [] END AS pathNodes, // 记录路径中的所有关系:BEGINS_AT、ENDS_AT,若有CONTAINS则一并加入 [(:BEGINS_AT), (:ENDS_AT)] + CASE WHEN foo IS NOT NULL THEN [(:CONTAINS)] ELSE [] END AS pathRels, firstConn.cost AS totalCost WHERE currentStop.name <> 'C' // 先排除直接到达C的情况,后续统一处理 // 第二步:递归扩展中间路径,逐步累积 UNION ALL MATCH (currentStop:Stop)<-[:BEGINS_AT]-(nextConn:Connection)-[:ENDS_AT]->(newStop:Stop) OPTIONAL MATCH (foo:Foo)-[:CONTAINS]->(nextConn) WITH currentStop, newStop, nextConn, foo, pathNodes, pathRels, totalCost WHERE NOT newStop IN pathNodes // 避免路径循环(若允许重复站点可删除此条件) WITH newStop AS currentStop, pathNodes + [nextConn, newStop] + CASE WHEN foo IS NOT NULL THEN [foo] ELSE [] END AS pathNodes, pathRels + [(:BEGINS_AT), (:ENDS_AT)] + CASE WHEN foo IS NOT NULL THEN [(:CONTAINS)] ELSE [] END AS pathRels, totalCost + nextConn.cost AS totalCost WHERE currentStop.name <> 'C' // 第三步:收集所有到达终点C的路径 UNION ALL MATCH (currentStop:Stop)<-[:BEGINS_AT]-(finalConn:Connection)-[:ENDS_AT]->(end:Stop {name: 'C'}) OPTIONAL MATCH (foo:Foo)-[:CONTAINS]->(finalConn) WITH currentStop, finalConn, foo, pathNodes, pathRels, totalCost WITH pathNodes + [finalConn, end] + CASE WHEN foo IS NOT NULL THEN [foo] ELSE [] END AS fullPathNodes, pathRels + [(:BEGINS_AT), (:ENDS_AT)] + CASE WHEN foo IS NOT NULL THEN [(:CONTAINS)] ELSE [] END AS fullPathRels, totalCost + finalConn.cost AS totalCost // 第四步:筛选出总成本最小的路径 WITH fullPathNodes, fullPathRels, totalCost ORDER BY totalCost ASC LIMIT 1 RETURN fullPathNodes AS completePathNodes, fullPathRels AS completePathRels, totalCost AS minimumTotalCost
关键细节说明
- 递归逻辑:通过
UNION ALL拼接初始路径、中间扩展路径和终点路径,实现路径的逐步累积扩展。 - 保留CONTAINS关系:用
OPTIONAL MATCH抓取每个Connection对应的Foo节点和CONTAINS关系,并将其加入路径集合,确保这层关联不会丢失。 - 避免循环路径:通过
WHERE NOT newStop IN pathNodes防止路径出现循环(如果业务允许重复经过同一站点,可以去掉这个条件)。 - 严格单向性:完全遵循Connection的单向规则,只从
BEGINS_AT指向的Stop出发,向ENDS_AT指向的Stop扩展,不会反向遍历。
简化版(若无需显式保留Foo关系)
如果你的需求只关注最短路径的站点序列和总成本,不需要显式输出Foo节点及CONTAINS关系,可以用更简洁的查询:
MATCH (start:Stop {name: 'A'}) // 递归查询所有到达C的有效路径,并计算总成本 CALL { WITH start MATCH (start)<-[:BEGINS_AT]-(c1:Connection)-[:ENDS_AT]->(s1:Stop) OPTIONAL MATCH (s1)<-[:BEGINS_AT]-(c2:Connection)-[:ENDS_AT]->(s2:Stop) // 可根据需要继续扩展多跳,或者用递归的方式适配任意跳数 // 这里用reduce计算总成本,只累加Connection的cost WITH [start, c1, s1] + CASE WHEN s2 IS NOT NULL THEN [c2, s2] ELSE [] END AS path, reduce(total = 0, conn IN [c1, c2] WHERE conn IS NOT NULL | total + conn.cost) AS totalCost WHERE last(nodes(path)).name = 'C' RETURN path, totalCost } ORDER BY totalCost ASC LIMIT 1 RETURN path, totalCost
不过这个简化版更适合跳数较少的场景,若要适配任意跳数,还是前面的完整递归查询更可靠。
内容的提问来源于stack exchange,提问作者Erik Hofer
相关产品推荐
相关产品推荐

