密码查询以查找多个节点之间的所有关系
Cypher query to find all relationship among multiple nodes
我有一个复杂的图表,如下图所示:
这里每个关系都有一个类型值。我需要编写一个密码查询来查找给定节点集(两个或更多)之间的所有关系(及其类型值)。可以按任何顺序输入节点,例如 x64->Linux->Oracle 或 Oracle->Linux->10.2。
编辑
我期待这样的输出。具有链接它们的关系名称的所有节点组合。
- 对于输入:x64->Linux->Oracle
- 对于输入:Linux->64->Oracle->12c
数据
可以从https://www.dropbox.com/s/w28omkdrgmhv7ud/Neo4j%20Queries.txt?dl=0
访问数据
编辑
输入 x64->Linux->Oracle
的新输出格式
备注
在我呈现解决方案和结果之前,我想建议对您的模型进行修改。
- 如果尚未完成,请以标签的形式引入不同的节点类型(
Architecture
、Software
、SoftwareVersion
等),这将使您的数据检索更加容易。
- 根据源数据库的数量
domain_database_n
和由此产生的并行 SUPPORTS
关系,multiple labels for a node 的使用可能更清晰、性能更高。在这种情况下,更多的关系是多余的。
- Neo4j 不关注关系属性的搜索和过滤。将这些属性建模为节点或节点的 属性 性能显着提高,尤其是对于大型图。
- 考虑 Neo4j naming conventions。
你的图表/初始情况
为了方便可能的进一步答案和解决方案,我记下了我的图形创建语句:
CREATE
(pc:UntypedNode {name: 'PC'})-[:SUPPORTS {type: 'domain_database_1'}]->(tenDotTwo:UntypedNode {name:'10.2'}),
(pc)-[:SUPPORTS {type: 'domain_database_2'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_3'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_4'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_5'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_6'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_7'}]->(tenDotTwo),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_1'}]->(linux:UntypedNode {name:'Linux'}),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_2'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_3'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_4'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_5'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_6'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_7'}]->(linux),
(linux)-[:SUPPORTS {type: 'domain_database_1'}]->(sevenDotZero:UntypedNode {name:'7.0'}),
(linux)-[:SUPPORTS {type: 'domain_database_2'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_3'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_4'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_5'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_6'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_7'}]->(sevenDotZero),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_1'}]->(x64:UntypedNode {name:'x64'}),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_2'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_3'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_4'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_5'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_6'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_7'}]->(x64),
(x64)-[:SUPPORTS {type: 'domain_database_1'}]->(sixtyFour:UntypedNode {name:'64'}),
(x64)-[:SUPPORTS {type: 'domain_database_2'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_3'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_4'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_5'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_6'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_7'}]->(sixtyFour),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_1'}]->(sqlServer:UntypedNode {name:'SQL Server'}),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_2'}]->(sqlServer),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_3'}]->(sqlServer),
(sqlServer)-[:SUPPORTS {type: 'domain_database_1'}]->(year2014:UntypedNode {name:'2014'}),
(sqlServer)-[:SUPPORTS {type: 'domain_database_2'}]->(year2016:UntypedNode {name:'2016'}),
(sqlServer)-[:SUPPORTS {type: 'domain_database_3'}]->(year2017:UntypedNode {name:'2017'}),
(year2014)-[:SUPPORTS {type: 'domain_database_1'}]->(s:UntypedNode {name:'S'}),
(year2016)-[:SUPPORTS {type: 'domain_database_2'}]->(s),
(year2017)-[:SUPPORTS {type: 'domain_database_3'}]->(s),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_4'}]->(oracle:UntypedNode {name:'Oracle'}),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_5'}]->(oracle),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_6'}]->(oracle),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_7'}]->(oracle),
(oracle)-[:SUPPORTS {type: 'domain_database_4'}]->(release12c:UntypedNode {name:'12c'}),
(oracle)-[:SUPPORTS {type: 'domain_database_5'}]->(release12gr2:UntypedNode {name:'12gR2'}),
(oracle)-[:SUPPORTS {type: 'domain_database_6'}]->(release12cr:UntypedNode {name:'12cR'}),
(oracle)-[:SUPPORTS {type: 'domain_database_7'}]->(release12cr1:UntypedNode {name:'12cR1'}),
(release12c)-[:SUPPORTS {type: 'domain_database_4'}]->(s),
(release12gr2)-[:SUPPORTS {type: 'domain_database_5'}]->(s),
(release12cr)-[:SUPPORTS {type: 'domain_database_6'}]->(s),
(release12cr1)-[:SUPPORTS {type: 'domain_database_7'}]->(s);
解决方案
MATCH (n:UntypedNode)
WHERE n.name IN $names
WITH collect(n) AS nodes
UNWIND nodes AS n
UNWIND nodes AS m
WITH *
WHERE id(n) < id(m)
MATCH path = allShortestPaths((n)-[relation*..10]-(m))
WHERE ALL(x IN relation
WHERE x.type = $relationshipName)
WITH
collect({ path: path, pathLength: length(path) }) AS data,
max(length(path)) AS maxLength
WITH [row IN data WHERE row.pathLength = maxLength] AS rows
UNWIND rows AS row
RETURN row.path AS path;
带参数:
- "nodeNames":
['Oracle', 'Linux', '10.2']
- "relationshipName":
'domain_database_4'
解释:
- 第 1-2 行:给定起始节点的标识
- 第 3-5 行:为起始节点创建两组单独的行
- 第6-7行:每个关系只取一个方向并排除节点自引用
- 第 8 行:计算两组起始节点行之间的所有最短路径,直到长度为 10 个关系
- 第 9-10 行:过滤关于具有指定类型(参数 relationshipName)的关系的所有结果路径
- 第 11-15 行:过滤最长的“最短路径”,省略部分路径
- 第 16 行:渲染结果路径
结果
╒═══════════════════════════════════════════════════════════╕
│"path" │
╞═══════════════════════════════════════════════════════════╡
│[{"name":"10.2"},{"type":"domain_database_4"},{"name":"Linu│
│x"},{"name":"Linux"},{"type":"domain_database_4"},{"name":"│
│7.0"},{"name":"7.0"},{"type":"domain_database_4"},{"name":"│
│x64"},{"name":"x64"},{"type":"domain_database_4"},{"name":"│
│64"},{"name":"64"},{"type":"domain_database_4"},{"name":"Or│
│acle"}] │
└───────────────────────────────────────────────────────────┘
假设您只查找直接连接集合中每对节点的关系(而不是查找集合中节点对之间的所有多跳路径),APOC 程序有 apoc.algo.cover()正是这个用例:
...
// assume `nodes` is the collection of nodes
CALL apoc.algo.cover(nodes) YIELD rel
RETURN rel
编辑
正如我在评论中提到的,您对要求的更改极大地改变了问题的性质。
您似乎想要完整的路径结果(定向),包括不在您的输入中的节点,并且您想要确保相同的 type
属性存在于路径中的所有关系。
这要求我们找到这些节点的顺序,以便我们可以识别所有节点之间的路径。虽然我们可以找到输入节点的所有可能排列(对于路径的遍历顺序),但我认为我们可以只为开始和结束节点找到 2 的排列(通过展开集合两次并删除其中的行)开始和结束节点相同)。我们首先要找到所有输入和输出关系类型,以便我们可以使用一些集合操作(起始节点的输出类型与结束节点的输入类型相交,与另一个节点的所有(相交)输入和输出类型相交节点)来查找可能存在于连接所有节点的关系中的潜在类型。
从过滤后的剩余行中,我们可以匹配到可以连接所有这些节点的可变长度路径,仅使用提供的类型,以便每条路径仅遍历具有单一类型的遍历关系。之后我们过滤以确保所有输入节点都在路径中。
我们假设节点的类型为:具有 属性 'name'.
的节点
MATCH (entity:Entity)
WHERE entity.key in ['Product','Version','BinaryType'] AND entity.value in ['pc','10.2','64']
WITH collect(entity) as nodes
UNWIND nodes as node
WITH nodes, node, [()-[r]->(node) | r.type] as inputTypes, [(node)-[r]->() | r.type] as outputTypes
WITH nodes, node, apoc.coll.toSet(inputTypes) as inputTypes, apoc.coll.toSet(outputTypes) as outputTypes
WITH nodes, collect({node:node, inputTypes:inputTypes, outputTypes:outputTypes}) as nodeData
UNWIND nodeData as start
UNWIND nodeData as end
WITH nodes, start, end, nodeData
WHERE start <> end
WITH nodes, start, end, apoc.coll.subtract(nodeData, [start, end]) as theRest
WITH nodes, start.node as start, end.node as end, apoc.coll.intersection(start.outputTypes, end.inputTypes) as possibleTypes, [data in theRest | apoc.coll.intersection(data.inputTypes, data.outputTypes)] as otherTypes
WITH nodes, start, end, reduce(possibleTypes = possibleTypes, types in otherTypes | apoc.coll.intersection(possibleTypes, types)) as possibleTypes
WHERE size(possibleTypes) > 0
UNWIND possibleTypes as type
MATCH path = (start)-[*]->(end)
WHERE all(rel in relationships(path) WHERE rel.type = type)
AND length(path) >= size(nodes) - 1
AND all(node in nodes WHERE node in nodes(path))
RETURN nodes(path) as pathNodes, type
要对类型和级别都执行此操作,我们需要在查询中更早地收集它们,因此我们不是只处理类型,而是处理类型和级别的映射。这确实使查询有点复杂,但有必要确保提供的路径对于路径中的所有关系具有相同的类型和级别。
MATCH (entity:Entity)
WHERE entity.key in ['Product','Version','BinaryType'] AND entity.value in ['pc','10.2','64']
WITH collect(entity) as nodes
UNWIND nodes as node
WITH nodes, node, [()-[r]->(node) | {type:r.type, level:r.level}] as inputs, [(node)-[r]->() | {type:r.type, level:r.level}] as outputs
WITH nodes, collect({node:node, inputs:apoc.coll.toSet(inputs), outputs:apoc.coll.toSet(outputs)}) as nodeData
UNWIND nodeData as start
UNWIND nodeData as end
WITH nodes, start, end, nodeData
WHERE start <> end
WITH nodes, start, end, apoc.coll.subtract(nodeData, [start, end]) as theRest
WITH nodes, start.node as start, end.node as end, apoc.coll.intersection(start.outputs, end.inputs) as possibles, [data in theRest | apoc.coll.intersection(data.inputs, data.outputs)] as others
WITH nodes, start, end, reduce(possibles = possibles, data in others | apoc.coll.intersection(possibles, data)) as possibles
WHERE size(possibles) > 0
UNWIND possibles as typeAndLevel
MATCH path = (start)-[*]->(end)
WHERE all(rel in relationships(path) WHERE rel.type = typeAndLevel.type AND rel.level = typeAndLevel.level)
AND length(path) >= size(nodes) - 1
AND all(node in nodes WHERE node in nodes(path))
RETURN nodes(path) as pathNodes, typeAndLevel.type as type, typeAndLevel.level as level
我有一个复杂的图表,如下图所示:
这里每个关系都有一个类型值。我需要编写一个密码查询来查找给定节点集(两个或更多)之间的所有关系(及其类型值)。可以按任何顺序输入节点,例如 x64->Linux->Oracle 或 Oracle->Linux->10.2。
编辑
我期待这样的输出。具有链接它们的关系名称的所有节点组合。
- 对于输入:x64->Linux->Oracle
- 对于输入:Linux->64->Oracle->12c
数据
可以从https://www.dropbox.com/s/w28omkdrgmhv7ud/Neo4j%20Queries.txt?dl=0
访问数据编辑 输入 x64->Linux->Oracle
的新输出格式备注
在我呈现解决方案和结果之前,我想建议对您的模型进行修改。
- 如果尚未完成,请以标签的形式引入不同的节点类型(
Architecture
、Software
、SoftwareVersion
等),这将使您的数据检索更加容易。 - 根据源数据库的数量
domain_database_n
和由此产生的并行SUPPORTS
关系,multiple labels for a node 的使用可能更清晰、性能更高。在这种情况下,更多的关系是多余的。 - Neo4j 不关注关系属性的搜索和过滤。将这些属性建模为节点或节点的 属性 性能显着提高,尤其是对于大型图。
- 考虑 Neo4j naming conventions。
你的图表/初始情况
为了方便可能的进一步答案和解决方案,我记下了我的图形创建语句:
CREATE
(pc:UntypedNode {name: 'PC'})-[:SUPPORTS {type: 'domain_database_1'}]->(tenDotTwo:UntypedNode {name:'10.2'}),
(pc)-[:SUPPORTS {type: 'domain_database_2'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_3'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_4'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_5'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_6'}]->(tenDotTwo),
(pc)-[:SUPPORTS {type: 'domain_database_7'}]->(tenDotTwo),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_1'}]->(linux:UntypedNode {name:'Linux'}),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_2'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_3'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_4'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_5'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_6'}]->(linux),
(tenDotTwo)-[:SUPPORTS {type: 'domain_database_7'}]->(linux),
(linux)-[:SUPPORTS {type: 'domain_database_1'}]->(sevenDotZero:UntypedNode {name:'7.0'}),
(linux)-[:SUPPORTS {type: 'domain_database_2'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_3'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_4'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_5'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_6'}]->(sevenDotZero),
(linux)-[:SUPPORTS {type: 'domain_database_7'}]->(sevenDotZero),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_1'}]->(x64:UntypedNode {name:'x64'}),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_2'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_3'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_4'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_5'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_6'}]->(x64),
(sevenDotZero)-[:SUPPORTS {type: 'domain_database_7'}]->(x64),
(x64)-[:SUPPORTS {type: 'domain_database_1'}]->(sixtyFour:UntypedNode {name:'64'}),
(x64)-[:SUPPORTS {type: 'domain_database_2'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_3'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_4'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_5'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_6'}]->(sixtyFour),
(x64)-[:SUPPORTS {type: 'domain_database_7'}]->(sixtyFour),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_1'}]->(sqlServer:UntypedNode {name:'SQL Server'}),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_2'}]->(sqlServer),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_3'}]->(sqlServer),
(sqlServer)-[:SUPPORTS {type: 'domain_database_1'}]->(year2014:UntypedNode {name:'2014'}),
(sqlServer)-[:SUPPORTS {type: 'domain_database_2'}]->(year2016:UntypedNode {name:'2016'}),
(sqlServer)-[:SUPPORTS {type: 'domain_database_3'}]->(year2017:UntypedNode {name:'2017'}),
(year2014)-[:SUPPORTS {type: 'domain_database_1'}]->(s:UntypedNode {name:'S'}),
(year2016)-[:SUPPORTS {type: 'domain_database_2'}]->(s),
(year2017)-[:SUPPORTS {type: 'domain_database_3'}]->(s),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_4'}]->(oracle:UntypedNode {name:'Oracle'}),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_5'}]->(oracle),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_6'}]->(oracle),
(sixtyFour)-[:SUPPORTS {type: 'domain_database_7'}]->(oracle),
(oracle)-[:SUPPORTS {type: 'domain_database_4'}]->(release12c:UntypedNode {name:'12c'}),
(oracle)-[:SUPPORTS {type: 'domain_database_5'}]->(release12gr2:UntypedNode {name:'12gR2'}),
(oracle)-[:SUPPORTS {type: 'domain_database_6'}]->(release12cr:UntypedNode {name:'12cR'}),
(oracle)-[:SUPPORTS {type: 'domain_database_7'}]->(release12cr1:UntypedNode {name:'12cR1'}),
(release12c)-[:SUPPORTS {type: 'domain_database_4'}]->(s),
(release12gr2)-[:SUPPORTS {type: 'domain_database_5'}]->(s),
(release12cr)-[:SUPPORTS {type: 'domain_database_6'}]->(s),
(release12cr1)-[:SUPPORTS {type: 'domain_database_7'}]->(s);
解决方案
MATCH (n:UntypedNode)
WHERE n.name IN $names
WITH collect(n) AS nodes
UNWIND nodes AS n
UNWIND nodes AS m
WITH *
WHERE id(n) < id(m)
MATCH path = allShortestPaths((n)-[relation*..10]-(m))
WHERE ALL(x IN relation
WHERE x.type = $relationshipName)
WITH
collect({ path: path, pathLength: length(path) }) AS data,
max(length(path)) AS maxLength
WITH [row IN data WHERE row.pathLength = maxLength] AS rows
UNWIND rows AS row
RETURN row.path AS path;
带参数:
- "nodeNames":
['Oracle', 'Linux', '10.2']
- "relationshipName":
'domain_database_4'
解释:
- 第 1-2 行:给定起始节点的标识
- 第 3-5 行:为起始节点创建两组单独的行
- 第6-7行:每个关系只取一个方向并排除节点自引用
- 第 8 行:计算两组起始节点行之间的所有最短路径,直到长度为 10 个关系
- 第 9-10 行:过滤关于具有指定类型(参数 relationshipName)的关系的所有结果路径
- 第 11-15 行:过滤最长的“最短路径”,省略部分路径
- 第 16 行:渲染结果路径
结果
╒═══════════════════════════════════════════════════════════╕
│"path" │
╞═══════════════════════════════════════════════════════════╡
│[{"name":"10.2"},{"type":"domain_database_4"},{"name":"Linu│
│x"},{"name":"Linux"},{"type":"domain_database_4"},{"name":"│
│7.0"},{"name":"7.0"},{"type":"domain_database_4"},{"name":"│
│x64"},{"name":"x64"},{"type":"domain_database_4"},{"name":"│
│64"},{"name":"64"},{"type":"domain_database_4"},{"name":"Or│
│acle"}] │
└───────────────────────────────────────────────────────────┘
假设您只查找直接连接集合中每对节点的关系(而不是查找集合中节点对之间的所有多跳路径),APOC 程序有 apoc.algo.cover()正是这个用例:
...
// assume `nodes` is the collection of nodes
CALL apoc.algo.cover(nodes) YIELD rel
RETURN rel
编辑
正如我在评论中提到的,您对要求的更改极大地改变了问题的性质。
您似乎想要完整的路径结果(定向),包括不在您的输入中的节点,并且您想要确保相同的 type
属性存在于路径中的所有关系。
这要求我们找到这些节点的顺序,以便我们可以识别所有节点之间的路径。虽然我们可以找到输入节点的所有可能排列(对于路径的遍历顺序),但我认为我们可以只为开始和结束节点找到 2 的排列(通过展开集合两次并删除其中的行)开始和结束节点相同)。我们首先要找到所有输入和输出关系类型,以便我们可以使用一些集合操作(起始节点的输出类型与结束节点的输入类型相交,与另一个节点的所有(相交)输入和输出类型相交节点)来查找可能存在于连接所有节点的关系中的潜在类型。
从过滤后的剩余行中,我们可以匹配到可以连接所有这些节点的可变长度路径,仅使用提供的类型,以便每条路径仅遍历具有单一类型的遍历关系。之后我们过滤以确保所有输入节点都在路径中。
我们假设节点的类型为:具有 属性 'name'.
的节点MATCH (entity:Entity)
WHERE entity.key in ['Product','Version','BinaryType'] AND entity.value in ['pc','10.2','64']
WITH collect(entity) as nodes
UNWIND nodes as node
WITH nodes, node, [()-[r]->(node) | r.type] as inputTypes, [(node)-[r]->() | r.type] as outputTypes
WITH nodes, node, apoc.coll.toSet(inputTypes) as inputTypes, apoc.coll.toSet(outputTypes) as outputTypes
WITH nodes, collect({node:node, inputTypes:inputTypes, outputTypes:outputTypes}) as nodeData
UNWIND nodeData as start
UNWIND nodeData as end
WITH nodes, start, end, nodeData
WHERE start <> end
WITH nodes, start, end, apoc.coll.subtract(nodeData, [start, end]) as theRest
WITH nodes, start.node as start, end.node as end, apoc.coll.intersection(start.outputTypes, end.inputTypes) as possibleTypes, [data in theRest | apoc.coll.intersection(data.inputTypes, data.outputTypes)] as otherTypes
WITH nodes, start, end, reduce(possibleTypes = possibleTypes, types in otherTypes | apoc.coll.intersection(possibleTypes, types)) as possibleTypes
WHERE size(possibleTypes) > 0
UNWIND possibleTypes as type
MATCH path = (start)-[*]->(end)
WHERE all(rel in relationships(path) WHERE rel.type = type)
AND length(path) >= size(nodes) - 1
AND all(node in nodes WHERE node in nodes(path))
RETURN nodes(path) as pathNodes, type
要对类型和级别都执行此操作,我们需要在查询中更早地收集它们,因此我们不是只处理类型,而是处理类型和级别的映射。这确实使查询有点复杂,但有必要确保提供的路径对于路径中的所有关系具有相同的类型和级别。
MATCH (entity:Entity)
WHERE entity.key in ['Product','Version','BinaryType'] AND entity.value in ['pc','10.2','64']
WITH collect(entity) as nodes
UNWIND nodes as node
WITH nodes, node, [()-[r]->(node) | {type:r.type, level:r.level}] as inputs, [(node)-[r]->() | {type:r.type, level:r.level}] as outputs
WITH nodes, collect({node:node, inputs:apoc.coll.toSet(inputs), outputs:apoc.coll.toSet(outputs)}) as nodeData
UNWIND nodeData as start
UNWIND nodeData as end
WITH nodes, start, end, nodeData
WHERE start <> end
WITH nodes, start, end, apoc.coll.subtract(nodeData, [start, end]) as theRest
WITH nodes, start.node as start, end.node as end, apoc.coll.intersection(start.outputs, end.inputs) as possibles, [data in theRest | apoc.coll.intersection(data.inputs, data.outputs)] as others
WITH nodes, start, end, reduce(possibles = possibles, data in others | apoc.coll.intersection(possibles, data)) as possibles
WHERE size(possibles) > 0
UNWIND possibles as typeAndLevel
MATCH path = (start)-[*]->(end)
WHERE all(rel in relationships(path) WHERE rel.type = typeAndLevel.type AND rel.level = typeAndLevel.level)
AND length(path) >= size(nodes) - 1
AND all(node in nodes WHERE node in nodes(path))
RETURN nodes(path) as pathNodes, typeAndLevel.type as type, typeAndLevel.level as level