我正在评估Neo4j在生产环境中使用的过程中,我在做一些我希望简单的事情时遇到了一些困难。我已设法解决它,但是以一种次优和相当复杂的方式,所以我在想是否有更简单的方法来完成同样的事情。
序言
Neo4j版本2.3.2
TL; DR
这是一个很长的解释,所以摘要如下:
给定节点A,我需要找到A子图中的所有节点,复杂度为O(number_of_vertices + number_of_edges)。
问题
对于我们的用例,我们有一个图表,就一种特定类型的关系而言,它被分解为每个不超过几十个节点的较小的断开连接的子图。我们要完成的是,给定一个节点的索引id发现子图中的所有节点(同时将图形视为无向)。另一个特点是我们的节点总是具有从每个节点回到自身的自反边缘。
算法上我们所需要的是广度优先搜索,其复杂度为O(num_of_vertices + num_of_edges)。由于我们的图形不密集,它们中的边缘数量与顶点数量大致呈线性关系,因此整体复杂度应与其中的顶点数量成线性关系。
测试图
为简单起见,我将此测试图设为完全连接的测试图。由于重点是比较密码查询,因此不会影响结果。
命令:
简单查询
我尝试获得所需结果的第一个查询如下:
该查询从未终止。当我为关系模式添加上限并分析查询时,我得到以下内容:
因此,不是与顶点和边的数量成线性,而是看起来它找到了所有可能的路径,这些路径当然会随着深度而爆炸。
效果更佳的查询
为了实现这一点,我改为编写了以下查询:
https://gist.github.com/Dalamar42/1ec93cd74b01c145e7bd
(这将搜索深度2.重复第6-16行以搜索深度4,6,8等等)
查询执行以下操作:
除了一个复杂性之外,这个查询几乎应该具有BFS的复杂性。根据我的理解,每个中间MATCH / WHERE需要匹配至少一个节点,否则cypher返回在前面的步骤中找到的空的忽略节点。我通过将第4步更改为:
来解决这个问题"从节点B只保留节点A和不在 nodes_found 中的节点作为节点C"
因为所有节点都具有自反边缘,所以节点A将始终位于节点B的集合中,并始终保持它,我确保查询的那一部分始终与至少一个节点匹配。
这意味着此查询存在以下问题:
此查询的好处是我的表现更好
更好的解决方案? 有谁知道更好/更简单的解决方案?我错过了Cypher的一些明显特征吗?我无法通过文档找到任何内容。
由于
答案 0 :(得分:2)
[增订]
这是一个与您类似的更简单的方法,但它使用COALESCE函数来避免必须人为地将“入口节点”添加到每个“rim节点”集合中。 (通过“rim node”,我的意思是在最近的一场比赛中发现的一个以前未被发现的节点。)
该查询假定您已在:Label(id)
上创建了一个索引,以加快第一个MATCH
。顶部部分仅用于获取“入口节点”并初始化“res”(或结果)和“rim”集合。后续部分只是彼此的精确副本,可以重复以匹配所需的搜索深度。
深度为2,如此处所示,仅消耗了40分贝的命中率。
注1:给定测试数据,只需要深度为1。在这种情况下,只消耗了16个DB命中。
注意2:每个部分中的第3个WITH
子句用于强制NULL
成为空的rim
集合。这是因为UNWIND
将在要求展开空集合时中止查询。出于某种原因,将[NULL]
而不是[]
传递给COALLESCE()
并不能正常工作。
MATCH (a:Label { id:1 })
USING INDEX a:Label(id)
WITH COLLECT(a) AS res
WITH res, res AS rim
UNWIND rim AS a
OPTIONAL MATCH (a)-[:REL]-(b:Label)
WHERE NOT b IN res
WITH res, COALESCE(COLLECT(DISTINCT b),[]) AS rim
WITH rim, res + rim AS res
WITH res, CASE rim WHEN [] THEN [NULL] ELSE rim END AS rim
UNWIND rim AS a
OPTIONAL MATCH (a)-[:REL]-(b:Label)
WHERE NOT b IN res
WITH res, COALESCE(COLLECT(DISTINCT b),[]) AS rim
WITH rim, res + rim AS res
WITH res, CASE rim WHEN [] THEN [NULL] ELSE rim END AS rim
RETURN res;
答案 1 :(得分:1)
Cybersam的答案非常棒,表现相当不错。但是,对于APOC程序,有一种替代方案似乎在大型图表上执行得更快(针对完全互连的电影图表进行测试)。
APOC's path expander允许使用bfs方法,并且在使用NODE_GLOBAL唯一性时,节点只访问过一次。这也允许更简洁的查询。
MATCH (a:Label { id:1 })
USING INDEX a:Label(id)
CALL apoc.path.expandConfig(a,{relationshipFilter:'REL', bfs:true, uniqueness:"NODE_GLOBAL"})
YIELD path
WITH a, LAST(NODES(path)) as b
RETURN b