在Neo4j图中扩展节点的层级

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

Extending layers of nodes in neo4j graph

问题

我有两种节点类型:“Person”和“Company”。还有一个名为“Shareholder”的关系,可以从个人到公司,也可以从公司到公司(因为公司也可以持有其他公司的股份)。“Shareholder”关系有三个属性:“shareholding_start_date”、“shareholding_end_date”、“number_of_shares”。

我有一个查询,接受公司名称(称之为'ABC'),以及提供一个日期(例如'2021-07-23'),查询返回在日期'2021-07-23'时是c的股东的所有个人/公司节点(即shareholding_start_date < 2021-07-23 < shareholding_end_date)。它还根据'number_of_shares'计算百分比。

下面是工作查询:

MATCH (c:Company {name:"ABC"})<-[r:Shareholder]-(s)
WHERE r.shareholding_start_date <= Date("2021-07-23") 
   AND r.shareholding_end_date >= DATE("2021-07-23")
WITH c, 
collect(s) as shareholders, 
collect(r) as rels, 
sum(r.number_of_shares) AS totalShares
UNWIND range(0, size(rels) - 1) AS i
RETURN shareholders[i].name AS shareholder, 
100.0 * rels[i].number_of_shares/totalShares as percentTotalShares

我希望扩展这个查询,以便它还显示股东的股东,直到在查询中定义的某个层级限制(例如,假设为3)。因此,在上面的示例中,查询将返回公司'ABC'的所有股东,它们可以是“Person”或“Company”节点类型。在'ABC'的股东是“Company”节点的情况下,我基本上希望在它们身上重复相同的代码,并列出所有他们的股东,以及在该公司中的股权百分比。这应该持续到达到限制为止(即3)。

英文:

I have two node types "Person" and "Company". There is also a relationship called "Shareholder", which can be from Person to Company or Company to Company (as companies can also hold shares in other companies). The Shareholder relationship has three properties "shareholding_start_date", "shareholding_end_date", "number_of_shares".

I have a query which takes in Company name (call it 'ABC'), as well as provide a date (e.g. '2021-07-23'), and the query returns all Person/Company nodes that are shareholders of c at the date '2021-07-23' (i.e. shareholding_start_date < 2021-07-23 < shareholding_end_date). It also calculates the percentage based on 'number_of_shares'.

The working query is below:

MATCH (c:Company {name:”ABC”})&lt;-[r:Shareholder]-(s)
WHERE R.shareholding_start_date &lt;= Date(“2021-07-23”) 
   AND r.shareholding_end_date &gt;= DATE(“2021-07-23”)
WITH c, 
collect(s) as shareholders, 
collect(r) as rels, 
sum(r.number_of_shares) AS totalShares
UNWIND range(0, size(rels) - 1) AS I
RETURN shareholders[i].name AS shareholder, 
100.0 * rels[i].number_of_shares/totalShares as percentTotalShares

