如何在Cypher中执行具有路径关系内的条件递归查询?

huangapple go评论51阅读模式
英文:

How do you perform a recursive query in cypher where there is a conditional within the path relationship?

问题

我试图设置一个新的图数据库,以包含产品及其各个版本组件之间的关系记录。对于每个产品,它可以有许多组件,而每个组件由多个版本组成。每个版本可以依赖于其他组件的零个或多个版本。我希望能够查询这个数据库,以选择组件的任何版本,并确定它依赖的其他版本组件,或者谁依赖于它。

我尝试过的数据结构在我的示例中还没有定义,所以如果完全不同的结构更适合,我愿意进行更改。最初,我考虑直接在成员之间建立DEPENDS_ON关系。然而,由于新成员将随时间而增加,如果添加了一个新成员并且它在现有记录的依赖范围(version_min和version_max)内,那么我将需要返回并识别所有受影响的记录并更新它们,这似乎不适合随时间推移的情况。这就引出了成员依赖于组件的想法,关系参数中定义了版本限制。

我制作了一个非常简单的示例,包括3个产品(最后提供了示例数据),每个产品都有一种类型的组件,每种组件只有一个版本,除了一个产品有多个版本。然后,我只添加了两个依赖关系到这个示例中,'a'依赖于'b'版本范围,而'b'版本中的一个依赖于'c'的一个版本。

我想能够执行一个查询,说“给我所有依赖于成员prod_a_comp_1_v_1的下游成员”。同样,我想以相反的方式执行这个操作,我想这可以通过颠倒一些关系参数来实现。

到目前为止,我已经实现了单跳(列出a依赖的b版本),如下所示:

MATCH
p=(a:member{name:'prod_a_comp_1_v_1'})-[d:DEPENDS_ON]->(c:component)<-[v:VERSION_OF]-(b:member) WHERE b.version >= d.version_min AND b.version <= d.version_max
RETURN p

但是我不知道如何让它在第一次匹配的结果上递归执行这个查询。我尝试了变量长度/深度,但由于变量深度中有关系中的条件参数(DEPENDS_ON),所以我无法让它工作。

从示例数据中,如果查询prod_a_comp_1_v_1的所有下游依赖项,它应该返回:['prod_b_comp_1_v_2','prod_b_comp_1_v_3','prod_c_comp_1_v_1']。
例如,这个图示:
如何在Cypher中执行具有路径关系内的条件递归查询?

目前我的想法是使用上面的查询,并基于客户端端的结果执行重复调用数据库的操作(捕获循环环路等),但这似乎是不可取的。

示例数据:

CREATE
(prod_a:product {name:'prod_a'}),
(prod_a_comp_1:component {name: 'prod_a_comp_1', type:'comp_1'}),
(prod_a_comp_1)-[:COMPONENT_OF {type:'comp_1'}]->(prod_a),
(prod_a_comp_1_v_1:member {name:'prod_a_comp_1_v_1', type:'comp_1', version:1}),
(prod_a_comp_1_v_1)-[:VERSION_OF {version:1}]->(prod_a_comp_1)

CREATE
(prod_b:product {name:'prod_b'}),
(prod_b_comp_1:component {name: 'prod_b_comp_1', type:'comp_1'}),
(prod_b_comp_1)-[:COMPONENT_OF {type:'comp_1'}]->(prod_b),
(prod_b_comp_1_v_1:member {name:'prod_b_comp_1_v_1', type:'comp_1', version:1}),
(prod_b_comp_1_v_2:member {name:'prod_b_comp_1_v_2', type:'comp_1', version:2}),
(prod_b_comp_1_v_3:member {name:'prod_b_comp_1_v_3', type:'comp_1', version:3}),
(prod_b_comp_1_v_1)-[:VERSION_OF {version:1}]->(prod_b_comp_1),
(prod_b_comp_1_v_2)-[:VERSION_OF {version:2}]->(prod_b_comp_1),
(prod_b_comp_1_v_3)-[:VERSION_OF {version:3}]->(prod_b_comp_1)

