前篇大篇幅讲解了update操作,在那部分中,update进行的是替换式更新,但这种更新在实际情况中用处实在很少(应该只会在文档结构发生巨变时使用)。通常我们遇到的是文档的部分更新,比如调整、增加、删除某个键。对于这种需求,MongoDB提供了原子的更新修改器。更新修改器是种特殊的键,专门用来进行复杂的细节的更新操作,比替换式更新更高效。

【“$set”修改器】

"$set"用来指定一个键,如果文档中不存在这个键,则创建它,如果存在,则将这个键进行替换。下面我们就举一个例子,我们要为一个用户文档,添加一个其喜欢的书籍的键(favorite book),并初始指定一本书,然后将这个键的值换成另外一本书,接着用户最喜欢的书不止一本了,将这个键的值的类型替换为数组,最后将这个键删除:

我们通过"$set"修改器,向原始文档中添加一个键:

> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "name" : "drifter"
}
> db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$set" : {"favorite book" : "war and peace"}});
> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : "war and peace",
        "name" : "drifter"
}
>

通过修改器修改这个键的值:

> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : "war and peace",
        "name" : "drifter"
}
> db.users.update({"name":"drifter"},
... {"$set" : {"favorite book":"green eggs and ham"}});
> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : "green eggs and ham",
        "name" : "drifter"
}
>

通过修改器修改这个键的值类型,由字符串改为数组:

> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : "green eggs and ham",
        "name" : "drifter"
}
> db.users.update({"name":"drifter"},
... {"$set":{"favorite book":["cat's cradle", "foundation trilogy"]}});
> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : [
                "cat's cradle",
                "foundation trilogy"
        ],
        "name" : "drifter"
}
>

最后,我们发现这个键还是不要了吧,删之:

> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "favorite book" : [
                "cat's cradle",
                "foundation trilogy"
        ],
        "name" : "drifter"
}
> db.users.update({"name":"drifter"},
... {"$unset":{"favorite book" : 1}});
> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "age" : 18,
        "name" : "drifter"
}
>

删除键,我们用了修改器"$unset"。使用为:"$unset":{文档}。其作用是会将所接文档中所有的键(其值不会影响操作效果),如果在待修改文档中存在,则从待修改文档中删除。

修改器“$set”可以用于修改内嵌文档:

> db.blogs.findOne({"title":"Java"});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "title" : "Java",
        "content" : "...",
        "author" : {
                "name" : "drifter",
                "email" : "drifter@example.com"
        }
}
> db.blogs.update({"_id": ObjectId("501e0d1905f64f64b0765c57")},
... {"$set":{"author.name":"drifter_zh"}});
> db.blogs.findOne({"title":"Java"});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "content" : "...",
        "title" : "Java"
}
>

内嵌文档的键就是:外层文档的键.内层文档的键。修改器"$set",“$unset”可以用来修改值为各种类型的键, MongoDB还提供了专们针对某种值类型的键的修改器。

【增加和减少修改器】

修改器"$inc"专门用户值为数字的键,对这个键的值进行增加或修改。如果文档中不存在这个键,则会创建。

原始文档中没有键"pv",我们第一次用修改器"$inc"会为文档添加键"pv":

> db.pvcount.findOne();
{ "_id" : ObjectId("501e101505f64f64b0765c58"), "url" : "www.csdn.net" }
> db.pvcount.update({"url":"www.csdn.net"},
... {"$inc":{"pv":1000}});
> db.pvcount.findOne();
{
        "_id" : ObjectId("501e101505f64f64b0765c58"),
        "pv" : 1000,
        "url" : "www.csdn.net"
}
>

我们通过"$inc"修改器来增加这个“pv”键的值:

> db.pvcount.findOne();
{
        "_id" : ObjectId("501e101505f64f64b0765c58"),
        "pv" : 1000,
        "url" : "www.csdn.net"
}
> db.pvcount.update({"url":"www.csdn.net"},
... {"$inc":{"pv":999}});
> db.pvcount.findOne();
{
        "_id" : ObjectId("501e101505f64f64b0765c58"),
        "pv" : 1999,
        "url" : "www.csdn.net"
}
>

