Docs 菜单
Docs 主页
/
MongoDB Manual
/ / /

MapReduce

在此页面上

  • 定义
  • 兼容性
  • 语法
  • 必需的访问权限
  • 限制
  • Map-Reduce 示例
  • 输出
  • 更多信息

注意

作为 Map-Reduce 替代方案的聚合管道

从MongoDB 5.0开始, map-reduce已弃用:

有关 map-reduce 的聚合管道替代方案的示例,请参阅:

mapReduce

mapReduce 命令允许您对集合运行 map-reduce 聚合操作。

提示

mongosh 中,该命令也可通过 mapReduce() 辅助方法运行。

辅助方法对 mongosh 用户来说很方便,但它们返回的信息级别可能与数据库命令不同。如果不追求方便或需要额外的返回字段,请使用数据库命令。

此命令可用于以下环境中托管的部署:

重要

M 0 、M 2和 M 5集群不支持此命令。 有关更多信息,请参阅不支持的命令。

注意

MongoDB 将忽略 verbose 选项。

从版本 4.2 开始,MongoDB 弃用了:

  • 用于创建新的分片集合的 map-reduce 选项,以及对 map-reduce 使用分片选项。要输出到分片集合,请首先创建分片集合。MongoDB 4.2 还弃用了替换现有分片集合的功能。

mapReduce 命令采用以下语法:

db.runCommand( {
mapReduce: <string>,
map: <string or JavaScript>,
reduce: <string or JavaScript>,
finalize: <string or JavaScript>,
out: <output>,
query: <document>,
sort: <document>,
limit: <number>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>,
collation: <document>,
maxTimeMS: <integer>,
writeConcern: <document>,
comment: <any>
} )

命令将以下字段作为参数:

字段
类型
说明

字符串

要对其执行 map-reduce 的集合的名称。此集合在由 map 函数处理之前将使用 query 进行过滤。

视图不支持 map-reduce 操作。

JavaScript 或字符串

一个 JavaScript 函数,用于将valuekey关联或“映射”,并发出key和值pair 。 您可以将函数指定为 BSON 类型 JavaScript(即 BSON 类型 13 )或字符串(即 BSON 类型 2 )。

有关更多信息,请参阅 map 函数的要求。

JavaScript 或字符串

一个 JavaScript 函数,用于将与特定key关联的所有values “减少”为单个对象。 您可以将函数指定为 BSON 类型 JavaScript(即 BSON 类型 13 )或字符串(即 BSON 类型 2 )。

有关更多信息,请参阅reduce 函数的要求。

字符串或文档

指定输出 map-reduce 操作结果的位置。可以输出到集合,也可以以内联方式返回结果。在副本集主节点上,可以输出到集合或以内联方式输出,但在辅助集合上,只能以内联方式输出。

有关详细信息,请参阅选项。

文档

可选。使用查询运算符指定选择标准,确定输入到 map 函数的文档。

文档

可选。对输入文档排序。此选项对于优化非常有用。例如,将排序键指定为与发出键相同,这样可减少 reduce 操作。排序键必须位于此集合的现有索引中。

数字

可选。指定输入到 map 函数的最大文档数。

JavaScript 或字符串

可选。 一个 JavaScript 函数,用于修改reduce函数之后的输出。 您可以将函数指定为 BSON 类型 JavaScript(即 BSON 类型 13 )或字符串(即 BSON 类型 2 )。

有关更多信息,请参阅 finalize 函数的要求。

文档

可选。指定可在 mapreducefinalize 函数中访问的全局变量。

布尔

可选。指定是否在计算 mapreduce 函数之间将中间数据转换为 BSON 格式。

默认值为 false

如果为 false

  • 在内部,MongoDB 将 map 函数发出的 JavaScript 对象转换为 BSON 对象。然后,在调用 reduce 函数时,这些 BSON 对象会转换回 JavaScript 对象。

  • map-reduce 操作将中间 BSON 对象放置在临时磁盘存储中。这允许对任意大的数据集执行 map-reduce 操作。

如果为 true

  • 在内部,map 函数期间发出的 JavaScript 对象仍保留为 JavaScript 对象。无需转换 reduce 函数的对象,这可以提高执行速度。

  • 对于映射器 emit() 函数的 key 参数少于 500,000 个的结果集,只能使用jsMode

布尔

可选。指定是否在结果信息中包含 timing 信息。将 verbose 设置为 true,以包含 timing 信息。

默认值为 false

此选项将被忽略。结果信息始终排除timing 信息。您可以通过在explain mapReduce"executionStats""allPlansExecution"verbosity 模式下运行带有 命令的 来查看计时信息。

布尔

可选。启用 mapReduce 可在操作过程中绕过文档验证。这样就可以插入不符合验证要求的文件。

如果输出选项设立为inline ,则不会进行文档验证。如果输出进入集合,则mapReduce 会遵守该集合的任何验证规则,并且不会插入任何无效文档,除非将bypassDocumentValidation 参数设立为 true。

