Docs 菜单
Docs 主页
/ /

$merge(聚合阶段)

注意

本页介绍了 $merge 阶段,它将聚合管道结果输出到集合中。对于 $mergeObjects 操作符,它将文档合并为单个文档,请参阅“$mergeObjects”。

$merge

聚合管道的结果写入指定的集合。$merge 操作符必须是管道的最后一个阶段。

$merge阶段:

  • 可以输出到相同或不同数据库中的集合。

  • 可以输出到正在聚合的同一集合。有关详细信息,请参阅 输出到正在聚合的同一集合。

  • 聚合管道使用 $merge$out 阶段时,请考虑以下几点:

    • 从MongoDB 5.0 开始,如果集群中的所有节点都将 featureCompatibilityVersion 设置为 5.0 或更高,且读取偏好允许读取从节点,那么具有 $merge 阶段的管道就可以在副本集从节点上运行。

      • $merge$out 阶段在从节点上运行,但写入操作被发送到节点。

      • 并非所有驱动程序版本都支持发送到辅助节点的 $merge 操作。有关详细信息,请参阅驱动程序文档。

    • 在早期的 MongoDB 版本中,具有 $out$merge 阶段的管道始终在主节点上运行,并且不考虑读取偏好。

  • 如果输出集合不存在,则创建一个新集合。

  • 可将结果(插入新文档、合并文档、替换文档、保留现有文档、操作失败、使用自定义更新管道处理文档)并入现有集合。

  • 可输出到分片集合。输入集合也可以是分片的。

有关与同样将聚合结果输出到集合中的 $out 阶段的比较,请参阅 $merge$out 比较

注意

按需物化视图

$merge 可以将管道结果纳入现有的输出集合,而不是执行对集合完全替换。此功能允许用户创建按需物化视图,在管道运行时增量更新输出集合的内容。

有关此用例的更多信息,请参阅《按需物化视图》以及本页上的示例。

物化视图与只读视图是分开的。有关创建只读视图的信息,请参阅只读视图。

可以使用 $merge 查找托管在以下环境中的部署:

$merge 通过以下语法实现:

{ $merge: {
into: <collection> -or- { db: <db>, coll: <collection> },
on: <identifier field> -or- [ <identifier field1>, ...], // Optional
let: <variables>, // Optional
whenMatched: <replace|keepExisting|merge|fail|pipeline>, // Optional
whenNotMatched: <insert|discard|fail> // Optional
} }

例如:

{ $merge: { into: "myOutput", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }

如果使用了 $merge 的所有默认选项(包括写入同一数据库中的集合),则可使用简化形式:

{ $merge: <collection> } // Output collection is in the same database

$merge 阶段采用包含以下字段的文档:

字段
说明

输出集合。指定以下任一项:

  • 将集合名称作为字符串输出到运行聚合的同一数据库中的集合。例如:

    into: "myOutput"

  • 文档中的数据库和集合名称,以输出到指定数据库中的集合。例如:

    into: { db:"myDB", coll:"myOutput" }

如果输出集合不存在,$merge 将创建集合:

  • 对于副本集或独立运行的实例,如果输出数据库不存在,$merge 还会创建该数据库。

  • 对于分片集群,指定的输出数据库必须已经存在。

输出集合可以是分片集合。

可选。 充当文档唯一标识符的一个或多个字段。 该标识符确定结果文档是否与输出集合中的现有文档匹配。指定以下任一项:

  • 字符串形式的单个字段名称。例如:

    on: "_id"

  • 数组中的字段组合。例如:

    on: [ "date", "customerId" ]
    The order of the fields in the array does not matter, and you cannot specify the same field multiple times.

对于指定的一个或多个字段:

  • 聚合结果文档必须包含 on 中指定的字段,除非 on 字段是 _id 字段。如果结果文档中缺少 _id 字段,MongoDB 会自动添加。

  • 指定的一个或多个字段不能包含 null 或数组值。

$merge 需要一个唯一索引,其中包含对应于 on 标识符字段的键。尽管索引键规范的顺序并不重要,但唯一索引必须仅包含 on 字段作为其键。

  • 索引必须与聚合具有相同的排序规则

  • 唯一索引可为稀疏索引。

  • 唯一索引不能为部分索引。

  • 对于已存在的输出集合,相应的索引必须已存在。

on 的默认值取决于输出集合:

  • 如果输出集合不存在,则 on 标识符必须是且默认为_id 字段。系统会自动创建相应的唯一 _id索引。

    • 要为不存在的集合使用不同的标识符字段,可以先在所需字段上创建唯一索引来创建集合。有关示例,请参阅有关不存在的输出集合的部分。

  • 如果现有输出集合未进行分片,on 标识符则默认为 _id 字段。

  • 如果现有输出集合是分片的集合,则 on 标识符默认为所有分片键字段和_id 字段。如果指定不同的 on 标识符,则 on 必须包含所有分片键字段。

可选。$merge 的行为(如果结果文档和集合中的现有文档针对指定 on 字段的值相同)。

您可以指定以下任一项:

  • 预定义的动作字符串之一:

    操作
    说明

    将输出集合中的现有文档替换为匹配的结果文档。

    执行替换时,替换文档不能导致修改 _id 值,或者,如果输出集合是分片的,则不能修改分片键值。否则,操作将产生错误。

    为避免此错误,如果 on字段不包含 _id字段,删除聚合结果中的_id 字段以避免错误,例如在前面添加$unset 阶段等。

    合并匹配文档(类似于 $mergeObjects 操作符)。

    • 如果结果文档包含不在现有文档中的字段,请将这些新字段添加到现有文档中。

    • 如果结果文档包含现有文档中的字段,则将现有的字段值替换为来自结果文件的值。

    例如,如果输出集合中有以下文档:

    { _id: 1, a: 1, b: 1 }

    且聚合结果中以下文档:

    { _id: 1, b: 5, z: 1 }

    则合并文档为:

    { _id: 1, a: 1, b: 5, z: 1 }

    执行合并时,合并的文档不会导致分片键值(如果输出集合已分片)或 _id 值发生修改。否则,操作将产生错误。

    为避免此错误,如果 on字段不包含 _id字段,删除聚合结果中的_id 字段以避免错误,例如在前面添加$unset 阶段等。

    停止聚合操作并使其失败。先前文档对输出集合的任何更改都不会恢复。

  • 用于更新集合中文档的聚合管道。

    [ <stage1>, <stage2> ... ]

    该管道只能由以下阶段组成:

    管道无法修改 on 字段的值。示例,如果您要对字段month 进行匹配,管道无法修改 month字段。

    whenMatched pipeline 可以使用 $<field> 直接访问输出集合中现有文档的字段。

    要访问聚合结果文档中的字段,请使用以下任一项:

    • 用于访问权限字段的内置$$new 变量。 具体来说,为 $$new.<field>$$new仅当省略 let 规范时, 变量才可用。

    • let字段中的用户定义变量。

      $$<variable_name> 形式指定双美元符号 ($$) 前缀以及变量名称。例如,$$year。如果将此变量设为文档,则还能以 $$<variable_name>.<field> 形式包含文档字段。例如,$$year.month

      有关更多示例,请参阅使用变量自定义合并。

可选。 指定在 whenMatched管道中使用的变量。

指定包含变量名和值表达式的文档:

{ <variable_name_1>: <expression_1>,
...,
<variable_name_n>: <expression_n> }

如果未指定,默认为{ new: "$$ROOT" } (请参阅ROOT )。whenMatched管道可以访问权限 $$new变量。

要访问权限whenMatched管道中的变量,请执行以下操作:

$$<variable_name> 形式指定双美元符号 ($$) 前缀以及变量名称。例如,$$year。如果将此变量设为文档,则还能以 $$<variable_name>.<field> 形式包含文档字段。例如,$$year.month

有关示例,请参阅使用变量自定义合并。

可选。$merge 的行为(如果结果文档与输出集合中的现有文档不匹配)。

您可以指定以下预定义的动作字符串之一:

操作
说明

"insert" (Default)

将此文档插入输出集合。

丢弃文档。具体来说,$merge 不会将文档插入到输出集合。

停止聚合操作并使其失败。已写入输出集合的所有更改均不会恢复。

如果聚合管道结果中的文档中不存在 _id 字段,则 $merge 阶段会自动生成该字段。

例如,在以下聚合管道中,$project 从传递给 $merge 的文档中排除 _id 字段。当 $merge 将这些文档写入 "newCollection" 时,$merge 会生成一个新的 _id 字段和值。

db.movies.aggregate( [
{ $project: { _id: 0 } },
{ $merge : { into : "newCollection" } }
] )

如果指定的输出集合不存在,则 $merge 操作会创建一个新集合。

  • $merge 将第一个文档写入集合时,输出集合就已创建,且立即可见。

  • 如果聚合失败,则在错误发生之前,$merge 完成的任何写入都不会回滚。

注意

对于副本集或独立运行的实例,如果输出数据库不存在,$merge 还会创建该数据库。

对于分片集群,指定的输出数据库必须已经存在。

如果输出集合不存在,$merge 要求将 on 标识符作为 _id 字段。要为不存在的集合使用不同的 on 字段值,可以先在所需字段上创建唯一索引来创建集合。例如,如果输出集合 newDailyCommentCount 不存在,而您想指定 commentDate 字段作为 on 标识符:

db.newDailyCommentCount.createIndex(
{ commentDate: 1 }, { unique: true } )
db.comments.aggregate( [
{ $match: { date: { $gte: new Date("2002-01-01"),
$lt: new Date("2002-02-01") } } },
{ $group: { _id: { $dateToString: { format: "%Y-%m-%d",
date: "$date" } }, count: { $sum: 1 } } },
{ $project: { _id: 0, commentDate: { $toDate: "$_id" },
count: 1 } },
{ $merge : { into : "newDailyCommentCount",
on: "commentDate" } }
] )

$merge 阶段可输出到分片集合。当输出集合为分片集合时,$merge 使用 _id 字段和所有分片键字段作为默认的on标识符。如果覆盖此默认值,on 标识符则须包含所有分片键字段:

{ $merge: {
into: "<shardedColl>" or { db:"<sharding enabled db>", coll: "<shardedColl>" },
on: [ "<shardkeyfield1>", "<shardkeyfield2>",... ], // Shard key fields and any additional fields
let: <variables>, // Optional
whenMatched: <replace|keepExisting|merge|fail|pipeline>, // Optional
whenNotMatched: <insert|discard|fail> // Optional
} }

例如,使用 sh.shardCollection() 方法创建新的分片集合 moviesByYearAndRating,其中 rated 字段作为分片键。

sh.shardCollection(
"sample_mflix.moviesByYearAndRating", // Namespace of the collection to shard
{ rated: 1 }, // Shard key
);

moviesByYearAndRating 集合将包含按年份(year 字段)和内容评级(分片键)列出的电影统计信息文档;具体来说,on 标识符是 ["year", "rated"](字段的顺序无关紧要)。因为 $merge 需要唯一索引,并且具有对应 on 标识符字段的键,所以创建唯一索引(字段的顺序无关紧要):[1]

db.moviesByYearAndRating.createIndex(
{ rated: 1, year: 1 }, { unique: true } )

通过创建分片集合 moviesByYearAndRating 和唯一索引后,可以使用 $merge 将聚合结果输出到此集合,匹配 [ "year", "rated" ],如下例所示:

db.movies.aggregate( [
{ $match: { rated: { $ne: null }, year: { $ne: null } } },
{ $group: {
_id: { year: "$year", rated: "$rated" },
movieCount: { $sum: 1 } } },
{ $project: { _id: 0, year: "$_id.year", rated: "$_id.rated",
movieCount: 1 } },
{ $merge: { into: "moviesByYearAndRating",
"on": [ "year", "rated" ], whenMatched: "replace",
whenNotMatched: "insert" } }
] )
[1] 在传递 { unique: true } 选项时,sh.shardCollection() 方法还可以在分片键上创建唯一索引,前提是:分片键基于范围,集合为空,并且分片键上的唯一索引尚不存在。在前面的示例中,由于 on 标识符是分片键和另一个字段,因此需要单独的操作来创建相应的索引。

$merge 可以替换输出集合中的现有文档,前提是聚合结果包含根据 on 规范匹配的一个或多个文档。因此,如果聚合结果包括集合中所有现有文档的匹配文档,并且您为 whenMatched 指定“replace”,则 $merge 可以替换现有集合中的所有文档。

但是,在不考虑聚合结果的情况下,如果要替换现有集合,请使用 $out

如果 $merge 导致现有文档的 _id 值发生变化,则会出现 $merge 错误。

提示

为避免出现此错误,如果 on 字段不包括 _id 字段,则应删除聚合结果中的 _id 字段,以避免出现错误,如前面的 $unset 阶段等。

此外,对于分片集合,如果 $merge 导致现有文档的分片键值发生变化,也会生成错误。

在错误发生之前,$merge 完成的任何写入都不会回滚。

如果 $merge on 字段使用的唯一索引在聚合过程中被删除,则无法保证会终止聚合。如果继续聚合,则无法保证文档中没有重复的 on 字段值。

如果 $merge 尝试写入的文档违反输出集合上的任何唯一索引,则操作会产生错误。例如:

  • 插入一个不匹配的文档,该文档违反了唯一索引(非 on 字段的索引)。

  • 失败,如果集合中有匹配的文档。具体而言,该操作会尝试插入一个违反了 on 字段唯一索引的匹配文档。

  • 将现有文档替换为违反了唯一索引而不是 on 字段的索引的新文档。

  • 合并导致文档违反唯一索引而不是 on 字段的索引的匹配文档。

如果您的集合使用模式验证并将 validationAction 设置为 error,则插入无效文档或使用 $merge 更新具有无效值的文档会抛出 MongoServerError,并且该文档不会写入目标集合。如果有多个无效文档,则只有出现的第一个无效文档会引发错误。所有有效文档都写入目标集合,所有无效文档都会写入失败。

$merge 在满足以下所有条件时,将文档直接插入到输出集合中:

随着$merge的引入,MongoDB 提供两个阶段,即 $merge$out,用于将聚合管道的结果写入集合:

$merge
  • 可以输出到相同或不同数据库中的集合。

  • 可以输出到相同或不同数据库中的集合。

  • 如果输出集合不存在,则创建一个新集合。

  • 如果输出集合不存在,则创建一个新集合。

  • 完全替换已存在的输出集合。

  • 可输出到分片集合。输入集合也可以是分片的。

  • 无法输出到分片集合。但是,可以对输入集合进行分片。

  • 对应 SQL 语句:

    • MERGE.

    • INSERT INTO T2 SELECT FROM T1.

    • SELECT INTO T2 FROM T1.

    • 创建/刷新物化视图。

  • 对应 SQL 语句:

    • INSERT INTO T2 SELECT FROM T1.

    • SELECT INTO T2 FROM T1.

警告

$merge 输出到正在聚合的同一集合时,文档可能会被多次更新,或者操作可能会导致无限循环。当$merge 执行的更新更改了磁盘上存储的文档的物理位置时,就会出现此行为。当文档的物理位置发生变化时,$merge 可能会将其视为全新文档,从而导致更多更新。有关此行为的更多信息,请参阅万圣节问题。

$merge 可以输出到正在聚合的同一集合。此外,也可以输出到管道的其他阶段中出现的集合,例如 $lookup

限制
说明

聚合管道不能在事务中使用 $merge

聚合管道不能使用 $merge 输出到时间序列集合。

Separate from materialized view

视图定义不能包括 $merge 阶段。如果视图定义包含嵌套管道(例如,视图定义包含 $facet 阶段),则此 $merge 阶段限制也适用于嵌套管道。

$lookup 阶段

$lookup 阶段的嵌套管道不能包含 $merge 阶段。

$facet 阶段

$facet 阶段的嵌套管道不能包含 $merge 阶段。

$unionWith 阶段

$unionWith 阶段的嵌套管道不能包含 $merge 阶段。

"linearizable" 读关注 (read concern)

$merge 阶段不能与读关注 "linearizable" 一起使用。换言之,如果您为 db.collection.aggregate() 指定 "linearizable" 读关注,则不能将 $merge 阶段包括在管道中。

本页上的示例使用sample_mflix示例数据集中的数据。有关如何将此数据集加载到自管理MongoDB 部署中的详细信息,请参阅加载示例数据集。如果对示例数据库进行了任何修改,则可能需要删除并重新创建数据库才能运行本页上的示例。

如果输出集合不存在,则 $merge 会创建该集合。

注意

对于副本集或独立部署,如果输出数据库不存在,$merge 也会创建该数据库。

对于分片集群部署,指定输出数据库必须已经存在。

你可以使用 $group$merge 阶段来创建一个 movieRatingSummary 集合,用于按上映年份和内容分级汇总获得高度评价的影片:

db.movies.aggregate( [
{ $match: { metacritic: 100, rated: { $ne: null },
year: { $lte: 1972 } } },
{ $group: { _id: { year: "$year", rated: "$rated" },
count: { $sum: 1 } } },
{ $merge : { into: "movieRatingSummary", on: "_id",
whenMatched: "replace", whenNotMatched: "insert" } }
] )

该管道使用以下阶段:

  • $match 用于筛选通过 1972 年发行且具有内容评级的广受好评电影的阶段

  • $group 阶段以按 yearrated 对这些影片进行分组

  • $merge 阶段将前一个 $group 阶段的输出写入 sample_mflix 数据库中的 movieRatingSummary 集合。

要查看新 movieRatingSummary 集合中的文档:

db.movieRatingSummary.find().sort(
{ _id: 1 } )
[
{ _id: { year: 1939, rated: 'PASSED' }, count: 1 },
{ _id: { year: 1962, rated: 'PG' }, count: 1 },
{ _id: { year: 1963, rated: 'PG' }, count: 1 },
{ _id: { year: 1970, rated: 'R' }, count: 1 },
{ _id: { year: 1972, rated: 'R' }, count: 1 }
]

要更新上一个示例中的 movieRatingSummary 集合,使其包含从 1963 年起获得高度评价的影片,该聚合管道使用以下阶段:

  • $match 阶段用于查找所有包含 metacritic: 100(内容分级)且上映年份晚于或等于 1963 的影片。

  • $group 阶段以按 yearrated 对这些影片进行分组。

  • $merge 将结果集写入 movieRatingSummary 集合,并用替换文档中具有相同 _id 值的记录。对于集合中没有匹配项的文档,$merge 会插入新文档。

db.movies.aggregate( [
{ $match: { metacritic: 100, rated: { $ne: null },
year: { $gte: 1963 } } },
{ $group: { _id: { year: "$year", rated: "$rated" },
count: { $sum: 1 } } },
{ $merge : { into: "movieRatingSummary", on: "_id",
whenMatched: "replace", whenNotMatched: "insert" } }
] )

聚合运行结束后,查看movieRatingSummary集合中的文档:

db.movieRatingSummary.find().sort(
{ _id: 1 } )
[
{ _id: { year: 1939, rated: 'PASSED' }, count: 1 },
{ _id: { year: 1962, rated: 'PG' }, count: 1 },
{ _id: { year: 1963, rated: 'PG' }, count: 1 },
{ _id: { year: 1970, rated: 'R' }, count: 1 },
{ _id: { year: 1972, rated: 'R' }, count: 1 },
{ _id: { year: 1982, rated: 'R' }, count: 1 },
{ _id: { year: 2014, rated: 'R' }, count: 1 }
]

为确保 $merge 不会覆盖集合中的现有数据,请将 whenMatched 设置为 keepExistingfail

sample_mflix 数据库中的集合 movieArchive 包含各发行年份备受好评电影的历史记录。

movieArchive 集合具有针对 year 字段的唯一索引。每个发布年份至多有一条记录:

db.movieArchive.createIndex(
{ year: 1 }, { unique: true } )

此聚合管道使用 movies 集合中的数据更新 movieArchive 集合,以包含从 1963 年开始以来广受好评的电影。该管道使用以下阶段:

  • $match 阶段查找所有具有 metacritic: 100、内容分级和 year >= 1963 的电影。

  • $group 阶段按 year 对电影标题进行分组。

  • $project 阶段以抑制 _id 字段并将 year 提升到顶级字段。将文档传递给 $merge 时,$merge 会自动为这些文档生成一个新的 _id 字段。

  • $merge 将结果集写入 movieArchive

    $merge 阶段会在 year 字段匹配文档,并在匹配时触发失败。也就是说,如果某个发行年份的文档已经存在,$merge 将会报错。

db.movies.aggregate( [
{ $match: { metacritic: 100, rated: { $ne: null },
year: { $gte: 1963 } } },
{ $group: { _id: "$year",
titles: { $push: "$title" } } },
{ $project: { _id: 0, year: "$_id", titles: 1 } },
{ $merge : { into: "movieArchive", on: "year",
whenMatched: "fail" } }
] )

如果 movieArchive 集合中已存在该范围 1963 年到 2014 年期间任意年份的文档,则该聚合会因重复键错误而失败。然而,该管道不会回滚在发生错误之前已插入的任何文档。

如果为匹配文档指定 keepExisting,则聚合不会影响匹配文档,也不会出现重复键错误。同样,如果指定 replace,操作不会失败,但会替换现有文档。

默认情况下,如果聚合结果中的文档与集合中的文档相匹配,$merge 阶段就会合并文档。

您可以使用 $merge 来合并来自 movies 集合和 comments 集合的结果,从而创建一个新的集合 yearlyStats

要创建 yearlyStats 集合,请运行以下管道:

db.movies.aggregate( [
{ $match: { metacritic: 100, rated: { $ne: null },
year: { $gte: 1970, $lte: 1972 } } },
{ $group: { _id: "$year", movieCount: { $sum: 1 } } },
{ $merge : { into: "yearlyStats", on: "_id",
whenMatched: "merge", whenNotMatched: "insert" } }
])
第一个阶段:
$match 阶段筛选出具有内容分级、在 1970 年到 1972 年期间发布的广受好评的电影 (metacritic: 100)。
第二阶段:
$group 阶段按 year 分组,并将电影计入新的 movieCount 字段。
第三阶段:
$merge 阶段会将这些文档写入同一数据库中的 yearlyStats 集合。如果此阶段在集合中找到与 _id 字段匹配的现有文档,此阶段则会合并这些匹配的文档。否则,此阶段会插入该文档。对于初始创建,所有文档都不应匹配。

要查看集合中的文档,运行以下操作:

db.yearlyStats.find().sort( { _id: 1 } )
[
{ _id: 1970, movieCount: 1 },
{ _id: 1972, movieCount: 1 }
]

同样,针对 comments 集合运行以下聚合管道,将评论计数合并到 yearlyStats 集合中。

db.comments.aggregate( [
{ $match: { date: { $gte: new Date("1970-01-01"),
$lt: new Date("1973-01-01") } } },
{ $group: { _id: { $year: "$date" },
commentCount: { $sum: 1 } } },
{ $merge : { into: "yearlyStats", on: "_id",
whenMatched: "merge", whenNotMatched: "insert" } }
])
第一个阶段:
$match 阶段:筛选 1970 年到 1972 年之间发布的评论。
第二阶段:
$group 阶段按从注释 date 中提取的年份进行分组,并将注释计数到新的 commentCount 字段中。
第三阶段:
$merge 阶段将这些文档写入同一数据库中的 yearlyStats 集合。如果该阶段在集合中找到与 _id 字段(年度)匹配的现有文档,则该阶段会合并匹配文档。否则,该阶段会插入文档。

要在合并数据后查看 yearlyStats 集合中的文档,请运行以下操作:

db.yearlyStats.find().sort( { _id: 1 } )
[
{ _id: 1970, movieCount: 1, commentCount: 889 },
{ _id: 1971, commentCount: 825 },
{ _id: 1972, movieCount: 1, commentCount: 863 }
]

当文档匹配时,$merge 可以使用自定义更新管道whenMatched 管道可以包含以下阶段:

集合 monthlyCommentTotals 追踪每月评论的累计数量。

sample_mflix.comments 集合每天都有新评论。以下聚合管道使用当天的评论计数更新每月总数:

db.comments.aggregate([
{ $match: { date: { $gte: new Date("1970-01-15"),
$lt: new Date("1970-01-16") } } },
{ $group: { _id: { $dateToString: { format: "%Y-%m",
date: "$date" } }, count: { $sum: 1 } } },
{ $merge: {
into: "monthlyCommentTotals",
on: "_id",
whenMatched: [
{ $addFields: {
count: { $add: [ "$count", "$$new.count" ] }
} }
],
whenNotMatched: "insert"
} }
])
第一个阶段:
$match 阶段可查找发布于 1970 年 1 月 15 日的所有评论。
第二阶段:
$group 阶段按年月对匹配的评论进行分组并对其进行计数。
第三阶段:

$merge 阶段将文档写入 monthlyCommentTotals 集合。如果该阶段在集合中找到与 _id 字段匹配的现有文档,则该阶段会使用管道将当天的 count 添加到现有的月度总数中。

  • 该管道不能直接访问结果文档中的字段。要访问结果文档中的 count 字段,管道使用 $$new 变量;即 $$new.count

  • 此管道可直接访问集合中现有文档的 count 字段;即 $count

生成的文档将替换现有文档。

要在合并操作后查看 monthlyCommentTotals 集合中的文档,请运行以下操作:

db.monthlyCommentTotals.find()
[ { _id: '1970-01', count: 71 } ]

您可以在 $merge 阶段 whenMatched 字段中使用变量。必须先定义变量,然后才能使用。

使用以下一种或两种方式定义变量:

要在 whenMatched 中使用变量,请执行以下操作:

$$<variable_name> 形式指定双美元符号 ($$) 前缀以及变量名称。例如,$$year。如果将此变量设为文档,则还能以 $$<variable_name>.<field> 形式包含文档字段。例如,$$year.month

下面的标签页展示在合并阶段、聚合命令或两者中定义变量时的行为。

您可以在 $merge 阶段 let 中定义变量,并在 whenMatched 字段中使用变量。

如下示例:

  • 使用来自 movies 集合中的一部电影来初始化 movieDetails 集合

  • 运行 aggregate 命令,在 $merge let 中定义 year 变量,并使用 whenMatched 将年份添加到 movieDetails 中。

  • retrieves the movieDetails document

db.movies.aggregate( [
{ $match: { title: "The Godfather" } },
{ $limit: 1 },
{ $project: { title: 1 } },
{ $merge: { into: "movieDetails", whenNotMatched: "insert" } }
] )
db.runCommand( {
aggregate: db.movieDetails.getName(),
pipeline: [ {
$merge: {
into: db.movieDetails.getName(),
let : { year: "2023" },
whenMatched: [ {
$addFields: { "addedYear": "$$year" }
} ]
}
} ],
cursor: {}
} )
db.movieDetails.find()
[ { _id: ..., title: 'The Godfather', addedYear: '2023' } ]

版本 5.0 中的新增功能

您可以在 aggregate 命令 let 中定义变量,并在 $merge 阶段 whenMatched 字段中使用变量。

如下示例:

  • 使用来自 movies 集合中的一部电影来初始化 movieDetails 集合

  • 运行 aggregate 命令,该命令会在 aggregate 命令 let 中定义 year 变量,并使用 whenMatched 将年份添加到 movieDetails

  • retrieves the movieDetails document

db.movies.aggregate( [
{ $match: { title: "The Godfather" } },
{ $limit: 1 },
{ $project: { title: 1 } },
{ $merge: { into: "movieDetails", whenNotMatched: "insert" } }
] )
db.runCommand( {
aggregate: db.movieDetails.getName(),
pipeline: [ {
$merge: {
into: db.movieDetails.getName(),
whenMatched: [ {
$addFields: { "addedYear": "$$year" }
} ]
}
} ],
cursor: {},
let : { year: "2023" }
} )
db.movieDetails.find()
[ { _id: ..., title: 'The Godfather', addedYear: '2023' } ]

您可以在$merge阶段定义变量,从 MongoDB 5.0 开始,还可以使用 aggregate 命令。

如果在 $merge 阶段和 aggregate 命令中定义了两个同名的变量,则使用 $merge 阶段变量。

在此示例中,管道使用 year: "2023" 而不是 year: "2019" aggregate 命令变量:

db.movies.aggregate( [
{ $match: { title: "The Godfather" } },
{ $limit: 1 },
{ $project: { title: 1 } },
{ $merge: { into: "movieDetails", whenNotMatched: "insert" } }
] )
db.runCommand( {
aggregate: db.movieDetails.getName(),
pipeline: [ {
$merge: {
into: db.movieDetails.getName(),
let : { year: "2023" },
whenMatched: [ {
$addFields: { "addedYear": "$$year" }
} ]
}
} ],
cursor: {},
let : { year: "2019" }
} )
db.movieDetails.find()
[ { _id: ..., title: 'The Godfather', addedYear: '2023' } ]

以下 Movie 类模型了解决此示例中使用的文档:

[BsonIgnoreExtraElements]
public class Movie
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("title")]
public string Title { get; set; } = null!;
[BsonElement("year")]
public int? Year { get; set; }
[BsonElement("runtime")]
public int? Runtime { get; set; }
[BsonElement("rated")]
public string? Rated { get; set; }
[BsonElement("metacritic")]
public int Metacritic { get; set; }
[BsonElement("plot")]
public string? Plot { get; set; }
[BsonElement("type")]
public string? Type { get; set; }
[BsonElement("cast")]
public string[]? Cast { get; set; }
[BsonElement("directors")]
public string[]? Directors { get; set; }
[BsonElement("writers")]
public string[]? Writers { get; set; }
[BsonElement("imdb")]
public ImdbData? Imdb { get; set; }
}

