首页 > 解决方案 > 如何计算 neo4j 中树中根节点的总数?

问题描述

我正在学习 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         |
+---------------------+

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

标签: neo4jtreecypher

解决方案


是的,有一些方法可以使您的 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;

避免在计算前通过过滤根公司来计算子公司的总收入。总分贝命中减少到 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;

这些是改进您的解决方案的一些方法。您可以在 Neo4j 文档中阅读有关查询调优的更多信息。


推荐阅读