知识库

使用 max() 和 min() 同时保留项目

聚合函数 max()min() 非常有用,但有时你会发现自己在与 Cypher 的聚合行为作斗争,而这些情况本应很简单。

当你想计算 max()min()(或类似的)时,却想保留与该最大或最小值关联的项目或多个项目,这种情况经常出现。

让我们用一个非常简单的例子:一个关于人们在商店购买食品的图。

(:Person {name})-[:BOUGHT]->(:FoodItem {name})

我们想要找出每个人购买次数最多的食品项目或多个项目。

本该简单却变得复杂

我们可以轻松使用 max() 来找出他们购买次数最多的食品的计数。

MATCH (person:Person)-[:BOUGHT]->(food:FoodItem)
WITH person, food, count(food) as timesBought
RETURN person, max(timesBought) as mostBoughtCount

但我们失去了生成该结果的食品相关数据!究竟是哪种食品被购买了这么多次?是只有一种食品,还是有几种食品并列?

如果我们像下面这样保留 food 的作用域:RETURN person, food, max(timesBought) as mostBoughtCount,会得到错误的结果,因为每种食品各占一行,而 mostBoughtCount 只对应各自的食品,而不是对所有食品的聚合。

如果我们这样使用 collect():RETURN person, collect(food) as foods, max(timesBought) as mostBoughtCount,虽然 mostBoughtCount 是正确的,但我们收集了所有食品,却不知道哪一个对应这个最大值。

我们被迫放弃这种做法,改为进行排序、collect(),然后保留首个结果

MATCH (person:Person)-[:BOUGHT]->(food:FoodItem)
WITH person, food, count(food) as timesBought
ORDER BY timesBought DESC
RETURN person, collect(food)[0] as favoriteFood, max(timesBought) as mostBoughtCount

但同样的问题是并列怎么办?一个人可能有多个最受欢迎的食品并列在同一 mostBoughtCount。我们可能会花很多时间重构查询,进行多次 collect()、UNWIND、计数和比较,导致查询更加复杂。

APOC 过程帮助保持简洁

最初,我们获得了自定义过程;随后获得了自定义函数;最后我们可以编写自定义聚合函数。自 APOC 3.5.0.5 起,新增了在此类场景下非常有用的函数。

apoc.agg.maxItems(item, value, groupLimit: -1)

返回一个映射 {items:[], value:n},其中 value 为出现的最大值,items 为所有具有该相同值的项目。可选地可以限制返回的项目数量。

同样还有 apoc.agg.minItems(),工作方式类似。

简而言之,这个函数让我们能够使用等同于 min() 或 max() 的功能,同时保留与该值关联的项目或多个项目。

如果我们把它加入查询中,就会得到以下结果

MATCH (person:Person)-[:BOUGHT]->(food:FoodItem)
WITH person, food, count(food) as timesBought
WITH person, apoc.agg.maxItems(food, timesBought) as maxData
RETURN person, maxData.items as favoriteFoods, maxData.value as mostBoughtCount

这使我们能够保留所有并列为最爱食品的项目;如果想限制并列数量,只需在函数调用时添加额外的参数即可。

© . This site is unofficial and not affiliated with Neo4j, Inc.