要使用MongoDB .NET/C# 驱动程序将 $merge 阶段添加到聚合管道,请对 PipelineDefinition 对象调用 Merge() 方法。

调用 Merge() 方法时,必须传递 MergeStageOptions 类的实例。通过该对象,您可以指定 $merge 阶段的选项,例如如何处理匹配文档。

以下示例创建了一个管道阶段,用于将管道中的文档合并到 movies集合中。MergeStageOptions对象指定以下选项:

  • OnFieldNames 选项指定操作应使用 "_id" 字段来标识匹配文档。

  • WhenMatched 选项指定,如果源集合中的文档与目标集合中的文档匹配,则操作会替换匹配的文档。

  • WhenNotMatched选项指定,如果源集合中的文档与目标集合中的文档不匹配,则操作将该文档插入到目标集合中。

var pipeline = new EmptyPipelineDefinition<Movie>()
.Merge(_targetCollection,
new MergeStageOptions<Movie>()
{
OnFieldNames = new List<string>() { "_id" },
WhenMatched = MergeStageWhenMatched.Replace,
WhenNotMatched = MergeStageWhenNotMatched.Insert,
});
{ "_id": "...", "title": "Back to the Future", "metacritic": 96 }
{ "_id": "...", "title": "Jurassic Park", "metacritic": 68 }
{ "_id": "...", "title": "The Shawshank Redemption", "metacritic": 80 }

本页上的 Node.js 示例使用 Atlas 示例数据集中的 sample_mflix数据库。要学习如何创建免费的MongoDB Atlas 集群并加载示例数据集,请参阅MongoDB Node.js驱动程序文档中的入门

要使用MongoDB Node.js驱动程序将 $merge 阶段添加到聚合管道,请在管道对象中使用 $merge操作符。

以下示例创建了一个管道阶段,用于将管道中的文档合并到 movies集合中。该示例包括以下字段:

  • on 选项指定操作应使用 "_id""title" 字段在源集合和 movies集合中查找匹配文档。

  • whenMatched 选项指定,如果源集合中的文档与 movies集合中的文档匹配,则替换 movies集合中的文档。

  • whenNotMatched 选项指定,如果源集合中的文档与 movies集合中的文档不匹配,则操作将该文档插入到 movies集合中。

然后,该示例运行聚合管道:

const pipeline = [
{
$merge: {
into: "movies",
on: ["_id", "title"],
whenMatched: "replace",
whenNotMatched: "insert"
}
}
];
const cursor = collection.aggregate(pipeline);
return cursor;

后退

$match

在此页面上