$merge
的用途是把聚合管道产生的结果写入指定的集合,有时候可以用$merge
来做物化视图。需要注意,$meger
操作必须是聚合管道的最后一个阶段。具体功能有:
- 能够输出到当前或不同的数据库
- 能够输出到正在聚合的集合(慎重:可能会导致频繁的更新甚至死循环)
- 可以在副本集的二级节点运行,前提是群集所有节点的
featureCompatibilityVersion
不小于4.4,且读选项允许二级读取。注意:$merge
读取操作会发送到二级节点,写入操作只发生在主节点。- 并非所有版本的驱动都支持
$merge
在副本集二级节点的操作,在使用前要确认驱动支持。
- 输出集合不存在时可以自动创建输出集合,数据库不存在时也可以自动创建
- 输出结果可以与现有集合合并,包括:插入新文档、合并文档、替换文档、保持已存在的文档、让操作失败、使用自定义的更新管道处理文档等。
语法
js
{ $merge: {
into: <collection> -or- { db: <db>, coll: <collection> },
on: <identifier field> -or- [ <identifier field1>, ...], // 可选
let: <variables>, // 可选
whenMatched: <replace|keepExisting|merge|fail|pipeline>, // 可选
whenNotMatched: <insert|discard|fail> //可选
} }
举例:
js
{ $merge: {into: "mycollection", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
如果都使用$merge
的默认选项并且输出到当前数据库,可以使用简化形式:
js
{ $merge: <collection> } //输出到当前数据库
字段说明
into
输出到的集合,可以指定到数据库,也可以不指定数据库,输出到当前数据库。例如:
输出到当前库的my_coll
集合:into: "my_coll"
输出到mydb
的my_coll
集合:into:{db:"mydb", coll:"my_coll"}
注意:
- 如果目标库或集合不存在,会自动创建
- 如果是共享的群集,目标集合必须要存在
- 输出集合也可以是共享集合
on
可选字段,可以指定一个或多个字段作为判断文档唯一性的ID,用于匹配目标集合中是否已经存在相同ID的文档。
举例:
- 一个字段:
on: "_id"
- 多个字段:
on: ["date", "name"]
注意:
- 如果指定了
on
字段,除非on
是_id
,否则在聚合结果文档中必须存在on
中指定的字段,如果聚合结果中没有_id
字段,会自动添加一个。 on
指定的字段的值不允许为空或数组。$merge
必须要有一个包含on
字段的唯一索引,索引键顺序无关紧要。- 索引必须有跟聚合集合同样的集合
- 唯一索引必须是一个稀疏索引
- 唯一索引不能是部分索引
- 对已经存在的输出集合,相应的索引必须已经存在
on
的缺省值依赖于输出集合:- 如果输出集合不存在,
on
的标识符必须且缺省为_id
字段,相应的唯一索引_id
是被自动创建的。 - 如果输出集合是已经存在的分片集合,
on
标识符缺省为_id
字段 - 如果输出集合是已经存在的非分片集合,
on
标识符缺省为所有分片键值字段和_id
字段,如果指定了一个不同的on
标识符,on
必须包含所有的分片键值字段。
- 如果输出集合不存在,
whenMatched
可选字段,指定当输出集合中存在与on
字段指定的键值相同的文档时的处理方式,可以为以下值:
"replace" 替换
使用聚合结果替换已经存在的文档。当执行替换的时候不能修改对应文档_id
字段的值,如果输出集合是分片集合,也不能修改分片键值,否则操作会产生错误。为了避免这个错误,如果on
字段中没有包含_id
字段,可以从聚合结果中移除_id
字段以避免这个错误,比如可以使用类似$unset
的阶段预先处理以下。
"keepExistin" 保留已存在的
不替换已经存在的文档
"merge" 合并
缺省值,合并匹配的文档,类似于$mergeObjects
操作
- 如果聚合结果文档的字段在目标文档中不存在,就添加
- 如果聚合结果文档的字段在目标文档中已存在,则替换
举例:
如果目标集合有一个文档:
json
{_id: 1, a: 1, b: 1 }
聚合结果的文档是:
json
{ _id: 1, b: 5, z: 1 }
则合并后的文档是:
json
{ _id: 1, a: 1, b: 5, z: 1 }
跟"replace"
类似,合并的时候"_id"字段或分片键值是不能被修改的。
"fail" 失败
停止并且报错,之前所有的输出和更改都不能撤销。
使用聚合管道更新输出集合文档
当on
指定的键值相同时,使用一个聚合管道更新输出集合的文档,如:
js
[ <stage1>, <stage2> ... ]
但是,管道只能包含下面的阶段:
$addFields
及其别名$set
$projecct
及其别名$unset
$replaceRoot
及其别名$replaceWith
管道不能修改on
涉及字段的值,比如匹配字段year
,管道是不能修改year
字段值的。另外whenMatched
管道可以使用$<field>
直接访问输出文档的字段。如果要在管道中访问聚合结果文档(就是输入文档)的字段,可以使用下面两种方式:
- 使用内置的
$$new
变量来访问字段,就是$$new.<field>
。$$new
变量只能在省略let
时才能使用。 - 在
let
字段使用用户自定义的变量。以$$
符号为前缀指定变量名$$<variable_name>
,如:$$year
。如果变量是文档,也可以包含文档字段,格式为$$<变量名>.<字段>
。例如,$$year.month
。
let
可选字段,为whenMatched
的管道指定变量。可以指定文档的变量名和表达式:
js
{ <variable_name_1>: <expression_1>,
...,
<variable_name_n>: <expression_n> }
whenNotMatch
可选字段,决定了$merge
在输出文档匹没有配到对应文档的情况,可以指定下面的预定义的字符串常量:
"insert"
插入,缺省值,将聚合后的文档插入到输出集合。discard
丢弃,就是不向输出集合中插入文档。fail
失败,停止并宣告聚合操作失败,之前已经在输出集合中写入或修改的文档不能回滚。