英文:
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']。
例如,这个图示:
目前我的想法是使用上面的查询,并基于客户端端的结果执行重复调用数据库的操作(捕获循环环路等),但这似乎是不可取的。
示例数据:
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)
英文:
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:'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
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:
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:'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)
答案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:
英文:
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:'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:
答案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_1
和d_1_1
版本共享的依赖关系。请注意,集合s2
和s1
是链的一部分。还请注意,版本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) // 冗余依赖
此更新后的查询返回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: '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)
Here is how to get the versions that version a_1_1
depends on:
MATCH (v:Version)-[:DEPENDS_ON*]->(x:Version)
WHERE v.id = 'a_1_1'
RETURN x.id
And here is the result:
╒═══════╕
│"x.id" │
╞═══════╡
│"b_1_3"│
├───────┤
│"c_1_1"│
├───────┤
│"b_1_2"│
└───────┘
[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:'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), // chaining 2 sets
(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) // redundant dependency
This updated query returns the distinct version dependencies for d_1_1
:
MATCH (v:Version)-[:DEPENDS_ON*]->(x:Version)
WHERE v.id = 'd_1_1'
RETURN DISTINCT x.id
The result is:
╒═══════╕
│"x.id" │
╞═══════╡
│"c_1_1"│
├───────┤
│"b_1_1"│
├───────┤
│"b_1_3"│
├───────┤
│"b_1_2"│
└───────┘
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论