Assumptions
- The date constraint is applied to all relationships in every path to the specified company ("ABC").
- The end date property does not exist if shares are still currently held.
- 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).
- The date constraint is also enforced when calculating
totalShares
for a company.
- A company can hold some of its own shares.
- You pass the target date and company name in the parameters
$targetDate
and $company
.
Data model
为了便于阅读,我简化了数据模型中的名称:
(:Person|Company {name})-[:HOLDS {start, end, shares}]->(:Company {name})
Cypher query
以下是实现您的用例的一种方法:
WITH DATE($targetDate) AS targetDate
// Find all paths up to length 3 involving the specified company, on the target date.
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))
// 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, TOFLOAT(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*shares/totalShares AS pct
Test data
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 bought 500 shares of Comp1, and later bought 250 more.
// Then he sold 125 shares from the second batch on 2023-06-30 and still has the remaining 125 shares.
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 and Comp2 own shares of each other
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 holds (and continues to hold) some of its own shares
CREATE (c1)-[:HOLDS {start: date( 2021-01-01 ), shares: 500}]->(c1)
Results
╒═══════╤═══════╤══════════════════╕
│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│
└───────┴───────┴──────────────────┘
Matching graph
以下是查询的第一个MATCH
/WHERE
对找到的路径图: