首页 > 解决方案 > Cypher 中的数据透视表类型的查询(一次通过)

问题描述

我试图一次性执行以下查询,但我得出结论,这是不可能的,而且会导致某种形式的“嵌套”结构,这在性能方面绝不是好消息。

但是,我可能在这里遗漏了一些东西,所以我想我可能会问。

底层数据结构是两个实体之间的多对多关系A<---0:*--->B

最终目标是获得在特定时间间隔内实体B对象分配给实体对象的次数占总分配次数的百分比A

正是问题的后半部分引起了头痛。

实体A包含一个item_date字段 实体B包含一个item_category字段。

结果的表示可以扩展到一个表,其列是不同的item_date,行是不同的item_category 标准化计数。我只是为了清楚起见而提到这一点,查询不必以这种确切的形式返回结果。

我的尝试:

with 12*30*24*3600 as window_length, "1980-1-1" as start_date, 
     "1985-12-31" as end_date 
     unwind range(apoc.date.parse(start_date,"s","yyyy-MM-dd"),apoc.date.parse(end_date,"s","yyyy-MM-dd"),window_length) as date_step
     match (a:A)<-[r:RELATOB]-(b:B) 
         where apoc.date.parse(a.item_date,"s","yyyy-MM-dd")>=date_step and apoc.date.parse(a.item_date,"s","yyyy-MM-dd")<(date_step+window_length)
         with window_length, date_step, count(r) as total_count unwind ["code_A", "code_B", "code_C"] as the_code [MATCH THE PATTERN AGAIN TO COUNT SPECIFIC `item_code` this time.

我发现很难一次性表达这一点,因为它需要在GROUP BY图形模式的定义之后相当于两个独立的类似子句。你不能同时表达这两个,所以你必须解开它们。我担心这会导致两种评估:一种用于总计数,另一种用于部分计数。我试图优化的一点是重写查询的某种方式,这样它就不必计算它之前“捕获”的节点,但这对于聚合函数应用于集合的隐含方式非常困难。

基本上,任何不是聚合函数的属性都会成为分层变量。我必须在这里说一个简单的双重分层(“抓住所有东西,通过item_date产生另一个级别的计数来产生一个级别的计数item_code)对我不起作用,因为没有办法控制.的宽度window_length。这意味着我无法在具有不同分配率的item_codes 的两个时间段之间进行比较,因为时间段不相等:(

请注意,在一段时间内(在密码外部)检索这些item_code特定代码的总和然后对其进行归一化不会导致准确的百分比,因为归一化将针对特定子集而不是总数.item_code

有没有办法r在一个时间段内执行同时计数,然后(以某种方式)重新使用已经匹配ab节点子集来评估那些特定的部分b计数(b:{item_code:the_code})-[r2:RELATOB]-(a) where a.item_date...

如果不是,那么我将转到下一个最快的事情,即执行两个独立的查询(一个用于总计数,一个用于部分),然后在外部进行除法:/。

标签: neo4jcypherquery-performance

解决方案


Tomaz Bratanic 在评论中提出的解决方案(我认为)是这样的:

with 1*30*24*3600 as window_length, 
     "1980-01-01" as start_date, 
     "1985-12-31" as end_date 
     unwind range(apoc.date.parse(start_date,"s","yyyy-MM-dd"),apoc.date.parse(end_date,"s","yyyy-MM-dd"),window_length) as date_step 
     unwind ["code_A","code_B","code_c"] as the_code 
         match (a:A)<-[r:RELATOB]-(b:B) 
             where apoc.date.parse(a.item_date,"s","yyyy-MM-dd")>=date_step and apoc.date.parse(a.item_category,"s","yyyy-MM-dd")<(date_step+window_length) 
         return the_code, date_step, tofloat(sum(case when b.item_category=code then 1 else 0 end)/count(r)) as perc_count order by date_step asc

这个:

  1. 正在工作中
  2. 它完全符合我的要求(经过一些小的修改)
  3. 它甚至添加了用零填充缺失值,因为ELSE 0即使不存在计数数据也会有效地强制为零。

但在现实条件下,它至少比我目前使用的重新匹配慢 30 秒(不,不是,请参阅编辑)。(不,这不是因为在填充缺失数据时现在返回的额外数据,这是原始查询时间)。

我认为可能值得在此处附加查询计划:

这是两次应用相同模式但快速的方法的计划:

在此处输入图像描述

这是一次执行计数的计划,但执行速度很慢

在此处输入图像描述

稍后我可能会看到时间如何随输入中的数据缩放,也许两者以不同的速率缩放,但在这一点上,“单程”似乎已经比“双程”慢,坦率地说,我无法看到它如何通过更多数据变得更快。这已经是分布在 18k 个项目(大约)中的 3 个类别的 12 个月的简单计数。

希望这对其他人也有帮助。

编辑:

虽然我最初是这样做的,但还有另一个修改,我没有包括比赛后第二次放松的位置。这将“双重匹配”以下的时间缩短了 20 秒,因为展开会影响返回,而不是同一查询的多次执行,现在变成:

with 1*30*24*3600 as window_length, 
     "1980-01-01" as start_date, 
     "1985-12-31" as end_date 
     unwind range(apoc.date.parse(start_date,"s","yyyy-MM-dd"),apoc.date.parse(end_date,"s","yyyy-MM-dd"),window_length) as date_step  
         match (a:A)<-[r:RELATOB]-(b:B) 
             where apoc.date.parse(a.item_date,"s","yyyy-MM-dd")>=date_step and apoc.date.parse(a.item_category,"s","yyyy-MM-dd")<(date_step+window_length) 
                 unwind ["code_A","code_B","code_c"] as the_code
         return the_code, date_step, tofloat(sum(case when b.item_category=code then 1 else 0 end)/count(r)) as perc_count order by date_step asc

这也是它的执行计划:

在此处输入图像描述

原始双人比赛大约 55790 毫秒,一次完成(比赛前都展开)82306 毫秒,一次完成(比赛第二次展开)23461 毫秒


推荐阅读