$accumulator(聚合)
定义
$accumulator
重要
服务器端 JavaScript 已弃用
从 MongoDB8.0 开始,服务器端 JavaScript 函数({
$accumulator
$function
$where
2} 、{ 10}、$where
)将被弃用。运行这些函数时,MongoDB 会记录警告。定义自定义累加器操作符。累加器是随着文档在管道中的进展,维持其状态的操作符(例如总计、最大值、最小值和相关数据)。使用
$accumulator
操作符执行您自己的 JavaScript 函数,以实现 MongoDB 查询语言不支持的行为。另请参阅$function
。$accumulator
可在以下阶段使用:重要
在聚合操作符内执行 JavaScript 可能会降低性能。仅当提供的 管道操作符 无法满足您的应用程序需求时才使用
$accumulator
操作符。
语法
$accumulator
操作符的语法如下:
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
字段 | 类型 | 说明 | ||||
---|---|---|---|---|---|---|
字符串或代码 | 用于初始化状态的函数。
溢出到磁盘或在分片集群上运行查询可导致累加器被计算为多个子累加的合并,其中的每个子累加都以调用 | |||||
阵列 | 可选。传递给
重要提示:在 | |||||
字符串或代码 | 用于累积文档的函数。
| |||||
阵列 | 传递给
| |||||
字符串或代码 | 用于合并两个内部状态的函数。
| |||||
字符串或代码 | 可选。用于更新累积结果的函数。
| |||||
字符串 |
重要提示:目前, |
行为
以下步骤概述 $accumulator
操作符如何处理文档:
操作符从 init 函数定义的初始状态开始。
对于每份文档,操作符根据 accumulate 函数更新状态。accumulate 函数的第一个参数是当前状态,其他参数在 accumulateArgs 数组中指定。
当此操作符需要合并多个中间状态时,它会执行合并函数。有关何时调用合并函数的更多信息,请参阅使用
$merge
合并两个状态。
将两个状态合并为 $merge
在内部操作中,$accumulator
操作符可能需要合并两个独立的中间状态。merge 函数指定操作符应如何合并两个状态。
合并函数始终是一次合并两个状态。如果必须合并两个以上的状态,则两个状态的合并结果将与单个状态合并。重复此过程直到合并所有状态。
例如,在以下情况下,$accumulator
可能需要组合两个状态:
$accumulator
在分片集群上运行。操作符需要合并每个分片的结果,以获得最终结果。单次
$accumulator
操作超出指定内存限制。如果您指定allowDiskUse
选项,该操作符会将进行中的操作存储到磁盘上,并在内存中完成操作。操作完成后,使用 merge 函数将磁盘和内存中的结果合并。
文档处理顺序
MongoDB 为 init()
、accumulate()
和 merge()
函数处理文档的顺序会有所不同,并且可能与为 $accumulator
函数指定文档的顺序不同。
例如,请考虑一系列文档,其中 _id
字段是字母表的字母:
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
接下来,请考虑一个聚合管道,该管道按 _id
字段对文档进行排序,然后使用 $accumulator
函数连接 _id
字段值:
[ { $sort: { _id: 1 } }, { $group: { _id: null, alphabet: { $accumulator: { init: function() { return "" }, accumulate: function(state, letter) { return(state + letter) }, accumulateArgs: [ "$_id" ], merge: function(state1, state2) { return(state1 + state2) }, lang: "js" } } } } ]
MongoDB 不保证按排序顺序处理文档,这意味着 alphabet
字段不一定设置为 abc...z
。
由于此行为,请确保 $accumulator
函数不需要按特定顺序处理并返回文档。
Javascript 启用
要使用 $accumulator
,必须启用服务器端脚本功能。
如果您不使用 $accumulator
(或 $function
、$where
或 mapReduce
),请禁用服务器端脚本:
对于
mongod
实例,请参阅security.javascriptEnabled
配置选项或--noscripting
命令行选项。对于
mongos
实例,请参阅security.javascriptEnabled
配置选项或--noscripting
命令行选项。In earlier versions, MongoDB does not allow JavaScript execution onmongos
instances.
另请参阅 ➤使用安全配置选项运行 MongoDB。
不支持的数组与字符串函数
MongoDB 6.0 升级了用于服务器端 JavaScript、$accumulator
、$function
和 $where
表达式的内部 JavaScript 引擎,并从 MozJS-60 升级到 MozJS-91。MozJS-91 已删除 MozJS-60 中存在的若干已弃用的非标准数组和字符串函数。
有关已删除数组和字符串函数的完整列表,请参阅 6.0 兼容性说明。
示例
使用$accumulator
实施$avg
操作符
注意
此示例介绍如何使用 $accumulator
操作符实施 MongoDB 已支持的 $avg
操作符。此示例的目标不是实现新功能,而是用熟悉的逻辑说明 $accumulator
操作符的行为和语法。
在 mongosh
中创建名为 books
的示例集合,其中包含以下文档:
db.books.insertMany([ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }, { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ])
以下操作按 author
对文档进行 groups
,并使用 $accumulator
计算每位作者所有书籍的平均副本数:
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } }, accumulate: function(state, numCopies) { // Define how to update the state return { count: state.count + 1, sum: state.sum + numCopies } }, accumulateArgs: ["$copies"], // Argument required by the accumulate function merge: function(state1, state2) { // When the operator performs a merge, return { // add the fields from the two states count: state1.count + state2.count, sum: state1.sum + state2.sum } }, finalize: function(state) { // After collecting the results from all documents, return (state.sum / state.count) // calculate the average }, lang: "js" } } } } ])
结果
此操作返回以下结果:
{ "_id" : "Dante", "avgCopies" : 1.6666666666666667 } { "_id" : "Homer", "avgCopies" : 10 }
行为
$accumulator
定义一个初始状态,其中 count
和 sum
都设置为 0
。对于 $accumulator
处理的每份文档,它通过以下方式更新状态:
将
count
增加 1,然后将文档的
copies
字段的值添加到sum
。accumulate 函数可访问copies
字段,因为它是在 accumulateArgs 字段中传递。
对于处理的每个文档,accumulate 函数都返回更新状态。
处理完所有文档后,finalize 函数将副本的 sum
除以文档的 count
,得出平均值。由于 finalize 函数会接收所有文档的累加 sum
和 count
,因此无需保持运行计算平均值。
使用以下项进行比较 $avg
此操作等同于使用 $avg
运算符的以下管道:
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $avg: "$copies" } } } ])
使用initArgs
按群组改变初始状态
您可以使用 initArgs 选项来改变$accumulator
的初始状态。如果您愿意,这可能很有用,例如:
使用不属于您所在状态的字段的值来影响您的状态,或者
根据正在处理的群组将初始状态设置为不同值。
在 mongosh
中创建名为 restaurants
的示例集合,其中包含以下文档:
db.restaurants.insertMany([ { "_id" : 1, "name" : "Food Fury", "city" : "Bettles", "cuisine" : "American" }, { "_id" : 2, "name" : "Meal Macro", "city" : "Bettles", "cuisine" : "Chinese" }, { "_id" : 3, "name" : "Big Crisp", "city" : "Bettles", "cuisine" : "Latin" }, { "_id" : 4, "name" : "The Wrap", "city" : "Onida", "cuisine" : "American" }, { "_id" : 5, "name" : "Spice Attack", "city" : "Onida", "cuisine" : "Latin" }, { "_id" : 6, "name" : "Soup City", "city" : "Onida", "cuisine" : "Chinese" }, { "_id" : 7, "name" : "Crave", "city" : "Pyote", "cuisine" : "American" }, { "_id" : 8, "name" : "The Gala", "city" : "Pyote", "cuisine" : "Chinese" } ])
假设某应用程序允许用户查询此数据以查找餐厅。它可能有助于显示该用户所在城市的更多结果。在此示例中,假定用户的城市在名为 userProfileCity
的变量中被调用。
以下聚合管道按city
对文档groups
。该操作使用 $accumulator
显示各城市的不同数量的结果,具体取决于餐厅所在城市是否与用户个人资料中的城市相匹配:
1 db.restaurants.aggregate([ 2 { 3 $group : 4 { 5 _id : { city: "$city" }, 6 restaurants: 7 { 8 $accumulator: 9 { 10 init: function(city, userProfileCity) { // Set the initial state 11 return { 12 max: city === userProfileCity ? 3 : 1, // If the group matches the user's city, return 3 restaurants 13 restaurants: [] // else, return 1 restaurant 14 } 15 }, 16 17 initArgs: ["$city", <userProfileCity>], // Argument to pass to the init function 18 19 accumulate: function(state, restaurantName) { // Define how to update the state 20 if (state.restaurants.length < state.max) { 21 state.restaurants.push(restaurantName); 22 } 23 return state; 24 }, 25 26 accumulateArgs: ["$name"], // Argument required by the accumulate function 27 28 merge: function(state1, state2) { 29 return { 30 max: state1.max, 31 restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) 32 } 33 }, 34 35 finalize: function(state) { // Adjust the state to only return field we need 36 return state.restaurants 37 } 38 39 lang: "js" 40 } 41 } 42 } 43 } 44 ])
结果
如果 userProfileCity
的值为 Bettles
,此操作会返回以下结果:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury", "Meal Macro", "Big Crisp" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
如果 userProfileCity
的值为 Onida
,此操作会返回以下结果:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap", "Spice Attack", "Soup City" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
如果 userProfileCity
的值为 Pyote
,此操作会返回以下结果:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave", "The Gala" ] } }
如果 userProfileCity
的值是任何其他值,则该操作将返回以下结果:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
行为
init 函数定义一个包含 max
和 restaurants
字段的初始状态。max
字段设置该特定群组的最大餐厅数量。如果文档的 city
字段与 userProfileCity
匹配,则该群组最多包含 3 家餐厅。如果文档 _id
与 userProfileCity
匹配,则该群组最多包含一家餐厅。init 函数接收来自 initArgs 数组的 city
userProfileCity
参数。
对于 $accumulator
处理的每份文档,它会将餐厅的 name
推入 restaurants
数组,前提是该名称不会使 restaurants
的长度超过 max
值。对于处理的每个文档,accumulate 函数都返回更新状态。
merge函数定义如何合并两个状态。 该函数将每个状态的restaurant
数组连接在一起,并使用 slice() 限制生成数组的长度 方法,以确保其不超过max
值。
处理完所有文档后,finalize 函数会修改结果状态,从而仅返回餐厅的名称。如果没有此函数,max
字段也会包含在输出中,这无法满足该应用程序的任何需求。