用于版本化节点的Neo4J路径子查询

时间:2017-02-23 19:17:23

标签: neo4j cypher

example graph(抱歉,您需要点击图片)

我有一个包含两种节点的图表:

  • identity-nodes 作为不含属性的结构元素(蓝色)
  • 版本化 value-nodes ,其中包含属性/数据(橙色)

有两种关系:

  • 版本:将值节点连接到身份节点,并包含一个窗口,关系在该窗口内有效(图片仅显示开始日期)
  • parent-of :以分层方式连接身份节点

情景:

  • 在任何给定时间,每个身份节点最多有一个有效的值节点。
  • 我想保留历史值并随时回顾,因此将当前有效的值复制到相应的身份节点将不起作用。
  • 层次结构可以是任意深度,例如1到10。
  • 我不想使用value-node的属性使用完全限定的路径 - 从root到leaf的字符串进行搜索。
  • 我想找到ID节点的路径以及路径末尾的正确值。 (想一想文件系统目录路径。)

在这个例子中,我正在搜索路径“A / CD / E”,其中A CD E是value-nodes的name属性的值。基本上我想找到正确的'E'节点,这里是与98相关的节点。时间从下面的例子中省略,以保持简短。

我该怎么做?仅使用Neo4J即可吗? (很容易将查询包装在另一个脚本中,该脚本生成路径匹配,包括每个路径条目的命名id节点。)

我可以查询路径的开头和结尾来限制搜索空间,但是可变数量的节点呢?以某种方式思考ID节点必须与每个查询之前与关系版本上的时间窗口匹配的值合并。

