MongoDB 学习笔记(三):更新文档

112 阅读8分钟

一、更新整篇文档

1.1 更新整篇文档

db.<collection>.update( <query>,<update>,<options>)

参数说明:
    <query>文档定义了 更新操作 时 筛选文档的条件(对哪些文档进行更新)
    <update>提供了更新的内容
    <options>声明了一些更新操作的参数

注意:
    如果<update>文档不包含任何 更新操作符(具体看1.2),db.collection.update()将会使用<update>文档 直接替换 集合中符合<query>文档筛选条件的文档(看例子1)
    
    文档主键_id是不可以更改的,如果在<update>文档中包含_id字段,则_id值一定要和被更新的文档_id值保持一致(看例子2)
    
    在使用<update>文档替换整篇被更新文档时,只有 第一篇 符合<query>文档筛选条件的文档会被更新,即只能应用在单一文档上(看列子3)
    
  • 例子1: 再来看一看alice的银行账户文档
> db.accounts.find( { name:"alice"})
{ "_id" : "account1", "name" : "alice", "balance" : 100 }

  • 例子1:将alice的账户余额改为123
> db.accounts.update( { name:"alice"},{name:"alice",balance:123})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
             # 有多少篇符合查询                  # 有多少篇真的被修改了
  • 例子1:查看更新之后的银行账户文档
> db.accounts.find( { name:"alice"})
{ "_id" : "account1", "name" : "alice", "balance" : 123 }
  • 例子2:主键_id不可以更改,要写也要和被更改的文档_id一致(不推荐加_id)
> db.accounts.update( { name:"alice"},{ _id:"account1",name:"alice",balance:100})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子3:查看账户余额在 20 到 80 之间的账户文档
> db.accounts.find( { balance:{$gt:20,$lt:80}})
{ "_id" : ObjectId("63bd080fef3b5e682a151ff8"), "name" : "bob", "balance" : 50 }
{ "_id" : ObjectId("63c50ac9238aa380d817fa40"), "name" : "bob", "balance" : 50 }

  • 例子3:更新账户余额在 20 到 80 之间的账户文档
