前言

通常文档只会有一部分要更新。所以可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。更新修改器是一种特殊的键, 用来指定复杂的更新操作,比如修改,增加或者删除键,还可能是操作数据或者内嵌文档。下面分别介绍常见的更新修改器的使用案例。

1、$set

$set用来指定一个字段的值,如果这个字段不存在,则创建它,存在则更新。

1.1 添加一个字段

例如存在这样一条用户资料

1
2
3
4
5
6
7
8
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin"
}

现在要添加一个 books 字段,可以这样操作

1
2
> db.users.update({"_id": ObjectId("5e704b190a1ed3c6d7339063")}, 
... {"$set": {books: "php"}});
1
2
3
4
5
6
7
8
9
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : "php"
}

1.2 修改字段的值

修改 books 字段

1
2
db.users.update({"_id": ObjectId("5e704b190a1ed3c6d7339063")},
... {"$set": {books: "linux"}});
1
2
3
4
5
6
7
8
9
db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : "linux"
}

1.3 错误做法

如果不用$set,直接 update 会怎样呢?现在试试

1
2
> db.users.update({"_id": ObjectId("5e704b190a1ed3c6d7339063")},
... {books: "php"});

这个文档被整体替换了

1
2
> db.users.findOne();
{ "_id" : ObjectId("5e704b190a1ed3c6d7339063"), "books" : "php" }

1.4 改变字段的类型

1
2
> db.users.update({"_id": ObjectId("5e704b190a1ed3c6d7339063")}, 
... {"$set": {books: ["php", "linux", "mysql", "mongodb"]}});

改变后的效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        "php",
        "linux",
        "mysql",
        "mongodb"
    ]
    }

1.5 修改内嵌文档

修改前

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "joe",
        "email" : "joe@example.com"
    }
}
1
2
> db.posts.update({"_id": ObjectId("5e704e860a1ed3c6d7339065")},
... {$set: {"author.name": "demojoe"}})

修改后

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    }
}

2、$unset

删除某个字段 删除前

1
2
3
4
5
6
7
8
9
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : "linux"
}

删除

1
2
> db.users.update({"_id": ObjectId("5e704b190a1ed3c6d7339063")},
... {"$unset": {"books": 1}});

删除后

1
2
3
4
5
6
7
8
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin"
}

3、$inc

$inc 修改器用来增加已有键的值,或者键不存在时就创建一个。
原始数据

1
2
3
4
5
6
> db.games.findOne();
{
    "_id" : ObjectId("5e705c3c0a1ed3c6d7339066"),
    "game" : "pinball",
    "user" : "joe"
}

增加一个 score,初始值为50

1
2
> db.games.update({"_id": ObjectId("5e705c3c0a1ed3c6d7339066")},
... {"$inc": {score: 50}})

增加后的效果

1
2
3
4
5
6
7
> db.games.findOne();
{
    "_id" : ObjectId("5e705c3c0a1ed3c6d7339066"),
    "game" : "pinball",
    "user" : "joe",
    "score" : 50
}

继续给 score 的值增加100

1
2
> db.games.update({"_id": ObjectId("5e705c3c0a1ed3c6d7339066")},
... {"$inc": {score: 100}})

此时 score值变为了150

1
2
3
4
5
6
7
> db.games.findOne();
{
    "_id" : ObjectId("5e705c3c0a1ed3c6d7339066"),
    "game" : "pinball",
    "user" : "joe",
    "score" : 150
}

注意:$inc 键的值必须为数字,不能使用字符串,数组或者其他非数字的值。

4、$push

$push 用来操作数组的元素,如果数组已经存在,$push 会向数组的末尾追加一个元素,如果数组不存在就创建一个数组并追加元素。
比如往下面这条帖子里追加一条评论

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    }
}
1
2
3
4
5
6
7
> db.posts.update({"_id": ObjectId("5e704e860a1ed3c6d7339065")},
... {"$push": {"comments":
... {"name":"joe", "email" : "joe@example",
... "content": "评论一下"}
... }
... }
... )

此时的数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下"
        }
    ]
}

再次追加

1
2
3
4
> db.posts.update({"_id": ObjectId("5e704e860a1ed3c6d7339065")},
... {"$push": {"comments":
... {"name":"jode", "email" : "jode@example",
... "content": "再评论一下啊"} } } )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下"
        },
        {
            "name" : "jode",
            "email" : "jode@example",
            "content" : "再评论一下啊"
        }
    ]
}

