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'
}