CREATE
(prod_c:product {name:'prod_c'}),
(prod_c_comp_1:component {name: 'prod_c_comp_1', type:'comp_1'}),
(prod_c_comp_1)-[:COMPONENT_OF {type:'comp_1'}]->(prod_c),
(prod_c_comp_1_v_1:member {name:'prod_c_comp_1_v_1', type:'comp_1', version:1}),
(prod_c_comp_1_v_1)-[:VERSION_OF {version:1}]->(prod_c_comp_1)

CREATE
(prod_a_comp_1_v_1)-[:DEPENDS_ON {version_min:2, version_max:3}]->(prod_b_comp_1),
(prod_b_comp_1_v_3)-[:DEPENDS_ON {version_min:1, version_max:100}]->(prod_c_comp_1)

显示完整示例数据集的图示:
如何在Cypher中执行具有路径关系内的条件递归查询?

英文:

I am attempting to setup a new graph database to contain records of products and their relationship on each other's versioned components. For each product it can have many components, and each component is made up of multiple versions. Each version can be dependent on none or many versions of any other components. I want to be able to query this database to pick any version of a component and determine what other versioned components it is depended on, or what depends on it.

The data structure I have attempted in my examples is not defined yet, so if a completely different structure is more suitable I'm open to changing it. I originally considered setting the DEPENDS_ON relationship directly between members. However, as new members will be added over time if a new member is added and falls within the version_min and version_max range of an existing records dependancy range, I would then need to go back and identify all affected records and update all of them, which doesn't feel like it would scale over time. This is what lead to the idea of having a member being dependent on a component, with the version limits defined in the relationship parameters.

I have put together a very simple example of 3 products (sample data at the end), with a single type of component and 1 version of each in all cases except one. I've then added only two dependencies into this example, 'a' depends on a range of 'b' versions, and one of the 'b' versions depends on a version of 'c'.

I would like to be able to perform a query to say "give me all downstream members which member prod_a_comp_1_v_1 depends on". Similarly I would like to do this in reverse too, which I imagine is achieved by just reversing some of the relationship parameters.

So far I've achieved this for a single hop (list b versions which a depends on), shown here:

