对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
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 查找托管在以下环境中的部署:

  • MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务

$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" ]

    数组中字段的顺序并不重要,不能多次指定同一字段。

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

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

  • 对于运行MongoDB8.0 及更早版本的部署,on 的指定字段不能缺失或包含 null 值。从MongoDB8.1 开始,如果支持的索引不是稀疏索引,则为 on指定的一个或多个字段可能会缺失或包含 null 值。

  • 指定的字段或多个字段不能包含大量值。

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

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

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

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

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

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

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

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

    • 从MongoDB 8.3 开始,服务器会进行检查,确保自动创建的 _id索引与查询的排序规则匹配。如果排序规则不匹配,则 _id索引无法为查询提供唯一性,并且查询将不会运行。

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

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

可选。如果结果文档与集合中的现有文档具有相同的指定字段值,则$merge 的行为。

您可以指定以下任一项:

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

    操作
    说明

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

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

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

    在输出集合中保留现有文档。

    "merge"(默认)

    合并匹配文档(类似于 $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"(默认)

将此文档插入输出集合。

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

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

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

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

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

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

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

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

注意

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

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

如果输出集合不存在,则$merge 要求标识符为_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 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 如果聚合结果包含根据...规范匹配的一个或多个文档,则可以替换输出集合中的现有文档。因此,如果聚合结果包括集合中所有现有文档的匹配文档并且您在$merge 中指定“替换”,则 可以替换现有集合中的所有文档。 for whenMatched。

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

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

提示

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

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

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

如果$merge 在字段上使用的唯一索引在聚合过程中被删除,则无法保证聚合会被终止。如果继续聚合,则无法保证文档中没有重复的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 输出到时间序列集合。

视图定义
与物化视图分开

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

$lookup 阶段

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

$facet 阶段

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

$unionWith 阶段

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

"linearizable" 读关注 (read concern)

$merge阶段不能与读关注(read concern)"linearizable" 一起使用。也就是说,如果您为 指定"linearizable" 读关注(read concern),则不能将db.collection.aggregate() $merge阶段包含在管道中。