注意
作为 Map-Reduce 替代方案的聚合管道
从 MongoDB 5.0 开始, map-reduce已弃用:
您应该使用聚合管道,而不是 map-reduce。聚合管道提供比 map-reduce 更好的性能和可用性。
对于需要自定义功能的 map-reduce 操作,可以使用
$accumulator和$function聚合操作符。可以使用这些操作符在 JavaScript 中定义自定义聚合表达式。
有关 map-reduce 的聚合管道替代方案的示例,请参阅:
重要
mongosh 方法
This page documents a mongosh method. This is not the documentation for database commands or language-specific drivers, such as Node.js.
有关数据库命令,请参阅 mapReduce 命令。
如需了解 MongoDB API 驱动程序,请参阅特定语言的 MongoDB 驱动程序文档。
定义
db.collection.mapReduce(map,reduce, { <options> })注意
视图不支持 map-reduce 操作。
兼容性
此方法可用于以下环境中托管的部署:
- MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
重要
M0 和 Flex 集群不支持此命令。有关更多信息,请参阅不支持的命令。
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本
语法
注意
MongoDB 会忽略 verbose 选项。
从版本 4.2 开始,MongoDB 弃用了:
- 用于创建 新的分片集合的 map-reduce 选项,以及对 map-reduce 使用分片选项。要输出到分片集合,请首先创建分片集合。MongoDB 4.2 还弃用了替换现有分片集合的功能。
db.collection.mapReduce() 通过以下语法实现:
db.collection.mapReduce( <map>, <reduce>, { out: <collection>, query: <document>, sort: <document>, limit: <number>, finalize: <function>, scope: <document>, jsMode: <boolean>, verbose: <boolean>, bypassDocumentValidation: <boolean> } )
参数
db.collection.mapReduce() 使用以下参数:
Parameter | 类型 | 说明 |
|---|---|---|
| JavaScript 或字符串 | |
| JavaScript 或字符串 | 一个 JavaScript 函数,可将与特定 请参阅 reduce 函数的要求,了解更多信息。 |
| 文档 | A document that specifies additional parameters to |
字段
The following table describes additional arguments that db.collection.mapReduce() can accept.
字段 | 类型 | 说明 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 字符串或文档 | |||||||||||
| 文档 | 使用查询运算符指定选择标准,确定输入到 | ||||||||||
| 文档 | 对输入文档排序。此选项对于优化非常有用。例如,将排序键指定为与发出键相同,这样可减少 reduce 操作。排序键必须位于此集合的现有索引中。 | ||||||||||
| 数字 | 指定输入到 | ||||||||||
| JavaScript 或字符串 | 可选。一个 JavaScript 函数,用于修改 请参阅 finalize 函数的要求,了解更多信息。 | ||||||||||
| 文档 | 指定可在 | ||||||||||
| 布尔 | 指定是否在计算 默认值为 如果为
如果为
| ||||||||||
| 布尔 | 指定是否在结果信息中包含 默认值为 This option is ignored. The result information always excludes the | ||||||||||
| 文档 | 可选。 可选。指定用于操作的排序规则。 排序规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音符号规则。 排序规则选项的语法如下: 指定排序规则时, 如果未指定排序规则,但集合具有默认排序规则(请参阅 如果没有为收集或操作指定排序规则,MongoDB 将使用先前版本中使用的简单二进制比较来进行字符串比较。 您不能为一个操作指定多个排序规则。例如,您不能为每个字段指定不同的排序规则,或者如果执行带排序的查找,则不能使用一种排序规则进行查找而另一种排序规则进行排序。 | ||||||||||
| 布尔 | 可选。启用 |
注意
map-reduce operations and $where operator expressions cannot access certain global functions or properties, such as db, that are available in mongosh.
以下 JavaScript 函数和属性用于 map-reduce operations 和 $where 操作符表达式:
可用属性 | 可用函数 | |
|---|---|---|
|
|
|
map 函数的要求
map 函数负责将每个输入文档转换为零个或多个文档。它可以访问 scope 参数中定义的变量,并具有以下原型:
function() { ... emit(key, value); }
map 函数具有以下要求:
在
map函数中,将当前文档引用为函数内的this。map函数不应 出于任何原因访问数据库。map函数应该是纯函数,或者对函数外部没有影响(即副作用)。map函数可以选择调用emit(key,value)任意次,以创建将key与value关联的输出文档。
以下 map 函数将调用 emit(key,value) 0 次或 1 次,具体取决于输入文档的 status 字段的值:
function() { if (this.status == 'A') emit(this.cust_id, 1); }
以下 map 函数可能会多次调用 emit(key,value) ,具体取决于输入文档的 items 字段中的元素数量:
function() { this.items.forEach(function(item){ emit(item.sku, 1); }); }
reduce 函数的要求
reduce 函数具有以下原型:
function(key, values) { ... return result; }
reduce 函数表现出以下行为:
reduce函数不应访问数据库,即使是执行读操作也是如此。reduce函数不应 影响外部系统。MongoDB 可以针对同一键多次调用
reduce函数。在这种情况下,该键的reduce函数的先前输出将成为该键的下一个reduce函数调用的输入值之一。reduce函数可以访问scope参数中定义的变量。reduce的输入不得大于 MongoDB 最大 BSON 文档大小的一半。当返回大型文档并在后续的reduce步骤中将其连接在一起时,可能会违反此要求。
由于可以针对同一键多次调用 reduce 函数,因此以下属性必须为 true:
返回对象的类型必须与
map函数发出的value的类型相同。reduce函数必须是关联函数。以下语句必须为 true:reduce(key, [ C, reduce(key, [ A, B ]) ] ) == reduce( key, [ C, A, B ] ) reduce函数必须是幂等的。确保以下语句为 true:reduce( key, [ reduce(key, valuesArray) ] ) == reduce( key, valuesArray ) reduce函数应该是可交换的:也就是说,valuesArray中元素的顺序不应影响reduce函数的输出,因此以下语句成立:reduce( key, [ A, B ] ) == reduce( key, [ B, A ] )
out 选项
您可以为 out 参数指定以下选项:
输出到集合
此选项输出到新集合,并且在副本集的从节点上不可用。
out: <collectionName>
输出到具有操作的集合
注意
从版本 4.2 开始,MongoDB 弃用了:
- 用于创建 新的分片集合的 map-reduce 选项,以及对 map-reduce 使用分片选项。要输出到分片集合,请首先创建分片集合。MongoDB 4.2 还弃用了替换现有分片集合的功能。
仅当将已存在的集合传递给 out 时,此选项才可用。它不适用于副本集的从节点。
out: { <action>: <collectionName> [, db: <dbName>] [, sharded: <boolean> ] }
当使用操作输出到集合时,out 具有以下参数:
<action>:指定以下操作之一:replace如果存在具有
<collectionName>的集合,则替换<collectionName>的内容。merge如果输出集合已经存在,则将新结果与现有结果合并。如果现有文档与新结果具有相同的键,则覆盖该现有文档。
reduce如果输出集合已经存在,则将新结果与现有结果合并。如果现有文档与新结果具有相同的键,请将
reduce函数应用于新文档和现有文档,并使用结果覆盖现有文档。
db:可选。您希望 map-Reduce 操作写入其输出的数据库的名称。默认情况下,这将是与输入集合相同的数据库。
输出内联
在内存中执行 map-reduce 操作并返回结果。此选项是副本集的从节点上 out 的唯一可用选项。
out: { inline: 1 }
结果必须符合 BSON 文档的最大大小。
finalize 函数的要求
finalize 函数具有以下原型:
function(key, reducedValue) { ... return modifiedObject; }
finalize 函数接收 key 值和来自 reduce 函数的 reducedValue 作为其参数。请注意:
finalize函数不应 出于任何原因访问数据库。finalize函数应该是纯函数,或者对函数外部没有影响(即副作用)。finalize函数可以访问scope参数中定义的变量。
Map-Reduce 示例
本节中的示例包括不带自定义聚合表达式的聚合管道替代方案。有关使用自定义表达式的替代方案,请参阅Map-Reduce 到聚合管道转换示例。
创建一个包含以下文档的样本集合 orders:
db.orders.insertMany([ { _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" }, { _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" }, { _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" }, { _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }, { _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"}, { _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" }, { _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }, { _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" }, { _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }, { _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" } ])
返回每位客户的总价
针对 orders 集合执行 map-reduce 操作会按 cust_id 进行分组,并为每个 cust_id 计算 price 之和:
定义 Map 函数来处理每个输入文档:
在函数中,
this指的是 Map-Reduce 操作正在处理的文档。该函数将每个文档的
price映射到cust_id,并输出cust_id和price。
var mapFunction1 = function() { emit(this.cust_id, this.price); }; 使用两个参数
keyCustId和valuesPrices定义相应的 Reduce 函数:valuesPrices是一个数组,其元素是由 Map 函数发出并按keyCustId分组的price值。该函数将
valuesPrice数组缩减为其元素之和。
var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; 使用
mapFunction1Map 函数和reduceFunction1Reduce 函数对orders集合中的所有文档执行 Map-Reduce:db.orders.mapReduce( mapFunction1, reduceFunction1, { out: "map_reduce_example" } ) 此操作将结果输出到名为
map_reduce_example的集合。如果map_reduce_example集合已存在,该操作将用此 Map-Reduce 操作的结果替换其内容。查询
map_reduce_example集合以验证结果:db.map_reduce_example.find().sort( { _id: 1 } ) 该操作会返回以下文档:
{ "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Busby Bee", "value" : 125 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Don Quis", "value" : 155 }
聚合替代方案
利用可用的聚合管道操作符,您可以重写 Map-Reduce 操作,而无需定义自定义函数:
db.orders.aggregate([ { $group: { _id: "$cust_id", value: { $sum: "$price" } } }, { $out: "agg_alternative_1" } ])
查询
agg_alternative_1集合以验证结果:db.agg_alternative_1.find().sort( { _id: 1 } ) 该操作将返回以下文档:
{ "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Busby Bee", "value" : 125 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Don Quis", "value" : 155 }
提示
有关使用自定义聚合表达式的替代方案,请参阅 Map-Reduce 到聚合管道转换示例。
通过每款商品的平均数量计算订单和总数量
在以下示例中,您将看到针对 ord_date 值大于或等于 2020-03-01 的所有文档对 orders 集合执行的 Map-Reduce 操作。
示例中的操作:
按
item.sku字段分组,计算每个sku的订单数量和总订购量。计算每个
sku值的每个订单的平均数量,并将结果合并到输出集合中。
合并结果时,如果现有文档与新结果具有相同的键,则该操作将覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。
步骤示例:
定义 Map 函数来处理每个输入文档:
在函数中,
this指的是 Map-Reduce 操作正在处理的文档。对于每款商品,该函数将
sku与新对象value关联,该对象包含1的count和订单的商品qty,并发出sku(存储在key中)和value。
var mapFunction2 = function() { for (var idx = 0; idx < this.items.length; idx++) { var key = this.items[idx].sku; var value = { count: 1, qty: this.items[idx].qty }; emit(key, value); } }; 使用两个参数
keySKU和countObjVals定义相应的 Reduce 函数:countObjVals是一个数组,其元素是映射到分组keySKU值的对象,这些值由 Map 函数传递到 Reducer 函数。该函数将
countObjVals数组缩减为包含count和qty字段的单个对象reducedValue。在
reducedVal中,count字段包含各个数组元素中count字段的总和,而qty字段包含各个数组元素中qty字段的总和。
var reduceFunction2 = function(keySKU, countObjVals) { reducedVal = { count: 0, qty: 0 }; for (var idx = 0; idx < countObjVals.length; idx++) { reducedVal.count += countObjVals[idx].count; reducedVal.qty += countObjVals[idx].qty; } return reducedVal; }; 使用两个参数
key和reducedVal定义 finalize 函数。该函数会修改reducedVal对象以添加名为avg的计算字段并返回修改后的对象:var finalizeFunction2 = function (key, reducedVal) { reducedVal.avg = reducedVal.qty/reducedVal.count; return reducedVal; }; 使用
mapFunction2、reduceFunction2和finalizeFunction2函数对orders集合执行 Map-Reduce 操作:db.orders.mapReduce( mapFunction2, reduceFunction2, { out: { merge: "map_reduce_example2" }, query: { ord_date: { $gte: new Date("2020-03-01") } }, finalize: finalizeFunction2 } ); 此操作使用
query字段来仅选择ord_date大于或等于new Date("2020-03-01")的文档。然后它将结果输出到集合map_reduce_example2。如果
map_reduce_example2集合已存在,该操作会将现有内容与此 Map-Reduce 操作的结果合并。即,如果现有文档的键与新结果相同,则操作会覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。查询
map_reduce_example2集合以验证结果:db.map_reduce_example2.find().sort( { _id: 1 } ) 该操作会返回以下文档:
{ "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } } { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } } { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } } { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } } { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }
聚合替代方案
利用可用的聚合管道操作符,您可以重写 Map-Reduce 操作,而无需定义自定义函数:
db.orders.aggregate( [ { $match: { ord_date: { $gte: new Date("2020-03-01") } } }, { $unwind: "$items" }, { $group: { _id: "$items.sku", qty: { $sum: "$items.qty" }, orders_ids: { $addToSet: "$_id" } } }, { $project: { value: { count: { $size: "$orders_ids" }, qty: "$qty", avg: { $divide: [ "$qty", { $size: "$orders_ids" } ] } } } }, { $merge: { into: "agg_alternative_3", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } } ] )
$match阶段仅选择ord_date大于等于new Date("2020-03-01")的文档。$unwind阶段按items数组字段对文档进行分解,为每个数组元素输出一个文档。例如:{ "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 5, "price" : 2.5 }, "status" : "A" } { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "apples", "qty" : 5, "price" : 2.5 }, "status" : "A" } { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "oranges", "qty" : 8, "price" : 2.5 }, "status" : "A" } { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" } { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "pears", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 4, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-18T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 5, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-19T00:00:00Z"), "price" : 50, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" } ... $group阶段按items.sku分组,针对每个 sku 进行计算:qty字段。qty字段包含- 每个
items.sku的订购qty总计(参见$sum)。
orders_ids数组。orders_ids字段包含一个items.sku的不同顺序_id的数组(参见$addToSet)。
{ "_id" : "chocolates", "qty" : 15, "orders_ids" : [ 2, 5, 8 ] } { "_id" : "oranges", "qty" : 63, "orders_ids" : [ 4, 7, 3, 2, 9, 1, 10 ] } { "_id" : "carrots", "qty" : 15, "orders_ids" : [ 6, 9 ] } { "_id" : "apples", "qty" : 35, "orders_ids" : [ 9, 8, 1, 6 ] } { "_id" : "pears", "qty" : 10, "orders_ids" : [ 3 ] } $unwind阶段按items数组字段对文档进行分解,为每个数组元素输出一个文档。例如:{ "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 5, "price" : 2.5 }, "status" : "A" } { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "apples", "qty" : 5, "price" : 2.5 }, "status" : "A" } { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "oranges", "qty" : 8, "price" : 2.5 }, "status" : "A" } { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" } { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "pears", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 4, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-18T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" } { "_id" : 5, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-19T00:00:00Z"), "price" : 50, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" } ... $group阶段按items.sku分组,针对每个 sku 进行计算:qty字段。qty字段包含使用$sum为每个items.sku订购的qty总计。orders_ids数组。orders_ids字段包含使用$addToSet的items.sku的不同顺序_id的数组。
{ "_id" : "chocolates", "qty" : 15, "orders_ids" : [ 2, 5, 8 ] } { "_id" : "oranges", "qty" : 63, "orders_ids" : [ 4, 7, 3, 2, 9, 1, 10 ] } { "_id" : "carrots", "qty" : 15, "orders_ids" : [ 6, 9 ] } { "_id" : "apples", "qty" : 35, "orders_ids" : [ 9, 8, 1, 6 ] } { "_id" : "pears", "qty" : 10, "orders_ids" : [ 3 ] } $project阶段会重塑输出文档以镜像 Map-Reduce 的输出,使其具有两个字段_id和value。$project会:使用
$size将value.count设置为orders_ids数组的大小将
value.qty设置为输入文档中的qty字段。
{ "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } } { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } } { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } } { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } } { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } } 最后,
$merge将输出写入集合agg_alternative_3。如果现有文档的键_id与新结果相同,则操作会覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。查询
agg_alternative_3集合以验证结果:db.agg_alternative_3.find().sort( { _id: 1 } ) 该操作将返回以下文档:
{ "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } } { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } } { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } } { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } } { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }
提示
有关使用自定义聚合表达式的替代方案,请参阅 Map-Reduce 到聚合管道转换示例。
输出
The output of the db.collection.mapReduce() method is identical to that of the mapReduce command. See the Output section of the mapReduce command for information on the db.collection.mapReduce() output.
限制
db.collection.mapReduce() no longer supports afterClusterTime. As such, db.collection.mapReduce() cannot be associatd with causally consistent sessions.
更多信息
mapReduce命令