> db.accounts.update( { balance: { $gt: 20, $lt:80 }}, {name:"bill", balance:50, gender: "M"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
  • 例子3:再次查看,发现只有第一篇被更改了
> db.accounts.find( { balance:{$gt:20,$lt:80}})
{ "_id" : ObjectId("63bd080fef3b5e682a151ff8"), "name" : "bill", "balance" : 50, "gender" : "M" }
{ "_id" : ObjectId("63c50ac9238aa380d817fa40"), "name" : "bob", "balance" : 50 }

1.2 更新特定字段

db.collection.update()
db.<collection>.upate( <query>,<update>,<options>)

如果<update>文档只包含更新操作符,db.collection.upadte()将会使用<update>文档更新集合中符合<query>文档筛选条件的文档中的特定字段
更新操作符:
    $set        更新或新增字段     { $set: { <field1>: <value1>, ...}}
    $unset      删除字段          { $unset: { <field1>: "", ... } }
    $rename     重命名字段        { $rename:  { <field1>:<newName1>, <field2>:<newName2>, ...}}
    $inc        加减字段值        { $inc: { <field1>: <amount1>,...}}
    $mul        相乘字段值        { $mul: { <field1>: <number1>, ...}}
    $min        比较减小字段值    { $min: { <field1>: <value1>,...}}
    $max        比较增大字段值    { $max: { <field1>: <value1>,...}} 

1.2.1 更新或新增字段--更新操作符

  • 例子4:查看jack的银行账户文档
> db.accounts.find( { name:"jack" } ).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 2000,
        "contact" : [
                "11111111",
                "Alabama",
                "US"
        ]
}

  • 例子4:更新jack的银行账户余额和开户信息
db.accounts.update(
    { name: "jack" },
    { $set:
        {
            balance:3000,
            info: {
                dateOpened: new Date("2016-06-18T16:00:00Z"),
                branch: "branch1"
            }
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子4:再次查看jack账户
> db.accounts.find( { name:"jack" } ).pretty(){
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 3000,
        "contact" : [
                "11111111",
                "Alabama",
                "US"
        ],
        "info" : {
                "dateOpened" : ISODate("2016-06-18T16:00:00Z"),
                "branch" : "branch1"
        }
}

1.2.2 更新或新增内嵌文档的字段

  • 例子5:更新jack的银行账户的开户时间
db.accounts.update(
    { name: "jack"},
    { $set:
        { "info.dateOpened":new Date("2017-01-01T16:00:00Z")}
    }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子5:再次查看jack的银行账户文档
> db.accounts.find( { name:"jack" } ).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 3000,
        "contact" : [
                "11111111",
                "Alabama",
                "US"
        ],
        "info" : {
                "dateOpened" : ISODate("2017-01-01T16:00:00Z"),
                "branch" : "branch1"
        }
}

1.2.3 更新或新增数组内的字段

  • 例子6:更新jack的联系电话
db.accounts.update(
    { name: "jack" },
    { $set:
        {
            "contact.0": "66666666"
        }
    }
)
  • 例子6: 再次查看jack的银行账户文档
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 3000,
        "contact" : [
                "66666666",
                "Alabama",
                "US"
        ],
        "info" : {
                "dateOpened" : ISODate("2017-01-01T16:00:00Z"),
                "branch" : "branch1"
        }
}

-例子6:添加jack新的联系方式(在第四个元素位置)

db.accounts.update(
    { name: "jack"},
    { $set:
        {
            "contact.3":"new contact"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子6: 再次查看jack的账户
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 3000,
        "contact" : [
                "66666666",
                "Alabama",
                "US",
                "new contact"
        ],
        "info" : {
                "dateOpened" : ISODate("2017-01-01T16:00:00Z"),
                "branch" : "branch1"
        }
}

  • 例子6:再次给jack添加联系方式(在第7个元素位置)
db.accounts.update(
    { name: "jack"},
    { $set:
        {
            "contact.6":"another new contact"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子6: 再次查看jack账户信息
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "balance" : 3000,
        "contact" : [
                "66666666",
                "Alabama",
                "US",
                "new contact",
                null,
                null,
                "another new contact"
        ],
        "info" : {
                "dateOpened" : ISODate("2017-01-01T16:00:00Z"),
                "branch" : "branch1"
        }
}
# 如果向现有数组字段范围以外的位置添加新值,数组字段的长度会扩大,未被赋值的数组成员将被设置为 null

1.2.4 删除字段

  • 例子7: 删除jack的银行账户 余额 和 开户地点(内嵌文档)
db.accounts.update(
    { name: "jack" },
    { $unset:
        {
            balance:"",
            "info.branch": "",
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子7:再次查看jack账户信息
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "contact" : [
                "66666666",
                "Alabama",
                "US",
                "new contact",
                null,
                null,
                "another new contact"
        ],
        "info" : {
                "dateOpened" : ISODate("2017-01-01T16:00:00Z")
        }
}

$unset命令中的赋值操作对结果没有任何影响,所以一般直接用空值"",看下面测试

  • 例子7: 删除jack的银行开户时间
db.accounts.update(
    { name: "jack" },
    { $unset: 
        {
            "info.dateOpened": "this can be any value"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

  • 例子7:再次查看jack的账户信息
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "contact" : [
                "66666666",
                "Alabama",
                "US",
                "new contact",
                null,
                null,
                "another new contact"
        ],
        "info" : {
                
        }
}

如果 $unset命令中的字段根本不存在,那么文档内容将保持不变(看下面例子)

  • 例子8:删除不存在的字段
db.accounts.update(
    { name: "jack"},
    { $unset:
        {
            notExistField:""
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

1.2.5 删除数组内的字段

  • 例子9:删除jack的联系电话
db.accounts.update(
    { name: "jack"},
    {
        $unset: {
            "contact.0": ""
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
  • 例子9:再次查看jackde账户信息
> db.accounts.find( { name:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "name" : "jack",
        "contact" : [
                null,
                "Alabama",
                "US",
                "new contact",
                null,
                null,
                "another new contact"
        ],
        "info" : {
                
        }
}
# 删除后数组长度没变化,元素值被设为null

1.2.6 重命名字段

  • 例子10: 如果$rename命令要重命名的字段并不存在,那么文档内容不会被改变
db.accounts.update(
    { name: "jack" },
    { $rename: 
        {
            "notExistField":"name"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

  • 例子11:如果新的字段名已经存在,那么原有的这个字段会被覆盖
db.accounts.update(
    { name: "jack"},
    { $rename: 
        {
            "name":"contact"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })


# 再次查看

> db.accounts.find( { contact:"jack"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152001"),
        "contact" : "jack",
        "info" : {
                
        }
}

rename命令中的新字段存在的时候,rename命令中的新字段存在的时候,rename命令会先unset新旧字段,然后再unset新旧字段,然后再set新字段

1.2.7 重命名内嵌文档的字段

  • 例子12:更新karen的银行账户的开户时间和联系方式
# 1.先查看karen账户内容
> db.accounts.find( { name:"karen"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152002"),
        "name" : "karen",
        "balance" : 2500,
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China"
        ]
}


# 2. 再更新
db.accounts.update(
    { name: "karen" },
    { $set: 
        {
            info: {
                dateOpene: new Date("2017-01-01T16:00:00Z"),
                branch: "branch1"
            },
            "contact.3": {
                primaryEmail: "xxx@gmail.com",
                secondaryEmail: "yyy@gmail.com"
            }
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 3. 再次查看账户内容

> db.accounts.find( { name:"karen"}).pretty(){
        "_id" : ObjectId("63be7ea6ef3b5e682a152002"),
        "name" : "karen",
        "balance" : 2500,
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                }
        ],
        "info" : {
                "dateOpene" : ISODate("2017-01-01T16:00:00Z"),
                "branch" : "branch1"
        }
}

  • 例子13:更新账户余额和开户地点字段在文档中的位置(两个交换位置)
db.accounts.update(
    { name: "karen"},
    { $rename: 
        {
            "info.branch": "branch",
            "balance": "info.balance"
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 再次查看
> db.accounts.find( { name:"karen"}).pretty()
{
        "_id" : ObjectId("63be7ea6ef3b5e682a152002"),
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                }
        ],
        "info" : {
                "dateOpene" : ISODate("2017-01-01T16:00:00Z"),
                "balance" : 2500
        },
        "branch" : "branch1"
}

1.2.8 重命名数组中内嵌文档的字段

  • 例子14:更新Karen的联系方式
# 错误1
db.accounts.update(
    { name: "karen" },
    {
        $rename:
        {
            "contact.3.primaryEmail": "primaryEamil"
        }
    }
)

WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 2,
                "errmsg" : "The source field cannot be an array element, 'contact.3.primaryEmail' in doc with _id: ObjectId('63be7ea6ef3b5e682a152002') has an array field called 'contact'"
        }
})


# 错误2:
db.accounts.update(
    { name: "karen" },
    {
        $rename:
        {
            "branch": "contact.3.branch"
        }
    }
)

WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 2,
                "errmsg" : "The destination field cannot be an array element, 'contact.3.branch' in doc with _id: ObjectId('63be7ea6ef3b5e682a152002') has an array field called 'contact'"
        }
})

$rename 命令中的旧字段和新字段都不可以指向数组元素

1.2.9更新字段数值(加减)

  • 例子15:更新david的账户余额
# 1. 余额减去0.5
db.accounts.update(
     { name: "david"},
     { $inc: 
         {
             balance:-0.5
         }
     }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
`

# 2. 查看账户信息
> db.accounts.find( { name:"david"}).pretty()
{
        "_id" : ObjectId("63bd1a23ef3b5e682a151ffa"),
        "name" : "david",
        "balance" : 199.5
}
{
        "_id" : ObjectId("63bd1c91ef3b5e682a151ffc"),
        "name" : "david",
        "balance" : 20
}

1.2.10 更新字段数值(相乘)

  • 例子16: 更新账户余额
# 1. 余额乘以0.5
db.accounts.update(
     { name: "david"},
     { $mul: 
         {
             balance: 0.5
         }
     }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 2. 查看账户信息
> db.accounts.find( { name:"david"}).pretty()
{
        "_id" : ObjectId("63bd1a23ef3b5e682a151ffa"),
        "name" : "david",
        "balance" : 99.75
}
{
        "_id" : ObjectId("63bd1c91ef3b5e682a151ffc"),
        "name" : "david",
        "balance" : 20
}

incinc和mul命令只能应用在数字字段上

1.2.11 比较之后更新字段值(min、max)

  • 例子17: 更新账户余额
# 1. 查看karen的银行账户文档
db.accounts.find(
    { name: "karen" },
    { name:1,info:1,_id:0}
)

{ "name" : "karen", "info" : { "dateOpene" : ISODate("2017-01-01T16:00:00Z"), "balance" : 2500 } }

# 2. min保留比较小的字段值
db.accounts.update(
    { name:"karen"},
    { $min:
        {
            "info.balance":5000
        }
    }
)
# 所以没有任何文档更新
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

# 3.max保留比较大的值
db.accounts.update(
    { name:"karen"},
    { $max:
        {
            "info.balance":5000
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 4. 查看更新结果
db.accounts.find(
    { name: "karen" },
    { name:1,info:1,_id:0}
)

{ "name" : "karen", "info" : { "dateOpene" : ISODate("2017-01-01T16:00:00Z"), "balance" : 5000 } }

  • 例子18: 如果被更新的字段不存在
# 1. 更新一个不存在的字段
db.accounts.update(
    { name: "karen"},
    { $min:
        {
            notYetExist:10
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 2. 查看karen的账户
db.accounts.find(
    { name: "karen" },
    { name:1,notYetExist:1,info:1,_id:0}
)

所以不存在的字段会添加到文档中,值就位上面设置的值
{ "name" : "karen", "info" : { "dateOpene" : ISODate("2017-01-01T16:00:00Z"), "balance" : 5000 }, "notYetExist" : 10 }

  • 例子19:如果被更新的字段类型和更新值类型不一致
# 1. 将数值与Null比
db.accounts.update(
    { name: "karen"},
    {
        $min:
        {
            "info.balance":null
        }
    }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 查看Karen账户文档
db.accounts.find(
    { name: "karen" },
    { name:1 , info: 1 , _id:0 }
)

用null替换掉之前的值
{ "name" : "karen", "info" : { "dateOpene" : ISODate("2017-01-01T16:00:00Z"), "balance" : null } }


如果被更新的字段类型和更新值类型不一致,minmin和max命令会按照BSON数据类型排序规则进行比较

最小:
    NUll
    Numbers(ints,longs,doubles,decimals)
    Symbol, String
    Object
    Array
    BinData
    ObjectId
    Boolean
    Date
    Timestamp
最大
    Regular Expression
    

1.3 更新数组

数组更新操作符:
    
    $addToSet  向数组中增添元素(可搭配$each)          { $addToSet: { <field1>: <value1>,...}}
    $pop       从数组中移除元素(默认末尾)             { $pop: { <field>: <-1 | 1>,... }}
    $pull      从数组中有选择性的移除元素             { $pull: { <filed1>: <value|condition>, ...}}
    $pullAll   从数组中有选择性的移除元素             { #pullAll: { <field1>: [ <value1>,<value2> ...], ...}}
    $push      向数组字段中添加元素                  { $push: { <field1>:<value1>, .... } }

1.3.1 向数组字段中添加元素(addToSetaddToSet、push)

$addToSet

  • 例子20:查看Karen的银行账户文档
db.accounts.find(
    { name: "karen"},
    { name: 1, contact: 1, _id: 0}
).pretty()

{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                }
        ]
}

  • 例子20: 向karen的账户(contact)添加联系方式
# 添加一个重复的值
db.accounts.update(
    { name: "karen"},
    { $addToSet: { contact: "China" }}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

如果要插入的值已经存在数组字段中,则$addToSet不会再添加重复值

注意一下:使用$addToSet插入数组和文档时,插入值中的字段顺序也和已有值重复的时候,才被算作重复值被忽略

  • 例子21:向Karen的账户文档中添加新的联系方式
添加 数组 重复值(但是顺序不一样)
db.accounts.update(
    { name: "karen" },
    { $addToSet: {
        contact: {
            "secondaryEmail": "yyy@gmail.com",
            "primaryEmail":"xxx@gmail.com"
        }
    }}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })


查看Karen的账户
db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                }
        ]
}

  • 例子22: 向Karen的账户文档中添加多个联系方式(一个数组成为内嵌数组)
 插入一个数组数据
db.accounts.update(
    { name: "karen"},
    { $addToSet: { contact: [ "contact1","contact2"]}}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

# 再次查看

> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [
                        "contact1",
                        "contact2"
                ]
        ]
}

$addToSet会将数组插入被更新的数组字段中,成为内嵌数组

如果想要将多个元素直接添加到数组字段中,则需要使用$each操作符

  • 例子23:向Karen的账户文档添加多个联系方式(一个数组直接插入元素)
db.accounts.update(
    { name: "karen"},
    { $addToSet: { contact: { $each: [ "contact1","contact2" ] } } }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

再次查看
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [
                        "contact1",
                        "contact2"
                ],
                "contact1",     # 发现元素单独插入,而不是直接插入一个数组
                "contact2"
        ]
}

$push

pushpush和addToSet命令相似,但是push命令的功能更强大,和push命令的功能更强大,和addToSet命令一样,如果$push命令指定的数组字段不存在,这个字段会被添加到原文档中

db.accounts.update(
    { name: "lawrence"},
    { $push: {newArray: "new element" } }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find(     { name: "lawrence"},     { name:1, newArray:1, _id:0 } ).pretty()
{ "name" : "lawrence", "newArray" : [ "new element" ] }

1.3.2 从数组字段中删除元素($pop)

  • 例子24:从Karen的账户文档中删除最后一个联系方式
1代表删除最后一个元素
db.accounts.update(
    { name: "karen"},
    { $pop: { contact:1 } }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [
                        "contact1",
                        "contact2"
                ],
                "contact1"  # 之前的contact2删掉了
        ]
}

-1代表删除第一个元素
db.accounts.update(
    { name: "karen" },
    { $pop: { "contact.5": -1}}   # 删除下标5(第6个)元素的第一个元素
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [
                        "contact2"   # 删除了这个内嵌数组的第一个元素
                ],
                "contact1"
        ]
}

再次删除,会保留空的数组

> db.accounts.update(     { name: "karen" },     { $pop: { "contact.5": -1}} )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [ ],    # 删掉最后一个元素,会留下空数组
                "contact1"
        ]
}

删除掉数组中最后一个元素,会留下空数组

注意一点,$pop操作符只能应用在数组字段上,看下面例子

尝试应用在数组字段上的字符串
db.accounts.update(
    { name: "karen" },
    { $pop: { "contact.1": -1 } }
)

WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 14,
                "errmsg" : "Path 'contact.1' contains an element of non-array type 'string'"
        }
})

1.3.3 从数组字段中删除特定元素

使用$pull

先将Karen的账户文档复制一份,并将新文档的用户姓名设置为lawrence

db.accounts.find(
    {name: "karen"},
    { _id: 0 }
).forEach( function(doc){
    var newDoc = doc;
    newDoc.name= "lawrence";
    db.accounts.insert(newDoc)
})

查看larence的银行账户文档
> db.accounts.find(     { name: "lawrence"} ).pretty()
{
        "_id" : ObjectId("63d630f61ce94226b81725a2"),
        "name" : "lawrence",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [ ],
                "contact1"
        ],
        "info" : {
                "dateOpene" : ISODate("2017-01-01T16:00:00Z"),
                "balance" : null
        },
        "branch" : "branch1",
        "notYetExist" : 10
}

  • 例子25:从Karen的联系方式中删去包含'hi'字母的元素
db.accounts.update(
    { name: "karen"},
    { $pull: { contact: { $elemMatch: { $regex: /hi/}}}}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
事实上,既然$pull操作符本身是只能作用在数组元素上的,我们便不需要再额外使用$elemMatch操作符了

可以将$pull操作符处理的数组元素当作普通的顶层字段来应用筛选条件

db.accounts.update(
    { name: "karen"},
    { $pull: { contact: {$regex: /hi/}}}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
再次查看
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",   # 删掉了原本的china
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [ ],
                "contact1"
        ]
}

当然,如果有数组元素本身就是一个内嵌数组,也可以使用$elemMatch来对这些内嵌数组进行筛选

  • 例子26:从Karen的联系方式中删去电话号码22222222
db.accounts.update(
    { name: "karen"},
    { $pull: { contact: { $elemMatch: {$eq: "22222222"}}}}
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find(     { name: "karen"},     { name: 1, contact: 1, _id: 0} ).pretty()
{
        "name" : "karen",
        "contact" : [
                "Beijing",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [ ],
                "contact1"
        ]
}

使用$pullAll

{ $pullAll: { <field1>: [ <value1>,<value2> ] } }
相当于
{ $pull: { <field>: { $in:[ <value1>,<value2> ] } } }

如果要删去的元素是一个数组,数组元素的值和排列顺序都必须和被删除的数组完全一样
  • 例子27:查看lawrence的银行账户文档
db.accounts.find(
    { name: "lawrence"},
    { name:1, contact:1, _id:0 }
).pretty()

{
        "name" : "lawrence",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                {
                        "secondaryEmail" : "yyy@gmail.com",
                        "primaryEmail" : "xxx@gmail.com"
                },
                [ ],
                "contact1"
        ]
}

  • 例子28:删除联系电话(一个内嵌数组)
当元素的排列顺序不一样时,无法删除
db.accounts.update(
    {name: "lawrence"},
    { $pullAll: { contact: [ [ "33333333", "22222222"]]}}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

当值和排列顺序完全一样时才可以删除

如果要删去的元素是一个文档,$pullAll命令只会删去字段和字段排列顺序完全匹配的文档元素

  • 例子29:
情况一:当要删去的文档只包含原文档的一部分时,删除失败
db.accounts.update(
    { name: "lawrence"},
    { $pullAll: {contact: [ { "primaryEmail": "xxx@gmail.com" } ] } }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

情况二:当包含全部字段时,就要看字段的顺序是否一致
db.accounts.update(
    { name: "lawrence"},
    { $pullAll: {contact: [ { "secondaryEmail": "yyy@gmail.com","primaryEmail": "xxx@gmail.com" } ] } }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

查看删除后的文档,发现删除了符合顺序的那个文档
> db.accounts.find(     { name: "lawrence"},     { name:1, contact:1, _id:0 } ).pretty()
{
        "name" : "lawrence",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                {
                        "primaryEmail" : "xxx@gmail.com",
                        "secondaryEmail" : "yyy@gmail.com"
                },
                [ ],
                "contact1"
        ]
}

$pull命令会删去包含指定的文档字段和字段值的文档元素,字段排列顺序不需要完全匹配

  • 例子30:
情况一:当提供给pull命令的参数文档只和原文档部分符合时,会删除
db.accounts.update(
    { name: "lawrence" },
    { $pull: { contact: { "primaryEmail": "xxx@gmail.com"} } }
)

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find(     { name: "lawrence"},     { name:1, contact:1, _id:0 } ).pretty()
{
        "name" : "lawrence",
        "contact" : [
                [
                        "22222222",
                        "33333333"
                ],
                "Beijing",
                "China",
                [ ],
                "contact1"
        ]
}

情况二:当提供给pull命令的参数文档和原文档的字段相同,顺序不同时,会删除
db.accounts.update(
    { name: "lawrence" },
    { $pull: { contact: { "secondaryEmail": "yyy@gmail.com","primaryEmail": "xxx@gmail.com"} } }
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })