Mongo shell指南(CRUD)

MongoDB 概念解析

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

MongoDB术语/概念 说明
database 数据库
collection 集合
document 文档
field
index 索引
primary key 主键,MongoDB自动将_id字段设置为主键

安装MongoDB

安装MongoDB请参考之前文章MongoDB安装与基本使用

创建文档

1. insertOne命令(用于创建单个文档)

db.<collection>.insertOne(<document>, { writeConcern: <document> })

writeConcern定义了本次文档创建的安全写级别(用来判断一次数据库写入操作是否是成功的,安全写级别越高,丢失数据的风险就越低,然而写入操作的延迟也可能更高,不提供writeConcern文档, MongoDB会启用默认的安全写级别)

db.<collection>.insertOne({
    _id: "account1", //可以自己提供(要保证唯一性),也可以省略_id主键,mongo自动会为我们生成唯一的文档主键(生成的是ObjectId对象)
    name: 'jack',
    balance: 100
})

操作成功之后返回

{ "acknowledged" : true, "insertedId" : "account1" }

acknowledged:true //表示默认的安全写级别被启用

insertedId:"account1" //表示被插入的主键id为自定义的account1

注意: _id是文档主键,当没有提供文档主键的情况下,默认生成ObjectId用作唯一的文档主键。如果<collection>不存在则会在创建document前先被创建

当储存错误的时候可以进行捕获

try {
    db.<collection>.insertOne({
        _id: "account1",
        name: 'jack',
        balance: 100
    })
}catch(e) {
    print(e)
}

如果发生错误,会返回WriteError对象,包含了errMsg字段,有详细的错误信息。

2. insertMany命令 (创建多个文档)

db.<collection>.insertMany([<document1>, <document2>, ...], { writeConcern: <document>, ordered: <boolean> })

insertMany 相比 insertOne多了一个参数ordered, 用来决定mongodb是否要按照顺序来写入这些文档。设置为false 有利于优化写入性能,前面报错,并不影响后一个写入,其默认值为true

db.<collection>.insertMany([
    {
        name: 'test',
        balance: 1000
    }, {
        name: 'spark',
        balance: 500
    }
])

操作成功之后返回

{ "acknowledged" : true, "insertedId" : [
        ObjectId('1234234323425'), 
        ObjectId('1234567654345')
    ] 
}

如果发生错误,会返回BulkWriteError对象,包含了errMsg字段,有出错文档的详细错误信息。

3. insert命令 创建单个或者多个文档

db.<collection>.insert(<document or array of documents>, {writeConcern: <document>, ordered: <boolean>})

既可以写入一个单一的文档,也可以写入多个文档

db.<collection>.insert({
    name: 'george',
    balance: 1000
})

操作成功之后返回

WriteResult({"nInserted": 1}) 

如果发生错误,会返回WriteError对象,包含了writeErrors数组字段,有出错文档的详细错误信息。

db.<collection>.insert([
    {
        name: '1111',
        bablace: 800
    }, {
        name: '2222',
        bablace: 200
    }
])

操作成功之后返回

BulkWriteResult({
    "wirteErrors": [],
    "writeConcernErrors": [],
    "nInserted": 2,
    ...
})

如果发生错误,会返回BulkWriteError对象,包含了writeErrors数组字段,有着出错文档的详细错误信息。


总结:inertOne、insertMany、insert区别

  • 三个命令返回的结果文档格式不一样
  • insertOne和insertMany命令不支持db.<collection>.explain()命令,而insert支持db.<collection>.explain()命令

4. save命令

db.<collection>.save(<document>, {writeConcern: <document>})

当save命令处理一个新文档的时候,它会调用db.<collection>.insert()


创建文档操作补充

主键操作:

  • 可以在shell命令行中,输入ObjectId()来生成主键id
  • 输入ObjectId("数据库中存储的任一主键id").getTimestamp(),来获取创建时间
  • 用文档作为文档主键叫做复合主键(复合主键仍要满足文档主键的唯一性)
db.<collection>.insert({
    _id: {
        accountNo: "001",
        type: "savings"
    }, // 复合主键,顺序不一样就不算同一个主键
    balance: 80
})

筛选文档

匹配查询

  • 根据字段查询,查询name为alice的文档

    db.<collection>.find({name: 'alice'}) 
    
  • 用多个字段查询, 查询name为alice并且balance为100的文档

    db.<collection>.find({name: 'alice', balance: 100})
    
  • 查询复合文档

    db.<collection>.find('_id.type': 'savings')
    

比较操作符

{field: {$<operator>: <value>}}

$eq : 匹配字段值相等的文档

$ne: 匹配字段值不相等的文档

$gt:匹配字段值大于查询值的文档

$gte: 匹配字段值大于或等于查询值的文档

$lt: 匹配字段值小于查询值的文档

$lte:匹配字段值小于或者等于查询值的文档

{field: {$in: [<value1>, <value2>...<valueN>]}}

$in 匹配字段值与任一查询值相等的文档

$nin 匹配字段值与任何值都不相等的文档


db.<collection>.find({name: {$eq: 'alice'}}) //跟匹配查询有相同的效果 

db.<collection>.find({balance: {$ne: 100}}) //查询结果将包含没有此字段的文档 

db.<collection>.find({balance: ${$gt: 500}}) //查询大于balance大于500的文档 

db.<collection>.find({balance: ${$lt:'fred'}}) //查询排在fred之前的文档(abide)

db.<collection>.find({name: {$in: ['alice', 'charlie']}}) //查询name为alice或者charlie的文档 

db.<collection>.find({name: {$nin: ['alice', 'charlie']}}) //查询name除alice和charlie之外的文档,如果查询的键值不存在,则一并返回没有此键值的文档

逻辑操作符

$not 匹配筛选条件不成立的文档

{field: {$not: {<operator-expression>}}}

$and 匹配多个筛选条件下全部成立的文档

{$and: [<expression1>, <expression2>....<expressionN>]}

$or 匹配至少一个筛选条件成立的文档

{$or: [<expression1>, <expression2>....<expressionN>]}

$nor 匹配多个筛选条件全部不成立的文档

{$nor: [<expression1>, <expression2>....<expressionN>]}

如果像下面这样也会查询出不包含查询字段的文档

db.<collection>.find({balance: {$not: {$lt: 500}}})

有些可以简化写

db.<collection>.find({$and: [{balance: {$gt: 100}}, {name: {$gt: 'fred'}}]}) 
↓↓↓↓↓↓↓ 简化
db.<collection>.find({balance: {$gt: 500, $lt: 1000}}) 

字段操作符

$exists 匹配包含查询字段的文档

{field: {$exist: <boolean>}}

$type 匹配字段类型符合查询值的文档

{field: {$type: <BSON type>}} or {field: {$type: [<BSON type1>, <BSON type2>, ...]}}

db.<collection>.find({'_id.type': {$ne: 'savings', $exists: true}})//查询_id有type字段的文档,并且不等于savings
db.<collection>.find({'_id': {$type:['object', 'objectId']}})
db.<collection>.find({'_id.type': {$ne: {$type: 'string'}}, exists: true})
db.<collection>.find({'name': {$type: 'null'}})

数组操作符

$all 匹配数组字段中包含所有查询值的文档

{field: {$all: ['value1', 'value2', ....]}}

$elemMatch 匹配数组字段中至少存在一个满足筛选条件的文档

{field: {$elemMatch: {\<query1>, \<query2>,...\<query?>}}}

db.<collection>.find({contact: {$all:['CHINA', '10010']}})
db.<collection>.find({contact: {$all: [['11111']]}})
db.<collection>.find({contact: {$elemMatch: {$eq: '111111'}}})
db.<collection>.find({contact: {$elemMatch: {$gt: 100, $lt: 1000}}})

运算操作符

$regex 匹配满足正则表达式的文档

{field: { $regex: /regex/,<options> }}

在和$in一起使用的时候需要用这种语法

{field: {$in:[/regex1/,/regex2/],<options>}}