5、$each

$each 配合 $push 可以一次往数组里添加多个值
添加前

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        "php",
        "mysql",
        "nignx"
    ]
}

添加操作

1
2
3
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {"$push": {"books": 
... {"$each": ["linux", "mongodb"]}}})

添加后

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        "php",
        "mysql",
        "nignx",
        "linux",
        "mongodb"
    ]
}

6、$slice

$slice 和 $push搭配使用可以保证数组不会超过设定好的长度,实际上就得到了一个最多包含 N 个元素的数组
比如5中的 books 我们最多保留5个元素,原始数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        "php",
        "mysql",
        "nignx",
        "linux",
        "mongodb"
    ]
}

继续追加元素

1
2
3
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {"$push": {"books":
... {"$each": ["shell", "设计模式"],$slice: -5}}});

此时的结果,php 和 mysql 就没有了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        "nignx",
        "linux",
        "mongodb",
        "shell",
        "设计模式"
    ]
}

注意:$slice 的值必须是负数,如果数组的元素数量小于指定的数量,那么所有元素都会保留,如果数组的元素大于指定的数量,那么只会保留最后加入的元素。

7、$sort

$sort 可以用来根据数组中的某个元素进行排序后删除。
原始数据的格式如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")}, {"$unset": {books: 1}});
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")}, {"$push": {books: { "$each": [{"name": "php", "score": 10}, {"name": "linux", "score": 8}]}}})
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "php",
            "score" : 10
        },
        {
            "name" : "linux",
            "score" : 8
        }
    ]
}

books 根据 score 排序,保留分数低的2个

1
2
3
4
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {"$push": {books: { "$each": [{"name": "mysql", "score": 11},
... {"name": "图解TCP\IP", "score": 9.1}],
... "$slice": -2, "$sort": {"score": -1}}}})

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "图解TCPIP",
            "score" : 9.1
        },
        {
            "name" : "linux",
            "score" : 8
        }
    ]
}

保留分数高的2个

1
2
3
4
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {"$push": {books: { "$each": [{"name": "php", "score": 10},
... {"name": "linux", "score": 8}], 
... "$slice": -2, "$sort": {"score": 1}}}})

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "图解TCPIP",
            "score" : 9.1
        },
        {
            "name" : "php",
            "score" : 10
        }
    ]
}

从上面的例子可以看出,$sort 1是升序排列,-1是降序排序
注意:不能只将 $slice 或者 $sort 与 $push 配合使用,必须搭配 $each 一起使用。

8、$ne

$ne 可以用来保证数组中的元素不重复。
原始数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "图解TCPIP",
            "score" : 9.1
        },
        {
            "name" : "php",
            "score" : 10
        }
    ],
    "cities" : [
        "beijing",
        "上海"
    ]
}

cities里没有天津,可以添加成功

1
2
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063"),
... "cities": {"$ne": "天津"}}, {$push: {"cities": "天津"}});

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "图解TCPIP",
            "score" : 9.1
        },
        {
            "name" : "php",
            "score" : 10
        }
    ],
    "cities" : [
        "beijing",
        "上海",
        "天津"
    ]
}

再次执行

1
2
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063"),
... "cities": {"$ne": "天津"}}, {$push: {"cities": "天津"}});

cities里仍然只有三个元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "books" : [
        {
            "name" : "图解TCPIP",
            "score" : 9.1
        },
        {
            "name" : "php",
            "score" : 10
        }
    ],
    "cities" : [
        "beijing",
        "上海",
        "天津"
    ]
}

9、$addToSet

类似$ne,但是比$ne 更方便,因为有些时候$ne 根本行不通
比如向这个用户再添加一个邮箱

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com"
    ]
}
1
2
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {$addToSet: {email: "joe@example.com"}});

由于已经存在了要添加的邮箱,所有 email 列表里仍然是两个

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com"
    ]
}

再加一个不存在的

1
2
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {$addToSet: {email: "joe@hotmail.com"}});

这次就添加进去了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com",
        "joe@hotmail.com"
    ]
}

将 $addToSet 和 $each 组合使用,可以一次添加多个不同的值

1
2
3
4
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")},
... {$addToSet: {"email":
... {$each:
... ["joe@php.net", "joe@itfzy.cn", "lighthome@itfzy.cn", "joe@example.com"]}}});

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com",
        "joe@hotmail.com",
        "joe@php.net",
        "joe@itfzy.cn",
        "lighthome@itfzy.cn"
    ]
}

10、$pop

若是把数组看成队列,可以用 $pop 从数组的任意一端删除元素,{$pop: {“key”: 1}} 表示从尾部删除元素,{$pop: {“key”: -1}} 表示从头部删除元素。 删除前

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com",
        "joe@hotmail.com",
        "joe@php.net",
        "joe@itfzy.cn",
        "lighthome@itfzy.cn"
    ]
}

从尾部删除

1
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")}, {$pop: {email: 1}});

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe@example.com",
        "joe1@example.com",
        "joe@hotmail.com",
        "joe@php.net",
        "joe@itfzy.cn"
    ]
}

从头部删除

1
2
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")}, {$pop: {email: -1}});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe1@example.com",
        "joe@hotmail.com",
        "joe@php.net",
        "joe@itfzy.cn"
    ]
}

11、$pull

这个也是删除数组里的元素,只不过这个可以指定条件,删除指定的元素
比如删除 email里的joe@php.net

1
> db.users.update({_id: ObjectId("5e704b190a1ed3c6d7339063")}, {$pull: {email: "joe@php.net"}});

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> db.users.findOne();
{
    "_id" : ObjectId("5e704b190a1ed3c6d7339063"),
    "name" : "joe",
    "age" : 28,
    "sex" : "male",
    "loc" : "wisconsin",
    "email" : [
        "joe1@example.com",
        "joe@hotmail.com",
        "joe@itfzy.cn"
    ]
}

注意:$pull 会将所有匹配的元素删除,而不只是删除一个。

12、基于位置的数组修改器

如果数组有多个元素,而我们只想对其中的一部分进行操作,这时可以通过位置或者定位操作符($)

12.1 如果数组下标是以0开头的,可以将下标直接作为键来选择元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下"
        },
        {
            "name" : "jode",
            "email" : "jode@example",
            "content" : "再评论一下啊"
        }
    ]
}

比如上面这个文档,如果要对第一条评论增加点赞数,可以这么处理

1
2
> db.posts.update({_id: ObjectId("5e704e860a1ed3c6d7339065")},
... {$inc: {"comments.0.like_count": 1}});

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下",
            "like_count" : 1
        },
        {
            "name" : "jode",
            "email" : "jode@example",
            "content" : "再评论一下啊"
        }
    ]
}

12.2 像上面这种情况是已经直到了要增加点赞数的评论是第一条,但是很多时候不先查询文档就不能直到要修改的数组的下标。 这个时候可以用定位操作符$来定位查询文档已经匹配的数组元素并进行更新。
先给评论增加一个 ID

1
2
3
4
> db.posts.update({_id: ObjectId("5e704e860a1ed3c6d7339065")},
... {$set: {"comments.0.comment_id": 123}});
> db.posts.update({_id: ObjectId("5e704e860a1ed3c6d7339065")},
... {$set: {"comments.1.comment_id": 124}});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下",
            "like_count" : 1,
            "comment_id" : 123
        },
        {
            "name" : "jode",
            "email" : "jode@example",
            "content" : "再评论一下啊",
            "comment_id" : 124
        }
    ]
}

现在增加评论 id 为124的点赞数

1
2
> db.posts.update({"comments.comment_id": 124},
... {"$inc": {"comments.$.like_count": 1}})

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
> db.posts.findOne();
{
    "_id" : ObjectId("5e704e860a1ed3c6d7339065"),
    "title" : "post title",
    "content" : "帖子内容",
    "author" : {
        "name" : "demojoe",
        "email" : "joe@example.com"
    },
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example",
            "content" : "评论一下",
            "like_count" : 1,
            "comment_id" : 123
        },
        {
            "name" : "jode",
            "email" : "jode@example",
            "content" : "再评论一下啊",
            "comment_id" : 124,
            "like_count" : 1
        }
    ]
}

注意:定位符只会更新第一个匹配的元素,所以如果库里有多条 id 相同的评论,此时只会增加匹配到的第一条评论的点赞数。