定义
$accumulator定义自定义累加器操作符。累加器是随着文档在管道中的进展,维持其状态的操作符(例如总计、最大值、最小值和相关数据)。使用
$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命令行选项。在早期版本中,MongoDB 不允许在
mongos实例上执行 JavaScript。
另请参阅 ➤使用安全配置选项运行 MongoDB。
不支持的数组与字符串函数
MongoDB 6.0 升级了用于服务器端 JavaScript、$accumulator、$function 和 $where 表达式的内部 JavaScript 引擎,并从 MozJS-60 升级到 MozJS-91。MozJS-91 已删除 MozJS-60 中存在的若干已弃用的非标准数组和字符串函数。
示例
本页上的示例使用来自 sample_mflix 数据集的数据。有关如何将此数据集加载到您的自管理 MongoDB 部署中的详细信息,请参阅 加载示例数据集。如果您对示例数据库进行了任何修改,您可能需要删除并重新创建数据库,才能运行此页面上的示例。
使用 $accumulator 实现 $avg 操作符
注意
此示例介绍如何使用 $accumulator 操作符实施 MongoDB 已支持的 $avg 操作符。此示例的目标不是实现新功能,而是用熟悉的逻辑说明 $accumulator 操作符的行为和语法。
以下操作 groups 按 rated 对文档排序,并使用 $accumulator 计算每个内容评级的平均 imdb.rating。$match 阶段将输入限制为 G、PG、PG-13 和 R 内容评级,并排除 imdb.rating 不是 double 的文档,例如空 string:
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $group: { _id: "$rated", avgRating: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } },
accumulate: function(state, rating) { // Define how to update the state return { count: state.count + 1, sum: state.sum + rating } },
accumulateArgs: [ "$imdb.rating" ], // 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" } } } }, { $sort: { _id: 1 } }] )[ { _id: 'G', avgRating: 6.628092243186578 }, { _id: 'PG', avgRating: 6.43525917926564 }, { _id: 'PG-13', avgRating: 6.404911676001717 }, { _id: 'R', avgRating: 6.456657633242984 }]行为
$accumulator 定义一个初始状态,其中 count 和 sum 都设置为 0。对于 $accumulator 处理的每份文档,它通过以下方式更新状态:
将
count增加 1,然后将文档的
imdb.rating字段的值添加到sum。accumulate 函数可访问imdb.rating字段,因为它是在 accumulateArgs 字段中传递。
对于处理的每个文档,accumulate 函数都返回更新状态。
处理完所有文档后,finalize 函数将评分的 sum 除以文档的 count,得出平均值。由于 finalize 函数会接收所有文档的累加 sum 和 count,因此无需保持运行计算平均值。
使用以下项进行比较 $avg
此操作等同于使用 $avg 运算符的以下管道:
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $group: { _id: "$rated", avgRating: { $avg: "$imdb.rating" } } }, { $sort: { _id: 1 } }] )[ { _id: 'G', avgRating: 6.6280922431865825 }, { _id: 'PG', avgRating: 6.435259179265659 }, { _id: 'PG-13', avgRating: 6.404911676001723 }, { _id: 'R', avgRating: 6.456657633242999 }]使用initArgs 按群组改变初始状态
您可以使用 initArgs 选项来改变$accumulator 的初始状态。如果您愿意,这可能很有用,示例:
使用不属于您所在状态的字段的值来影响您的状态,或者
根据正在处理的群组将初始状态设置为不同值。
假设某应用程序允许用户查询此数据以查找电影。它可能有助于显示用户偏好的内容分级的更多结果。在此示例中,用户偏好的内容分级为 PG-13,它作为 userProfileRated 传递给 init 函数。
以下聚合管道按rated对文档groups。该操作使用 $accumulator 显示不同数量的电影,具体取决于电影的评级是否与用户个人资料中的评级相匹配:
注意
要使用不同的首选内容分级运行此示例,请将 initArgs 数组中的 "PG-13" 替换为其他值,例如 "R"。
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $sort: { "imdb.rating": -1, title: 1 } }, { $group: { _id: { rated: "$rated" }, movies: { $accumulator: { init: function(rated, userProfileRated) { // Set the initial state return { max: rated === userProfileRated ? 3 : 1, // If the group matches the user's rating, movies: [] // return 3 movies. Otherwise, return 1. } },
initArgs: [ "$rated", "PG-13" ], // Argument to pass to the init function
accumulate: function(state, movieTitle) { // Define how to update the state if (state.movies.length < state.max) { state.movies.push(movieTitle); } return state; },
accumulateArgs: [ "$title" ], // Argument required by the accumulate function
merge: function(state1, state2) { return { max: state1.max, movies: state1.movies.concat(state2.movies).slice(0, state1.max) } },
finalize: function(state) { // Adjust the state to only return the field we need return state.movies }, lang: "js" } } } }, { $sort: { "_id.rated": 1 } }] )[ { _id: { rated: 'G' }, movies: [ 'Modern Times' ] }, { _id: { rated: 'PG' }, movies: [ 'Life' ] }, { _id: { rated: 'PG-13' }, movies: [ 'A Brave Heart: The Lizzie Velasquez Story', 'The Dark Knight', 'The Lord of the Rings: The Return of the King' ] }, { _id: { rated: 'R' }, movies: [ 'The Shawshank Redemption' ] }]行为
init 函数定义一个包含 max 和 movies 字段的初始状态。max 字段设置该特定群组的最大电影数量。如果文档的 rated 字段与 userProfileRated 匹配,则该群组最多包含 3 部电影。否则,如果文档的 rated 字段与 userProfileRated 不匹配,则该群组最多包含一部电影。init 函数接收来自 initArgs 数组的 rated 和 userProfileRated 参数。
对于 $accumulator 处理的每份文档,它会将电影的 title 推入 movies 数组,前提是该标题不会使 movies 的长度超过 max 值。对于处理的每个文档,accumulate 函数都返回更新省/市/自治区。
merge 函数定义如何合并两个状态。该函数将每个状态的 movies 数组连接在一起,并使用slice()方法限制生成的大量的长度,以确保其不超过 max 值。
处理完所有文档后,finalize 函数会修改结果省/市/自治区,从而仅返回电影的标题。如果没有此函数,max 字段也会包含在输出中,这无法满足该应用程序的任何需求。