db.<collection>.find({name: {$in:[/^s/, /^j/]}})
db.<collection>.find({name: {$regex: /^DW/, $options: 'i'}})

游标 

    筛选操作返回的实际是一个游标,游标指向一个文档集合,不迭代游标的情况下,文档只会列出20个
    游标遍历完全部文档之后,默认10分钟会自动关闭
    想要改变其默认行为需要用noCursorTimeout()阻止其自动关闭

    var cursor = db.<collection>.find().noCursorTimeout()  
    //使用之后需要手动关闭 
    cursor.close()

游标函数
    cursor.hasNext() 判断游标中有无剩余的文档
    cursor.next() 将游标指向下一个文档
    cursor.forEach(<function>) 遍历游标中指向的文档
    cursor.limit(<number>) 限制游标返回的文档数量,传入0则不进行限制
    cursor.skip(<offset>) 跳过游标指向的文档数量
    cursor.count(<applySkipLimit>) 返回游标文档数量,默认情况下applySkipLimit为false,则不考虑limit和skip之后的数量
    cursor.sort(<document>) document代表排序的要求 -1逆向排序 1正向排序


    cursor.skip()在cursor.limit()之前执行
    cursor.sort()在cursor.skip()和cursor.limit()之前执行


    

文档投影

    db.<collection>.find(<query>, <objection>) 
    利用第二个参数进行文档投影获取想要的字段
    语法:{field: <inclusion>}
    1代表返回字段 0表示不返回字段

    db.<collection>.find({name: 'alice'}, {_id: 0, name: 1})
    其中_id为特殊字段,可为0和1,其余字段有1不能写0, 有0不能写1

    对数组字段中使用投影
    $slice 只返回数组中的部分数据

    db.<collection>.find({}, {_id:0, name: 1, contact: {$slice: 1}})
    db.<collection>.find({}, {_id:0, name: 1, contact: {$slice: -1}}) //返回数组字段中倒数一个元素
    db.<collection>.find({}, {_id:0, name: 1, contact: {$slice: -2}}) //返回数组字段中倒数两个元素

    db.<collection>.find({}, {_id:0, name: 1, contact: {$slice: [1, 3]}}) //$slice数组中第一个元素会进行skip操作,而第二个元素会进行limit操作(跳过第一个元素,返回3个元素)
    db.<collection>.find({}, {_id: 0, contact: {$elemMatch: {$gt: 'CHINA'}}})

更新文档

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

<query> 定义更新文档的筛选条件,与find中query用法一致

<update> 提供文档的更新内容

<options> 声明一些更新操作的参数

只会更新查询出所有文档的第一个文档

更新操作符

$set 更新或者新增字段

{$set: {<field>: <value1>, ...}}

$unset 删除字段

{$unset: {<field>: <value1>, ...}}

$rename 重命名字段

{ $rename: {<field>: <newName1>,<field>: <newName2>} }

$inc 加减字段值

{$inc: {<field>: <value1>, ...}}

$mul 相乘字段值

{$mul: {<field>: <value1>, ...}}

$min 比较字段值取最小值

{$min: {<field>: <value1>, ...}}

$max 比较字段值取最大值

{$max: {<field>: <value1>, ...}}

1. $set 用法

    db.<collection>.update({name: 'jack'}, {
        $set: {
            balance: 3000,
            info: {
                dateOpened: Date.now(),
                branch: 'branch1'
            }
        }
    })
  • 更新内嵌文档字段
    db.<collection>.update({name: 'jack'}, {
        $set: {
            'info.dateOpened': new Date(2018)
        }
    })
    
  • 更新数组内元素
    db.<collection>.update({
        name: 'jack'
    }, {
        $set: {
            'contact.0': 'spark'
        }
    })
    

如果下标不存在且有跳过的元素,则跳过的元素未赋值的元素都将设置为null

  • 添加数组内元素
    db.<collection>.update({name: 'jack'}, {
        $set: {
            'contact.5': 'testIndex'
        }
    })
    