我们再通过"$inc"修改器来减小这个“pv”键的值:

> db.pvcount.findOne();
{
        "_id" : ObjectId("501e101505f64f64b0765c58"),
        "pv" : 1999,
        "url" : "www.csdn.net"
}
> db.pvcount.update({"url":"www.csdn.net"},
... {"$inc":{"pv":-777}});
> db.pvcount.findOne();
{
        "_id" : ObjectId("501e101505f64f64b0765c58"),
        "pv" : 1222,
        "url" : "www.csdn.net"
}
>

我们必须要铭记的是,修改器"$inc"虽然十分好用,但其修改的键的值必须为数字,如果待修改文档中该键已经存在,但值类型不是数字,则修改器"$inc"操作失败:

> db.blogs.findOne({"_id":ObjectId("501df9ba05f64f64b0765c52")});
{
        "_id" : ObjectId("501df9ba05f64f64b0765c52"),
        "comments" : "good",
        "contents" : "how to learn java",
        "readcount" : "100",
        "title" : "my second blog"
}
> db.blogs.update({"_id":ObjectId("501df9ba05f64f64b0765c52")},
... {"$inc":{"readcount":200}});
Cannot apply $inc modifier to non-number
>

【数组修改器】

MongoDB提供了一套修改器专门用于操作值为数组的键。数组是MongoDB中经常使用并且十分有用的数据结构,他们不仅是可通过索引进行使用的列表,而且还可作为集合来使用。我们这里提供的一组修改器只可用于值为数组的键的更新操作上。

“$push”修改器,如果这个键存在,会向其数组类型的值的末尾加入一个元素,如果这个键不存在,则会创建,并且其值为数组类型。

第一次使用修改器"$push",会创建值类型为数组的相应键值对:

> db.blogs.findOne();
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "content" : "...",
        "title" : "Java"
}
> db.blogs.update({"_id": ObjectId("501e0d1905f64f64b0765c57")},
... {"$push":{"comments":{"name":"joe", "email":"joe@example.com","content":
... "good blog"}}});
> db.blogs.findOne();
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "good blog"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
>

当这个键存在,再次调用该修改器"$push",会往数组末尾添加元素:


> db.blogs.findOne();
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "good blog"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
> db.blogs.update({"_id": ObjectId("501e0d1905f64f64b0765c57")},
... {"$push":{"comments":{"name":"bob", "email":"bob@example.com", "content":
... "just so so!"}}});
> db.blogs.findOne();
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "good blog"
                },
                {
                        "name" : "bob",
                        "email" : "bob@example.com",
                        "content" : "just so so!"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
>

在往数组中添加元素的操作中,我们通常还有这个需求,如果这个元素不在数组中,就添加,否则不添加,我们可以通过修改器"$ne"或“$addToSet”来实现,我们这里分别进行演示:

> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com"
        ],
        "name" : "drifter"
}
> db.users.update({"email":{"$ne":"dri@126.com"}},
... {"$push":{"email":"dri@126.com"}});
> db.users.findOne();
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com"
        ],
        "name" : "drifter"
}
>

修改器“$ne”的解释为:如果键对应的值中不包含修改器指定的值,则往这个键对应的数组中push这个值进去。

对应修改器"$addToSet"的示例为:

>  db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com"
        ],
        "name" : "drifter"
}
>  db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$addToSet":{"email":"dri@126.com"}});
> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com"
        ],
        "name" : "drifter"
}
>

对于上述两种使用,感觉修改器"$addToSet"比修改器"$ne"含义更清晰,也更易用!并且修改器"$addToSet"可以和修改器“$each”组合使用,修改器“$ne”就无法做到这点:

> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com"
        ],
        "name" : "drifter"
}
> db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$addToSet":{"email":{"$each":["dri@hotmail.com","dri@google.com",
... "dri@126.com","dri@163.com"]}}});
> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com",
                "dri@hotmail.com",
                "dri@google.com"
        ],
        "name" : "drifter"
}
>

修改器"$pop","$pull"可以从数组中删除相应的值:{“$pop”:{key:1}}从数组末尾删一个值,{"$pop":{key:-1}}从数组头部删一个值,{“$pull”:{key:value}},从数组中删掉所有特定值,示例如下:

> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com",
                "dri@hotmail.com",
                "dri@google.com"
        ],
        "name" : "drifter"
}
> db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$pop":{"email":1}});
> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@126.com",
                "dri@163.com",
                "dri@hotmail.com"
        ],
        "name" : "drifter"
}
> db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$pop":{"email":-1}});
> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@163.com",
                "dri@hotmail.com"
        ],
        "name" : "drifter"
}
> db.users.update({"_id" : ObjectId("501e069405f64f64b0765c56")},
... {"$pull":{"email":"dri@163.com"}});
> db.users.findOne({"name":"drifter"});
{
        "_id" : ObjectId("501e069405f64f64b0765c56"),
        "email" : [
                "dri@hotmail.com"
        ],
        "name" : "drifter"
}
>

【数组的定位修改器】

上面提到的数组修改器是针对整个数组使用的,但有时我们需要针对数组中某个特定的值进行一些修改,有两种方式可以做到这点:通过数组索引精确定位或通过定位操作符("$")。我们分别演示一下:

通过数组索引精确定位的方式:数组索引都是从0开始的,可以直接拿这个索引值作为键来定位数组中某个值,并对这个值进行修改:

> db.blogs.findOne({"_id" : ObjectId("501e0d1905f64f64b0765c57")});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "good blog"
                },
                {
                        "name" : "bob",
                        "email" : "bob@example.com",
                        "content" : "just so so!"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
> db.blogs.update({"_id" : ObjectId("501e0d1905f64f64b0765c57")},
... {"$set":{"comments.0.name":"joe dave"}});
> db.blogs.findOne({"_id" : ObjectId("501e0d1905f64f64b0765c57")});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "content" : "good blog",
                        "email" : "joe@example.com",
                        "name" : "joe dave"
                },
                {
                        "name" : "bob",
                        "email" : "bob@example.com",
                        "content" : "just so so!"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
>

这种方式简单,容易理解,但实用性不高,因为通常我们并不知道我们要修改的元素在数组中的什么位置上。这时,我们可以使用定位符"$":

> db.blogs.findOne({"_id" : ObjectId("501e0d1905f64f64b0765c57")});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "content" : "good blog",
                        "email" : "joe@example.com",
                        "name" : "joe dave"
                },
                {
                        "name" : "bob",
                        "email" : "bob@example.com",
                        "content" : "just so so!"
                }
        ],
        "content" : "...",
        "title" : "Java"
}
> db.blogs.update({"comments.email":"bob@example.com"},
... {"$set":{"comments.$.name":"bob james"}});
> db.blogs.findOne({"_id" : ObjectId("501e0d1905f64f64b0765c57")});
{
        "_id" : ObjectId("501e0d1905f64f64b0765c57"),
        "author" : {
                "email" : "drifter@example.com",
                "name" : "drifter_zh"
        },
        "comments" : [
                {
                        "content" : "good blog",
                        "email" : "joe@example.com",
                        "name" : "joe dave"
                },
                {
                        "content" : "just so so!",
                        "email" : "bob@example.com",
                        "name" : "bob james"
                }
        ],
        "content" : "...",
        "title" : "Java"
}

通过定位符,我们找到需要修改的数组中的元素,如果通过定位条件可以定位多个数组中元素,定位符“$”只会指向匹配到的第一个,然后更新操作也只会发生在第一个匹配元素上

以上是通过修改器对文档进行更新操作部分。修改器很灵活,功能也很强大!