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 还弃用了替换现有分片集合的功能。

该命令具有以下语法:

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“reduce”(简化)为单个对象。您可以将函数指定为 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信息。您可以通过在"executionStats""allPlansExecution" verbosity模式下运行explainmapReduce命令来查看计时信息。

布尔

可选。启用 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 将使用先前版本中使用的简单二进制比较来进行字符串比较。

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

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