2. $unset 用法

 db.<collection>.update({name: 'jack'}, {
     $unset: {
         'balance': '', //赋值为空不会对操作结果有任何影响
         'contact.5': ''
     }
 })

删除数组内的元素,不会影响数组的长度,而是将其设置为null

3. $rename 用法

如果$rename重命名的字段并不存在,那么对文档内容不会有任何影响。如果新的字段名已经存在,那么原来的字段将被覆盖

 db.<collection>.update({
     name: 'jack'
 }, {
     $rename: {
         bablace: 'name'
     }
 })

$rename过程是先$unset老的字段, 然后$set新的字段

  • 操作内嵌文档

    {
        _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
        name: 'jack',
        info: {
            email: '228436652@qq.com'
        },
        balace: 3000
    }
    

    进行shell操作,使用$rename改变文档中的位置

    db.<collection>.update({
        name: 'jack'
    }, {
        $rename: {
            'info.balance': 'balace',
            'email': 'info.email'
        }
    }) 
    

    原来的数据结构将变成

    {
        _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
        name: 'jack',
        info: {
            balace: 3000
        },
        email: '228436652@qq.com'
    }
    

    注意: $rename不可操作位于数组中的元素

4. $inc与$mul

这两种操作符只能应用在数字字段上,如果$inc操作符用在不存在的字段上,将会把此字段加上并设置value为要加减的值,如果$mul操作符用在不存在的字段上,将会把此字段加上并设置value为0

    db.<collection>.update({
        name: "jack",
    }, {
        $inc: {
            balace: -200 //原来的值减去200
        }
    }) 
    db.<collection>.update({
        name: "jack",
    }, {
        $mul: {
            balace: 2 //原来的值乘以2
        }
    })

5. $min和$max

不只是数字字段可以比较,比如(日期)

    {
        _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
        num: 10
    }

以下shell执行后将被设置为10

    db.<collection>.update({
        num: 10
    }, {
        $min: {
            num: 100
        }
    })

以下shell执行后将被设置为100

    db.<collection>.update({
        num: 10
    }, {
        $max: {
            num: 100
        }
    }) 

扩展

    如果相比较值的类型不一致, 将会按照BSON数据类型排序规则进行比较
            null 最小
            Number
            Symbol, String
            Object
            Array
            binData
            ObjectId
            Boolean
            Date
            Timestamp
            Regular Expresion 最大

数组操作符

$addToSet 向数组中添加元素 {}

$pop 从数组中移除元素,1代表最后一个 -1代表第一个(只能删除最后一个或者第一个)

$pull 从数组中有选择的移除元素

$pullAll 从数组中有选择的移除元素

$push 向数组中添加元素

1. $addToSet

{ $addToSet: { <field>: <value1>, <value2>, ... } }

 db.<collection>.update({name: 'jack'}, {
     $addToSet: {
         contact: 'china'
     }
 })

如果添加的是一个内嵌文档,文档中已有的与添加的完全一致则不会进行覆盖,否则就算是key顺序有变动,依然会进行覆盖

2. $pop

{ $pop: { <field>: < -1| 1>,... } }

 db.<collection>.update({name: 'jack'}, {
     $pop: {
         contact: -1
     }
 }) 
  • 删除内嵌数组

     db.<collection>.update({name: 'jack'}, {
         $pop: {
             "contact.4": -1
         }
     })
    

$pop删除数组中所有元素之后 依然会保留空数组

2. $pull

{$pull: { <field>: <value|condition>}}

 db.<collection>.update({
     name: 'jack'
 }, {
     $pull: {
         "contact": {$regex: /hi/} //不需要用$elemMatch, $pull专门应用于数组字段
     }
 })

如果说数组元素本身就是一个内嵌数组,那我们就可以用$elemMatch来对这些内嵌数组进行筛选

 {
     _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
     name: 'jack',
     info: {
         balace: 3000
     },
     contact: ['china', '17612189352', [111111, 33333333, 2222222]]
     email: '228436652@qq.com'
 }