MATCH
p=(a:member{name:&#39;prod_a_comp_1_v_1&#39;})-[d:DEPENDS_ON]-&gt;(c:component)&lt;-[v:VERSION_OF]-(b:member) WHERE b.version &gt;= d.version_min AND b.version &lt;= d.version_max
RETURN p

如何在Cypher中执行具有路径关系内的条件递归查询?

But I don't know how to get it to recursively perform this query on the results of this first match. I investigated variable length/depths, but because there is a conditional parameter in the relationship in the variable depth (DEPENDS_ON), I could not get this to work.

From the example data if querying all downstream dependencies of prod_a_comp_1_v_1 it should return: [prod_b_comp_1_v_2, prod_b_comp_1_v_3, prod_c_comp_1_v_1].
e.g. this figure:
如何在Cypher中执行具有路径关系内的条件递归查询?

Currently my thought is to use the above query and perform the repeated call on the database based on the results from the client end (capturing circular loops etc.), but that seems very undesirable.

Sample data:

CREATE
(prod_a:product {name:&#39;prod_a&#39;}),
(prod_a_comp_1:component {name: &#39;prod_a_comp_1&#39;, type:&#39;comp_1&#39;}),
(prod_a_comp_1)-[:COMPONENT_OF {type:&#39;comp_1&#39;}]-&gt;(prod_a),
(prod_a_comp_1_v_1:member {name:&#39;prod_a_comp_1_v_1&#39;, type:&#39;comp_1&#39;, version:1}),
(prod_a_comp_1_v_1)-[:VERSION_OF {version:1}]-&gt;(prod_a_comp_1)

CREATE
(prod_b:product {name:&#39;prod_b&#39;}),
(prod_b_comp_1:component {name: &#39;prod_b_comp_1&#39;, type:&#39;comp_1&#39;}),
(prod_b_comp_1)-[:COMPONENT_OF {type:&#39;comp_1&#39;}]-&gt;(prod_b),
(prod_b_comp_1_v_1:member {name:&#39;prod_b_comp_1_v_1&#39;, type:&#39;comp_1&#39;, version:1}),
(prod_b_comp_1_v_2:member {name:&#39;prod_b_comp_1_v_2&#39;, type:&#39;comp_1&#39;, version:2}),
(prod_b_comp_1_v_3:member {name:&#39;prod_b_comp_1_v_3&#39;, type:&#39;comp_1&#39;, version:3}),
(prod_b_comp_1_v_1)-[:VERSION_OF {version:1}]-&gt;(prod_b_comp_1),
(prod_b_comp_1_v_2)-[:VERSION_OF {version:2}]-&gt;(prod_b_comp_1),
(prod_b_comp_1_v_3)-[:VERSION_OF {version:3}]-&gt;(prod_b_comp_1)

CREATE
(prod_c:product {name:&#39;prod_c&#39;}),
(prod_c_comp_1:component {name: &#39;prod_c_comp_1&#39;, type:&#39;comp_1&#39;}),
(prod_c_comp_1)-[:COMPONENT_OF {type:&#39;comp_1&#39;}]-&gt;(prod_c),
(prod_c_comp_1_v_1:member {name:&#39;prod_c_comp_1_v_1&#39;, type:&#39;comp_1&#39;, version:1}),
(prod_c_comp_1_v_1)-[:VERSION_OF {version:1}]-&gt;(prod_c_comp_1)

CREATE
(prod_a_comp_1_v_1)-[:DEPENDS_ON {version_min:2, version_max:3}]-&gt;(prod_b_comp_1),
(prod_b_comp_1_v_3)-[:DEPENDS_ON {version_min:1, version_max:100}]-&gt;(prod_c_comp_1)

Figure showing full sample data set:
如何在Cypher中执行具有路径关系内的条件递归查询?

答案1

得分: 0

抱歉,我理解您的要求。以下是代码部分的翻译:

Apologies if I have missunderstood your question but I believe this may be possible with the APOC Expand Paths function: APOC Expand Paths

Example Cypher for your graph:

MATCH (a:member{name:'prod_a_comp_1_v_1'})
CALL apoc.path.expand(a, ">DEPENDS_ON|<VERSION_OF", null, 1, -1)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;

Example Results:

如何在Cypher中执行具有路径关系内的条件递归查询?

英文:

Apologies if I have missunderstood your question but I believe this may be possible with the APOC Expand Paths function: https://neo4j.com/docs/apoc/current/graph-querying/expand-paths/

Example Cypher for your graph:

MATCH (a:member{name:&#39;prod_a_comp_1_v_1&#39;})
CALL apoc.path.expand(a, &quot;&gt;DEPENDS_ON|&lt;VERSION_OF&quot;, null, 1, -1)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;

Example Results:

如何在Cypher中执行具有路径关系内的条件递归查询?

答案2

得分: 0

首先,让我先将您的节点标签调整为驼峰式,以遵循建议的neo4j命名约定。我还会将member重命名为更直观的Version标签。

您可以更改数据模型,使DEPENDS_ON关系类型直接连接Version节点。虽然这可能会导致更多的关系,但也允许您跳过版本号范围内的某些版本号(例如,如果范围内的某些版本号不可靠/昂贵/颜色不对等)。但这样做的主要原因是它将使您的用例非常简单。

以下是如何创建一个更新后的示例数据版本,带有我的建议调整(并且信息冗余较少):

CREATE
  (a:Product {id: 'a'}),
  (a_1:Component {id: 'a_1', type: 'comp_1'})-[:COMPONENT_OF]->(a),
  (a_1_1:Version {id: 'a_1_1', version: 1})-[:VERSION_OF]->(a_1)

CREATE
  (b:Product {id: 'b'}),
  (b_1:Component {id: 'b_1', type: 'comp_1'})-[:COMPONENT_OF]->(b),
  (b_1_1:Version {id: 'b_1_1', version: 1})-[:VERSION_OF]->(b_1),
  (b_1_2:Version {id: 'b_1_2', version: 2})-[:VERSION_OF]->(b_1),
  (b_1_3:Version {id: 'b_1_3', version: 3})-[:VERSION_OF]->(b_1)

CREATE
  (c:Product {id:'c'}),
  (c_1:Component {id: 'c_1', type: 'comp_1'})-[:COMPONENT_OF]->(c),
  (c_1_1:Version {id: 'c_1_1', version: 1})-[:VERSION_OF]->(c_1)

CREATE
  (a_1_1)-[:DEPENDS_ON]->(b_1_2),
  (a_1_1)-[:DEPENDS_ON]->(b_1_3),
  (b_1_3)-[:DEPENDS_ON]->(c_1_1)

以下是如何获取版本a_1_1依赖的版本:

MATCH (v:Version)-[:DEPENDS_ON*]->(x:Version)
WHERE v.id = 'a_1_1'
RETURN x.id

查询结果如下:

╒═══════╕
│"x.id" │
╞═══════╡
│"b_1_3"│
├───────┤
│"c_1_1"│
├───────┤
│"b_1_2"│
└───────┘

[更新]

为了减少DEPENDS_ON关系的总数,我们可以使用可选的VersionSet节点,表示共享的版本集合。VersionSet可以用作DEPENDS_ON关系的端点,就像Version节点一样。它们甚至可以链接在一起。

我们可以通过用以下内容替换上面的Cypher示例代码的最后一个CREATE子句来修改它。在这个新示例中,VersionSet s1表示a_1_1d_1_1版本共享的依赖关系。请注意,集合s2s1是链的一部分。还请注意,版本d_1_1依赖于Version以及VersionSet。有很大的灵活性,甚至可以处理具有重叠版本的版本集链(d_1_1以2种不同的方式依赖于c_1_1)。

...

CREATE
  (d:Product {id:'d'}),
  (d_1:Component {id: 'd_1', type: 'comp_2'})-[:COMPONENT_OF]->(d),
  (d_1_1:Version {id: 'd_1_1', version: 1})-[:VERSION_OF]->(d_1)

CREATE
  (s1:VersionSet),
  (s1)-[:DEPENDS_ON]->(b_1_2),
  (s1)-[:DEPENDS_ON]->(b_1_3),
  (s2:VersionSet),
  (s2)-[:DEPENDS_ON]->(s1),      // 链接2个集合
  (s2)-[:DEPENDS_ON]->(b_1_1)

CREATE
  (a_1_1)-[:DEPENDS_ON]->(s1),
  (b_1_3)-[:DEPENDS_ON]->(c_1_1),
  (d_1_1)-[:DEPENDS_ON]->(s2),
  (d_1_1)-[:DEPENDS_ON]->(c_1_1) // 冗余依赖

结果图:如何在Cypher中执行具有路径关系内的条件递归查询?

此更新后的查询返回d_1_1不同版本依赖项:

MATCH (v:Version)-[:DEPENDS_ON*]->(x:Version)
WHERE v.id = 'd_1_1'
RETURN DISTINCT x.id

结果如下:

╒═══════╕
│"x.id" │
╞═══════╡
│"c_1_1"│
├───────┤
│"b_1_1"│
├───────┤
│"b_1_3"│
├───────┤
│"b_1_2"│
└───────┘
英文:

First, let me first adjust your node labels to use camel casing to follow the suggested neo4j naming convention. I will also rename member to the much more intuitive Version label.

You can change your data model so that the DEPENDS_ON relationship type directly connects Version nodes. Although this can result in many more relationships, it will also allow you to skip version numbers within a min/max range (e.g., if some version numbers in the range are unreliable/expensive/the wrong color/etc.). But the main reason to do that is it will make your use case extremely simple.

Here is how to create an updated version of your sample data, with my suggested adjustments (and with less redundant info):

CREATE
  (a:Product {id: &#39;a&#39;}),
  (a_1:Component {id: &#39;a_1&#39;, type: &#39;comp_1&#39;})-[:COMPONENT_OF]-&gt;(a),
  (a_1_1:Version {id: &#39;a_1_1&#39;, version: 1})-[:VERSION_OF]-&gt;(a_1)

CREATE
  (b:Product {id: &#39;b&#39;}),
  (b_1:Component {id: &#39;b_1&#39;, type: &#39;comp_1&#39;})-[:COMPONENT_OF]-&gt;(b),
  (b_1_1:Version {id: &#39;b_1_1&#39;, version:1})-[:VERSION_OF]-&gt;(b_1),
  (b_1_2:Version {id: &#39;b_1_2&#39;, version:2})-[:VERSION_OF]-&gt;(b_1),
  (b_1_3:Version {id: &#39;b_1_3&#39;, version:3})-[:VERSION_OF]-&gt;(b_1)

CREATE
  (c:Product {id:&#39;c&#39;}),
  (c_1:Component {id: &#39;c_1&#39;, type: &#39;comp_1&#39;})-[:COMPONENT_OF]-&gt;(c),
  (c_1_1:Version {id: &#39;c_1_1&#39;, version: 1})-[:VERSION_OF]-&gt;(c_1)

CREATE
  (a_1_1)-[:DEPENDS_ON]-&gt;(b_1_2),
  (a_1_1)-[:DEPENDS_ON]-&gt;(b_1_3),
  (b_1_3)-[:DEPENDS_ON]-&gt;(c_1_1)

Here is how to get the versions that version a_1_1 depends on:

MATCH (v:Version)-[:DEPENDS_ON*]-&gt;(x:Version)
WHERE v.id = &#39;a_1_1&#39;
RETURN x.id

And here is the result:

╒═══════╕
│&quot;x.id&quot; │
╞═══════╡
│&quot;b_1_3&quot;│
├───────┤
│&quot;c_1_1&quot;│
├───────┤
│&quot;b_1_2&quot;│
└───────┘

[UPDATE]

To reduce the total number of DEPENDS_ON relationships, we can use optional VersionSet nodes that represent shared collections of versions. A VersionSet can be used as either endpoint of a DEPENDS_ON relationship, just like Version nodes. And they can even be chained together.

We can modify the above Cypher example code by replacing its last CREATE clause with the following. In this new example, VersionSet s1 represents dependencies shared by versions a_1_1 and d_1_1. Notice how sets s2 and s1 are part of a chain. Also notice how version d_1_1 depends on a Version as well as VersionSet. There is a lot of flexibility, and we can even handle a chain of version sets with overlapping versions (d_1_1 depends on c_1_1 in 2 different ways).

...

CREATE
  (d:Product {id:&#39;d&#39;}),
  (d_1:Component {id: &#39;d_1&#39;, type: &#39;comp_2&#39;})-[:COMPONENT_OF]-&gt;(d),
  (d_1_1:Version {id: &#39;d_1_1&#39;, version: 1})-[:VERSION_OF]-&gt;(d_1)

CREATE
  (s1:VersionSet),
  (s1)-[:DEPENDS_ON]-&gt;(b_1_2),
  (s1)-[:DEPENDS_ON]-&gt;(b_1_3),
  (s2:VersionSet),
  (s2)-[:DEPENDS_ON]-&gt;(s1),      // chaining 2 sets
  (s2)-[:DEPENDS_ON]-&gt;(b_1_1)

CREATE
  (a_1_1)-[:DEPENDS_ON]-&gt;(s1),
  (b_1_3)-[:DEPENDS_ON]-&gt;(c_1_1),
  (d_1_1)-[:DEPENDS_ON]-&gt;(s2),
  (d_1_1)-[:DEPENDS_ON]-&gt;(c_1_1) // redundant dependency

The resulting graph: 如何在Cypher中执行具有路径关系内的条件递归查询?

This updated query returns the distinct version dependencies for d_1_1:

MATCH (v:Version)-[:DEPENDS_ON*]-&gt;(x:Version)
WHERE v.id = &#39;d_1_1&#39;
RETURN DISTINCT x.id

The result is:

╒═══════╕
│&quot;x.id&quot; │
╞═══════╡
│&quot;c_1_1&quot;│
├───────┤
│&quot;b_1_1&quot;│
├───────┤
│&quot;b_1_3&quot;│
├───────┤
│&quot;b_1_2&quot;│
└───────┘

huangapple
  • 本文由 发表于 2023年2月13日 23:05:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75437652.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定