I am looking to extend this so that it also shows shareholders of shareholders, up to a certain layer limit which can be defined in the query (let's say 3, for example). So in the above example, the query will return all shareholders of company 'ABC', which may be "Person" or "Company" node types. In the case where the shareholders of 'ABC' are "Company" nodes, I basically want to repeat the same code on them and list all their Shareholders, along with the shareholding % in that company. This should continue until we reach the limit (i.e. 3).

答案1

得分: 1

假设

  1. 日期约束适用于指定公司("ABC")的所有关系路径。
  2. 如果仍然持有股份,那么结束日期属性不存在。
  3. 如果实体仅销售给定购买的一部分股份,将添加结束日期属性,股份数量减少到已出售的数量,并使用剩余股份创建具有相同开始日期的新关系。(这在我的答案中未实现,因为用例不出售任何股份)。
  4. 计算公司的totalShares时也强制执行日期约束。
  5. 公司可以持有自己的股份。
  6. 您可以在parameters $targetDate$company 中传递目标日期和公司名称。

数据模型

为了更容易阅读,我简化了数据模型中的名称:

(:Person|Company {name})-[:HOLDS {start, end, shares}]->(:Company {name})

Cypher 查询

以下是实现您用例的一种方法:

WITH DATE($targetDate) AS targetDate

// 找到与指定公司在目标日期上的路径长度不超过3的所有路径。
MATCH ()-[rs:HOLDS*..3]->(:Company {name: $company})
WHERE ALL(r IN rs WHERE r.start <= targetDate AND (r.end IS NULL OR targetDate <= r.end))

// 收集路径中每个公司的不同HOLD关系。
WITH targetDate, ENDNODE(rs[0]) AS c, COLLECT(DISTINCT rs[0]) AS rels

// 计算每个公司在目标日期上持有的股份总数。
UNWIND rels AS rel
WITH c, rels, SUM(rel.shares) AS totalShares

// 返回公司、股东和股东在目标日期上拥有的公司的百分比。
UNWIND rels AS rel
WITH totalShares, c.name AS company, STARTNODE(rel).name AS holder, SUM(rel.shares) AS shares
RETURN company, holder, 100.0*shares/totalShares AS pct

测试数据

CREATE (p1:Person {name: 'John'}),
       (p2:Person {name: 'Dave'}),
       (p3:Person {name: 'Alice'})

CREATE (c1:Company {name: 'Comp1'}),
       (c2:Company {name: 'Comp2'}),
       (c3:Company {name: 'Comp3'}),
       (c4:Company {name: 'ABC'})

CREATE (p1)-[:HOLDS {start: date('2018-01-01'), end: date('2022-12-31'), shares: 1000}]->(c1)
CREATE (c2)-[:HOLDS {start: date('2020-01-01'), end: date('2025-12-31'), shares: 750}]->(c1)
CREATE (p3)-[:HOLDS {start: date('2019-01-01'), end: date('2022-12-31'), shares: 800}]->(c3)
CREATE (c1)-[:HOLDS {start: date('2016-07-01'), end: date('2023-12-31'), shares: 800}]->(c4)

// Dave购买了Comp1的500股,后来又购买了250股。
// 然后他在'2023-06-30'从第二批中出售了125股,仍然持有剩余的125股。
CREATE (p2)-[:HOLDS {start: date('2020-01-01'), end: date('2023-12-31'), shares: 500}]->(c1)
CREATE (p2)-[:HOLDS {start: date('2023-01-01'), end: date('2023-06-30'), shares: 125}]->(c1)
CREATE (p2)-[:HOLDS {start: date('2023-01-01'), shares: 125}]->(c1)

// ABC和Comp2互相拥有股份
CREATE (c2)-[:HOLDS {start: date('2017-01-01'), end: date('2023-12-31'), shares: 700}]->(c4)
CREATE (c4)-[:HOLDS {start: date('2016-07-01'), shares: 500}]->(c2)

// Comp1持有(并继续持有)自己的一些股份
CREATE (c1)-[:HOLDS {start: date('2021-01-01'), shares: 500}]->(c1)

结果

使用 $targetDate = "2023-07-21" 和 $company = "ABC":

╒═══════╤═══════╤══════════════════╕
│company│holder │pct               │
╞═══════╪═══════╪══════════════════╡
│"ABC"  │"Comp2"│46.666666666666664│
├───────┼───────┼──────────────────┤
│"ABC"  │"Comp1"│53.333333333333336│
├───────┼───────┼──────────────────┤
│"Comp2"│"ABC"  │100.0             │
├───────┼───────┼──────────────────┤
│"Comp1"│"Comp2"│40.0              │
├───────┼───────┼──────────────────┤
│"Comp1"│"Dave" │33.333333333333336│
├───────┼───────┼──────────────────┤
│"Comp1"│"Comp1"│26.666666666666668│
└───────┴───────┴──────────────────┘

匹配图

以下是上述查询的第一个MATCH/WHERE对找到的路径的图形表示。

在Neo4j图中扩展节点的层级

英文:

Assumptions

  1. The date constraint is applied to all relationships in every path to the specified company ("ABC").
  2. The end date property does not exist if shares are still currently held.
  3. If an entity sells only a portion of its shares from a given purchase, the end date property is added, the number of shares is reduced to the number sold, and the remaining shares are used to create a new relationship with the same start date. (This is not implemented in my answer, since the use case does not sell any shares).
  4. The date constraint is also enforced when calculating totalShares for a company.
  5. A company can hold some of its own shares.
  6. You pass the target date and company name in the parameters $targetDate and $company.

Data model

I simplified the names in the data model for easier reading:

(:Person|Company {name})-[:HOLDS {start, end, shares}]-&gt;(:Company {name})

Cypher query

Here is one way to implement your use case:

WITH DATE($targetDate) AS targetDate

// Find all paths up to length 3 involving the specified company, on the target date.
MATCH ()-[rs:HOLDS*..3]-&gt;(:Company {name: $company})
WHERE ALL(r IN rs WHERE r.start &lt;= targetDate AND (r.end IS NULL OR targetDate &lt;= r.end))

// Collect the distinct HOLD relationships in the paths for each company.
WITH targetDate, ENDNODE(rs[0]) AS c, COLLECT(DISTINCT rs[0]) AS rels

// Calculate total number of shares owned by each company in the paths, on the target date
UNWIND rels AS rel
WITH c, rels, SUM(rel.shares) AS totalShares

// Return a company, a shareholder, and the percentage of that company owned by the shareholder on the target date.
UNWIND rels AS rel
WITH totalShares, c.name AS company, STARTNODE(rel).name AS holder, SUM(rel.shares) AS shares
RETURN company, holder, 100.0*shares/totalShares AS pct

Test data

CREATE (p1:Person {name: &#39;John&#39;}),
       (p2:Person {name: &#39;Dave&#39;}),
       (p3:Person {name: &#39;Alice&#39;})

CREATE (c1:Company {name: &#39;Comp1&#39;}),
       (c2:Company {name: &#39;Comp2&#39;}),
       (c3:Company {name: &#39;Comp3&#39;}),
       (c4:Company {name: &#39;ABC&#39;})

CREATE (p1)-[:HOLDS {start: date(&#39;2018-01-01&#39;), end: date(&#39;2022-12-31&#39;), shares: 1000}]-&gt;(c1)
CREATE (c2)-[:HOLDS {start: date(&#39;2020-01-01&#39;), end: date(&#39;2025-12-31&#39;), shares: 750}]-&gt;(c1)
CREATE (p3)-[:HOLDS {start: date(&#39;2019-01-01&#39;), end: date(&#39;2022-12-31&#39;), shares: 800}]-&gt;(c3)
CREATE (c1)-[:HOLDS {start: date(&#39;2016-07-01&#39;), end: date(&#39;2023-12-31&#39;), shares: 800}]-&gt;(c4)

// Dave bought 500 shares of Comp1, and later bought 250 more.
// Then he sold 125 shares from the second batch on &#39;2023-06-30&#39; and still has the remaining 125 shares.
CREATE (p2)-[:HOLDS {start: date(&#39;2020-01-01&#39;), end: date(&#39;2023-12-31&#39;), shares: 500}]-&gt;(c1)
CREATE (p2)-[:HOLDS {start: date(&#39;2023-01-01&#39;), end: date(&#39;2023-06-30&#39;), shares: 125}]-&gt;(c1)
CREATE (p2)-[:HOLDS {start: date(&#39;2023-01-01&#39;), shares: 125}]-&gt;(c1)

// ABC and Comp2 own shares of each other
CREATE (c2)-[:HOLDS {start: date(&#39;2017-01-01&#39;), end: date(&#39;2023-12-31&#39;), shares: 700}]-&gt;(c4)
CREATE (c4)-[:HOLDS {start: date(&#39;2016-07-01&#39;), shares: 500}]-&gt;(c2)

// Comp1 holds (and continues to hold) some of its own shares
CREATE (c1)-[:HOLDS {start: date(&#39;2021-01-01&#39;), shares: 500}]-&gt;(c1)

Results

Using $targetDate = "2023-07-21", and $company = "ABC":

╒═══════╤═══════╤══════════════════╕
│company│holder │pct               │
╞═══════╪═══════╪══════════════════╡
│&quot;ABC&quot;  │&quot;Comp2&quot;│46.666666666666664│
├───────┼───────┼──────────────────┤
│&quot;ABC&quot;  │&quot;Comp1&quot;│53.333333333333336│
├───────┼───────┼──────────────────┤
│&quot;Comp2&quot;│&quot;ABC&quot;  │100.0             │
├───────┼───────┼──────────────────┤
│&quot;Comp1&quot;│&quot;Comp2&quot;│40.0              │
├───────┼───────┼──────────────────┤
│&quot;Comp1&quot;│&quot;Dave&quot; │33.333333333333336│
├───────┼───────┼──────────────────┤
│&quot;Comp1&quot;│&quot;Comp1&quot;│26.666666666666668│
└───────┴───────┴──────────────────┘

Matching graph

Here is a graph of the paths found by the above query's first MATCH/WHERE pair.

在Neo4j图中扩展节点的层级

Here is the visualization query (a minor variant of the first 3 clauses of above query):

WITH DATE($targetDate) AS targetDate
MATCH (h)-[rs:HOLDS*..3]-&gt;(c:Company {name: $company})
WHERE ALL(r IN rs WHERE r.start &lt;= targetDate AND (r.end IS NULL OR targetDate &lt;= r.end))
RETURN *

huangapple
  • 本文由 发表于 2023年7月13日 09:44:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76675398.html
匿名

发表评论

匿名网友

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

确定