【问题标题】:How do I calculate totals for the root nodes in a tree in neo4j?如何计算 neo4j 中树中根节点的总数?
【发布时间】:2019-04-28 03:03:18
【问题描述】:

我正在学习 cypher,遇到了一个我已经实际解决的问题,但我想知道是否有更好的方法来编写 cypher 查询。

我有一个任意深度的层次结构(树),由公司及其子公司和子公司的子公司等组成。

每个公司/子公司都是一个节点,每个节点上的一个属性是该特定公司/子公司获得的收入。

我想只计算根节点的总收入。也就是说,我需要计算顶级公司的总收入,即其自身收入加上其下所有子公司的收入之和。

我提出的查询会计算每个迷你树(父级及其直接子公司)的所有小计。查询似乎从树的底部开始并向上运行。

查询的第一部分的输出是所有节点(叶子除外)的列表及其下所有节点的总和。

接下来我计算所有的根节点并将这个根节点列表“加入”到上一个结果中。

这将返回我需要的答案。然而,这似乎很复杂——因此我的问题是,有没有办法更优雅地做到这一点——也许只需要一个匹配子句?

以下是一些示例数据和我目前编写的查询。

create (a:Company {revenue: 10, cid: "a"})
create (b:Company {revenue: 10, cid: "b"})
create (c:Company {revenue: 20, cid: "c"})
create (d:Company {revenue: 15, cid: "d"})
create (e:Company {revenue: 20, cid: "e"})
create (f:Company {revenue: 25, cid: "f"})
create (g:Company {revenue: 30, cid: "g"})
create (h:Company {revenue: 10, cid: "h"})
create (i:Company {revenue: 20, cid: "i"})
create (j:Company {revenue: 20, cid: "j"})
create (k:Company {revenue: 40, cid: "k"})
create (l:Company {revenue: 10, cid: "l"})
create (m:Company {revenue:  5, cid: "m"})

create (b)-[:REPORTS_TO]->(a)
create (c)-[:REPORTS_TO]->(a)
create (d)-[:REPORTS_TO]->(b)
create (e)-[:REPORTS_TO]->(c)
create (f)-[:REPORTS_TO]->(c)

create (h)-[:REPORTS_TO]->(g)
create (i)-[:REPORTS_TO]->(g)
create (j)-[:REPORTS_TO]->(h)
create (k)-[:REPORTS_TO]->(h)
create (l)-[:REPORTS_TO]->(i)
create (m)-[:REPORTS_TO]->(i)
;

这是我创建的查询:

// First Calculate total revenue for each company in the tree with subsidiaries.
// This will include top level and intermediate level companies.
match (c: Company)<-[:REPORTS_TO*]-(s:Company)
  with c.cid as r_cid, sum (s.revenue) + c.revenue as tot_revenue

// Next, Determine the root nodes
// "join" the list of root nodes to the totals for each company.
// The result is the root node companies with their total revenues.
  match (c)
  where not ()<-[:REPORTS_TO]-(c) AND
      c.cid = r_cid
      // Return the root company id and the revenue for it.
  return c.cid, tot_revenue;

以上返回我期望的结果是:

+---------------------+
| c.cid | tot_revenue |
+---------------------+
| "g"   | 135         |
| "a"   | 100         |
+---------------------+

同样,这个问题是关于是否有比我想出的解决方案更好的方法来编写密码查询?

【问题讨论】:

  • 你提到你正在学习 Cypher,你写了一个很好的查询。
  • 感谢 Raj - 花了一段时间(和大量搜索),但我到了! :-)

标签: neo4j tree cypher


【解决方案1】:

是的,有一些方法可以使您的 Cypher 查询更好。

您在查询中所做的一些事情是不需要或可以改进的:

  1. 第二次扫描所有节点,然后过滤WHERE,将当前节点的cid与这些节点匹配,得到你已经拥有的节点。

  2. 为所有公司计算total revenue。您可以避免计算子公司的总收入,因为您没有在任何地方使用它。

要使查询高效运行,您需要最小化数据库调用总数(AKA db hits)。您可以通过分析查询来检查您的数据库命中。这将向您显示查询计划以及哪些操作员正在完成大部分工作。 您需要通过在开头添加PROFILE 来运行查询。

我为您的查询进行了分析。您的查询的总数据库命中数为 311.

让我们逐步更改您的查询:

删除不必要的比较:总 db hits 减少到 131

PROFILE 
MATCH (c:Company)<-[:REPORTS_TO*]-(s:Company)
WITH c, sum(s.revenue) + c.revenue AS tot_revenue
MATCH (c)
WHERE  NOT ()<-[:REPORTS_TO]-(c)
RETURN c.cid, tot_revenue;

通过在计算之前过滤根公司来避免计算子公司的总收入。总 db 命中减少到 108

PROFILE 
MATCH (c:Company)<-[:REPORTS_TO*]-(s:Company)
WHERE  NOT ()<-[:REPORTS_TO]-(c)
WITH c.cid AS r_cid, sum(s.revenue) + c.revenue AS tot_revenue
RETURN r_cid, tot_revenue;

从聚合中分离别名并添加公司收入。总分贝命中减少到 90

PROFILE 
MATCH (c:Company)<-[:REPORTS_TO*]-(s:Company)
WHERE  NOT ()<-[:REPORTS_TO]-(c)
WITH c, sum(s.revenue) AS sub_tot_revenue
RETURN c.cid AS cid, sub_tot_revenue + c.revenue AS tot_revenue;

这些是改进您的解决方案的一些方法。你可以阅读更多关于query tuning in Neo4j documentation.

【讨论】:

  • 感谢 Raj 的提示,我不知道个人资料 - 它真的很有帮助。此外,你一步一步的改进给了我一些有用的见解。我需要挖掘我的历史,因为我确定我尝试了你的倒数第二个查询 - 并且总是得到一个空的结果集。显然,我的倒数第二个查询的变体与您的不同,因为您的查询有效,而我的查询无效。再次感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-19
  • 1970-01-01
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多