文档

可选。

指定用于操作的排序规则

排序规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音符号规则。

排序规则选项的语法如下:

collation: {
locale: <string>,
caseLevel: <boolean>,
caseFirst: <string>,
strength: <int>,
numericOrdering: <boolean>,
alternate: <string>,
maxVariable: <string>,
backwards: <boolean>
}

指定排序规则时,locale 字段为必填字段;所有其他排序规则字段均为可选字段。有关字段的说明,请参阅排序规则文档

如果未指定排序规则,但集合具有默认排序规则(请参阅 db.createCollection()),则操作将使用为集合指定的排序规则。

如果没有为收集或操作指定排序规则,MongoDB 将使用先前版本中使用的简单二进制比较来进行字符串比较。

您不能为一个操作指定多个排序规则。例如,您不能为每个字段指定不同的排序规则,或者如果执行带排序的查找,则不能使用一种排序规则进行查找而另一种排序规则进行排序。

版本 3.4 中的新增功能

maxTimeMS

non-negative integer

可选。

指定时间限制(以毫秒为单位)。如果您未指定 maxTimeMS 值,操作将不会超时。如果值为 0 ,则显式指定默认无限制行为。

MongoDB 使用与 db.killOp() 相同的机制终止超过分配的时间限制的操作。MongoDB 仅在指定的中断点之一中终止操作。

文档

可选。一种文档,表示输出到集合时要使用的写关注。省略以使用默认的写关注。

comment

any

可选。用户提供的待附加到该命令的注释。设置后,该注释将与该命令的记录一起出现在以下位置:

注释可以是任何有效的 BSON 类型(字符串、整型、对象、数组等)。

下面是 mapReduce 命令的原型用法:

var mapFunction = function() { ... };
var reduceFunction = function(key, values) { ... };
db.runCommand(
{
mapReduce: <input-collection>,
map: mapFunction,
reduce: reduceFunction,
out: { merge: <output-collection> },
query: <query>
}
)

注意

MongoDB 中的 JavaScript

尽管 mapReduce 使用 JavaScript,但与 MongoDB 进行的大多数交互并不使用 JavaScript,而是使用采用交互应用程序语言的惯用驱动程序

map 函数负责将每个输入文档转换为零个或多个文档。它可以访问 scope 参数中定义的变量,并具有以下原型:

function() {
...
emit(key, value);
}

map 函数具有以下要求:

  • map 函数中,将当前文档引用为函数内的 this

  • map 函数不应 出于任何原因访问数据库。

  • map 函数应该是纯函数,或者对函数外部没有影响(即副作用)。

  • map 函数可以选择调用 emit(key,value) 任意次,以创建将 keyvalue 关联的输出文档。

以下 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 函数具有以下原型:

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 ] )

finalize 函数具有以下原型:

function(key, reducedValue) {
...
return modifiedObject;
}

finalize 函数接收 key 值和来自 reduce 函数的 reducedValue 作为其参数。请注意:

  • finalize 函数不应 出于任何原因访问数据库。

  • finalize 函数应该是纯函数,或者对函数外部没有影响(即副作用)。

  • finalize 函数可以访问 scope 参数中定义的变量。

您可以为 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 文档的最大大小

如果您的 MongoDB 部署强制进行身份验证,则执行 mapReduce 命令的用户必须拥有以下权限操作:

带有 {out : inline} 输出选项的 map-reduce:

输出到集合时使用 replace 操作执行 Map-reduce:

输出到集合时使用 mergereduce 操作执行 Map-reduce:

readWrite 内置角色为执行 map-reduce 聚合提供了必要的权限。

mapReduce 命令不再支持 afterClusterTime。因此,mapReduce 不能与因果一致的会话相关联。

mongosh 中,db.collection.mapReduce() 方法是 mapReduce 命令的封装器。以下示例使用 db.collection.mapReduce() 方法:

本节中的示例包括不带自定义聚合表达式的聚合管道替代方案。有关使用自定义表达式的替代方案,请参阅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 之和:

  1. 定义 Map 函数来处理每个输入文档:

    • 在函数中,this 指的是 Map-Reduce 操作正在处理的文档。

    • 该函数将每个文档的 price 映射到 cust_id,并输出 cust_idprice

    var mapFunction1 = function() {
    emit(this.cust_id, this.price);
    };
  2. 使用两个参数 keyCustIdvaluesPrices 定义相应的 Reduce 函数:

    • valuesPrices 是一个数组,其元素是由 Map 函数发出并按 keyCustId 分组的 price 值。

    • 该函数将 valuesPrice 数组缩减为其元素之和。

    var reduceFunction1 = function(keyCustId, valuesPrices) {
    return Array.sum(valuesPrices);
    };
  3. 使用 mapFunction1 Map 函数和 reduceFunction1 Reduce 函数对 orders 集合中的所有文档执行 Map-Reduce:

    db.orders.mapReduce(
    mapFunction1,
    reduceFunction1,
    { out: "map_reduce_example" }
    )

    此操作将结果输出到名为 map_reduce_example 的集合。如果 map_reduce_example 集合已存在,该操作将用此 Map-Reduce 操作的结果替换其内容。

  4. 查询 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" }
])
  1. $group 阶段按 cust_id 分组并计算 value 字段(另见 $sum)。value 字段包含每个 cust_idprice 总额。

    此阶段将以下文档输出到下一阶段:

    { "_id" : "Don Quis", "value" : 155 }
    { "_id" : "Ant O. Knee", "value" : 95 }
    { "_id" : "Cam Elot", "value" : 60 }
    { "_id" : "Busby Bee", "value" : 125 }
  2. 然后,$out 将输出写入集合 agg_alternative_1。或者,你可以使用 $merge 代替 $out

  3. 查询 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 操作。