利用$elemMatch匹配数组字段中的数组元素中的值

 db.<collection>.update({name: 'jack'}, {
     $pull: {
         contact: {
             $elemMatch: {
                 $eq: 2222222
             }
         }
     }
 })
 {
     _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
     name: 'jack',
     info: {
         balace: 3000
     },
     contact: ['china', '17612189352'] //不会保留空数组
     email: '228436652@qq.com' 
 }

3. $pullAll

{$pullAll: { <field>: [<value1>, <value2>, ...]}}

注意$pull比$push命令模糊程度更高,匹配值一定要完全对应,顺序也很重要,但对$pull顺序并不重要

4. $push

{ $push: { field: <value1>, <value2>, ... } }

 db.<collection>.update({name: 'jack'}, {
     $push: {
         newArray: {
             $each: [1,2,3,5]   //如果不用$each 将添加成一个内嵌数组,而是以单独元素进行添加
         }
     }
 })

与$push, $each搭配的操作符

1. $position 插入位置

 db.<collection>.update({name: 'jack'}, {
     $push: {
         newArray: {
             $each: [1,2,3,5],   //如果不用$each 将添加成一个内嵌数组,而是以单独元素进行添加
             $position: 0
         }
     }
 })

当为负数的时候,意思是将在倒数第几个元素前插入

 db.<collection>.update({name: 'jack'}, {
     $push: {
         newArray: {
             $each: [1,2,3,5],   //如果不用$each 将添加成一个内嵌数组,而是以单独元素进行添加
             $position: -1 //将插入在倒数第一个元素之前
         }
     }
 })

2. $sort 排序

    db.<collection>.update({name: 'jack'}, {
        $push: {
            newArray: {
                $each: [1,2,3,5],   //如果不用$each 将添加成一个内嵌数组,而是以单独元素进行添加
                $sort: 1 //1是数组有小到大排序, -1是从大到小倒序排列
            }
        }
    })
  • 插入的内嵌文档进行排序

     db.<collection>.update({name: 'jack'}, {
         $push: {
             newArray: {
                 $each: [{
                     key: 'sort1', 
                     value: 1
                 }, {
                     key: 'sort2',
                     value: 122
                 }],
                 $sort: {value: -1}
             }
         }
     })
    
  • 如果不想插入新的元素, 仅仅想要排序,可以这么写

     db.<collection>.update({name: 'jack'}, {
         $push: {
             newArray: {
                 $each: [],
                 $sort: {value: -1}
             }
         }
     })
    

3. $slice 截取部分数组

    db.<collection>.update({name: 'jack'}, {
        $push: {
            newAarray: {
                $each:[],
                $slice: -3   //保留数组后三个,正数则一样(3就是保留前三个)
            }
        }
    })

他们的执行顺序:$position ==> $sort ==> $slice去执行(书写时候的顺序无所谓的)

占位符

如果mongo中存储一个类似以下这样的文档:

    {
        _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
        name: 'jack',
        info: {
            balace: 3000
        },
        contact: ['china', '17612189352', [111111, 33333333, 2222222]]
        email: '228436652@qq.com'
    }

1. $ 是数组中第一个符合筛选条件的数组元素的占位符

 db.<collection>.update(
     { <array>: <query selector> },
     { <update operator>: {'<array.$>': value} }
 )
 db.<collection>.update({
     name: 'jack',
     contact: 'china'
 }, {
     $set: {
         'contact.$': 'update'
     }
 })
↓↓↓
 {
     _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
     name: 'jack',
     info: {
         balace: 3000
     },
     contact: ['update', '17612189352', [111111, 33333333, 2222222]]
     email: '228436652@qq.com'
 }

2. $[] 更新数组中所有的元素

 db.<collection>.update({
     name: 'jack'
 }, {
     $set: {
         'contact.2.$[]': 'this is test'
     }
 })
↓↓↓ 
 {
     _id: ObjectId("5c9d7cf30c994d956fd6d27d"),
     name: 'jack',
     info: {
         balace: 3000
     },
     contact: ['update', '17612189352', ['this is test', 'this is test', 'this is test']] //内嵌数组内所有元素都变成了'this is test'.
     email: '228436652@qq.com'
 }