使用 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` 中并列。我们可能会花费大量时间重构该查询,进行 `collects()`、`UNWINDs`、计数和比较,导致查询变得更加复杂。
APOC 过程有助于简化操作
首先,我们获得了自定义过程,然后是自定义函数,最后是编写自定义聚合函数的能力。自 APOC 3.5.0.5 版本起,新增了一些函数来帮助处理这些情况。
|
返回一个映射 `{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
这使我们能够保留所有并列为最喜欢的食物,如果确实想限制并列项,可以将其作为附加参数添加到函数调用中。
此页面有用吗?