示例中的操作:

  1. item.sku 字段分组,计算每个 sku 的订单数量和总订购量。

  2. 计算每个 sku 值的每个订单的平均数量,并将结果合并到输出集合中。

合并结果时,如果现有文档与新结果具有相同的键,则该操作将覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。

步骤示例:

  1. 定义 Map 函数来处理每个输入文档:

    • 在函数中,this 指的是 Map-Reduce 操作正在处理的文档。

    • 对于每款商品,该函数将 sku 与新对象 value 关联,该对象包含 1count 和订单的商品 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);
    }
    };
  2. 使用两个参数 keySKUcountObjVals 定义相应的 Reduce 函数:

    • countObjVals 是一个数组,其元素是映射到分组 keySKU 值的对象,这些值由 Map 函数传递到 Reducer 函数。

    • 该函数将 countObjVals 数组缩减为包含 countqty 字段的单个对象 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;
    };
  3. 使用两个参数 keyreducedVal 定义 finalize 函数。该函数会修改 reducedVal 对象以添加名为 avg 的计算字段并返回修改后的对象:

    var finalizeFunction2 = function (key, reducedVal) {
    reducedVal.avg = reducedVal.qty/reducedVal.count;
    return reducedVal;
    };
  4. 使用 mapFunction2reduceFunction2finalizeFunction2 函数对 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 操作的结果合并。即,如果现有文档的键与新结果相同,则操作会覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。

  5. 查询 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" } }
] )
  1. $match 阶段仅选择 ord_date 大于等于 new Date("2020-03-01") 的文档。

  2. $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" }
    ...
  3. $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 ] }
  4. $project 阶段会重塑输出文档以镜像 Map-Reduce 的输出,使其具有两个字段 _idvalue$project 会:

  5. $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" }
    ...
  6. $group 阶段按 items.sku 分组,针对每个 sku 进行计算:

    • qty 字段。qty 字段包含使用 $sum 为每个 items.sku 订购的 qty 总计。

    • orders_ids 数组。orders_ids 字段包含使用 $addToSetitems.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 ] }
  7. $project 阶段会重塑输出文档以镜像 Map-Reduce 的输出,使其具有两个字段 _idvalue$project 会:

    • 使用 $sizevalue.count 设置为 orders_ids 数组的大小

    • value.qty 设置为输入文档中的 qty 字段。

    • 使用 $divide$sizevalue.avg 设置为每个订单的平均数量

    { "_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 } }
  8. 最后,$merge 将输出写入集合 agg_alternative_3。如果现有文档的键 _id 与新结果相同,则操作会覆盖现有文档。如果没有具有相同键的现有文档,操作将插入该文档。

  9. 查询 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 到聚合管道转换示例

有关更多信息和示例,请参阅Map-Reduce页面和执行增量 Map-Reduce。

如果将 out 参数设置为将结果写入集合,mapReduce 命令将以如下形式返回文档:

{ "result" : "map_reduce_example", "ok" : 1 }

如果将 out 参数设置为以内联方式输出结果,mapReduce 命令将返回以下格式的文档:

{
"results" : [
{
"_id" : <key>,
"value" :<reduced or finalizedValue for key>
},
...
],
"ok" : <int>
}
mapReduce.result

对于发送到集合的输出,该值为以下任一项:

  • 如果 out 未指定数据库名称,则为集合名称提供字符串,或者

  • 包含 dbcollection 字段的文档,如果 out 同时指定了数据库和集合名称。

mapReduce.results

对于以内联方式写入的输出,生成文档的数组。每个生成文档都包含两个字段:

  • _id 字段包含 key 值,

  • value 字段包含关联的 key 的减少值或最终值。

mapReduce.ok

值为 1 表示 mapReduce 命令运行成功。值为 0 表示出现错误。

除了上述特定的命令返回字段外,db.runCommand() 还包括其他信息:

  • 对于副本集:$clusterTimeoperationTime

  • 对于分片集群:operationTime$clusterTime

有关这些字段的详细信息,请参阅 db.runCommand Response

后退

distinct