MATCH p=(start:value) -[:`version-of`]-> (:ID) <-[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE start.name = 'A' AND end.name = 'E'
RETURN p

我想问的是(使用可变深度键而不是简短示例);以下是无效的密码:

MATCH p=(start:value) -[:`version-of`]-> (:ID) <-[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE p = 'A/CD/E' // or something longer, "A/CD/E/F/X"
RETURN p, end

到目前为止我看过的是什么

我还可以在路径上构建名称列表,并将完整路径与查询字符串进行比较。但它无法获得开始和结束之间节点的名称值:

MATCH p=(start:value) -[:`version-of`]-> (:ID) <-[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE start.name = 'A' AND end.name = 'E'
RETURN EXTRACT(n in nodes(p)|n['name'])

与成对查询相关的子图,但缺少路径。我能以某种方式获得id,val对的路径吗?

MATCH p=(start:value) -[:`version-of`]-> (:ID) <-[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE start.name = 'A' AND end.name = 'E'
UNWIND nodes(p) as id
MATCH (id) <-[:`version-of`]- (val)
RETURN COLLECT([id,val])

这是图表

MATCH (n) DETACH DELETE n
CREATE (:value {name:'A'}) -[:`version-of` {from:'2017-01-01'}]-> (:ID {id:32})
CREATE (:value {name:'CC'}) -[:`version-of` {from:'2017-01-05'}]-> (:ID {id:21})
CREATE (:value {name:'E'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:98})
CREATE (:value {name:'E'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:48})
MATCH (n:ID {id:21}) CREATE (:value {name:'CD'}) -[:`version-of` {from:'2017-03-01'}]-> (n)
MATCH (a:ID {id:32}),(b:ID {id:21}) CREATE (a)<-[:`parent-of`]-(b)
MATCH (a:ID {id:21}),(b:ID {id:98}) CREATE (a)<-[:`parent-of`]-(b)
MATCH (a:ID {id:32}),(b:ID {id:48}) CREATE (a)<-[:`parent-of`]-(b)

2 个答案:

答案 0 :(得分:0)

你非常接近!

您的上一个查询(包含id / val对)缺少路径,因为您在没有任何上下文的情况下进行收集:

RETURN COLLECT([id,val])

这只是要求一个id / val对的集合。如果您希望为每个路径收集id / val对,则需要将路径变量p作为非聚合字段,这将作为聚合的分组键:

WITH p, COLLECT([id,val]) as pairs  // one collection per path
RETURN pairs

至于确保CD在路径中,我们需要找到CD的:ID节点,并添加一个谓词以确保该节点在路径上:

MATCH (:value{name:'CD'})-[:`version-of`]->(cdID:ID)
MATCH p=(start:value) -[:`version-of`]-> (:ID) <-[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE start.name = 'A' AND end.name = 'E' AND cdID in nodes(p)
UNWIND nodes(p)[1..-1] as id
MATCH (id) <-[:`version-of`]- (val)
WITH p, COLLECT([id,val]) as pairs
RETURN pairs

修改

现在澄清的要求包括指定以下名称:与以下任何一个相关联的值节点:ID应该沿着路径,可能是按顺序。

让我们看看这个是否会奏效。我将使用您现有的图形(删除重复的E-for-id-48行),并且仅针对此示例我们将省略:parent-of relationship的方向,因为对于现有方向,我们没有足够的节点在中间路径中有多个元素。让我们从E到E,按顺序要求以下值节点的ID:'E','A','CD','E'。使用我们的图表,我们希望得到以下的单一路径:与值名称关联的ID:48,32,21,98

这将是一个复杂的查询,因为在Cypher中处理索引并不是那么简单。这也是我们过滤所有可能路径的查询,因为我看不到在扩展期间过滤的方法:

WITH ['E', 'A', 'CD', 'E'] as names
UNWIND RANGE(0, SIZE(names)-1) as index
WITH index, names[index] as name
// each name with its expected index in the path

MATCH (:value{name:name})-[:`version-of`]->(id:ID)
WITH index, name, COLLECT(id) as ids
ORDER BY index ASC
WITH COLLECT({name:name, ids:ids}) as pathdef
// index-order collection of expected name and possible IDs for the name

MATCH p=(start:value) -[:`version-of`]-> (:ID) -[:`parent-of`*]- (:ID) <-[:`version-of`]- (end:value)
WHERE start.name = 'E' AND end.name = 'E' AND LENGTH(p)-1 = SIZE(pathdef)
// we have all paths from E to E of the desired length

// only interested in :ID nodes in match, leave off :value nodes at end
WITH pathdef, NODES(p)[1..-1] as nodes, RANGE(0, SIZE(pathdef)-1) as indices
// due to our match from start and end values, don't need to check start/end :ID nodes
WHERE ALL(index in indices[1..-1] WHERE nodes[index] in pathdef[index].ids)
// we've filtered to only path nodes associated with values in expected order
UNWIND indices as index
RETURN COLLECT([nodes[index], pathdef[index].name]) as pairs

答案 1 :(得分:0)

MATCH (n) DETACH DELETE n;
CREATE (:value {name:'A'}) -[:`version-of` {from:'2017-01-01'}]-> (:ID {id:32});
CREATE (:value {name:'CC'}) -[:`version-of` {from:'2017-01-05'}]-> (:ID {id:21});
CREATE (:value {name:'E'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:98});
CREATE (:value {name:'N'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:87});
CREATE (:value {name:'E'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:48});
CREATE (:value {name:'W'}) -[:`version-of` {from:'2017-02-01'}]-> (:ID {id:74});
CREATE (:value {name:'E'}) -[:`version-of` {from:'2017-01-01'}]-> (:ID {id:75});
MATCH (n:ID {id:21}) CREATE (:value {name:'CD'}) -[:`version-of` {from:'2017-03-01'}]-> (n);
MATCH (a:ID {id:32}),(b:ID {id:21}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:32}),(b:ID {id:23}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:32}),(b:ID {id:48}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:48}),(b:ID {id:87}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:87}),(b:ID {id:98}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:21}),(b:ID {id:74}) CREATE (a)<-[:`parent-of`]-(b);
MATCH (a:ID {id:74}),(b:ID {id:75}) CREATE (a)<-[:`parent-of`]-(b);

a more exiting example-graph to test on (上面的解决方案也在这里工作)