MongoDB学习专题(二)核心操作
目录
1、复合主键
2、逻辑操作符匹配
1、$not
2、$and
2、$or
3、字段匹配
4、排序&分页查询&正则表达式查询
1、指定排序
2、分页查询
1、处理分页问题 – 巧分页
2、处理分页问题 – 避免使用 count
3、正则表达式匹配查询
6、更新文档
1、更新文档的操作符
2、更新单个文档
3、增减数值
4、更新多个文档
5、upsert命令
6、无操作符 update的 replace 语义
7、findAndModify命令
7、删除文档
1、使用 remove 删除文档
2、使用 delete 删除文档(推荐)
3、返回被删除文档
1、复合主键
可以使用文档作为复合主键
db.demeDoc.insert(
{_id: { product_name: 1, // 主键字段1product_type: 2 // 主键字段2},supplierId: "001", // 普通字段create_Time: new Date() // 自动生成时间戳
})
注意复合主键,字段顺序换了,会当做不同的对象被创建,即使内容完全一致
2、逻辑操作符匹配
$not(非(¬)) : 匹配筛选条件不成立的文档
$and(与(∧)): 匹配多个筛选条件同时满足的文档
$or (或(∨)): 匹配至少一个筛选条件成立的文档
$nor(或非(¬∨)) : 匹配多个筛选条件全部不满足的文档
首先构造一组数据:
db.members.insertMany([
{nickName:"曹操",points:1000
},
{nickName:"刘备",points:500
}
]);
1、$not
用法:
{ field: { $not : { operator-expression} }}
积分不小于100 的
db.members.find({points: { $not: { $lt: 100}}} );
$not 也会筛选出并不包含查询字段的文档,相当于只会在自己领域用排除法
2、$and
用法
{ $and : [ condition expression1 , condition expression2 ..... ]}
1、昵称等于曹操, 积分大于 1000 的文档
db.members.find({$and : [ {nickName:{ $eq : "曹操"}}, {points:{ $gt:1000}}]});
2、当作用在不同的字段上时 可以省略 $and(nickName和points就不同)
db.members.find({nickName:{ $eq : "曹操"}, points:{ $gt:1000}});
3、当作用在同一个字段上面时可以简化为
db.members.find({points:{ $gte:1000, $lte:2000}});
2、$or
用法
{ $or :{ condition1, condition2, condition3,... }}
db.members.find(
{$or : [ {nickName:{ $eq : "刘备"}}, {points:{ $gt:1000}}]}
);
如果都是等值查询的话, $or 和 $in 结果是一样的
3、字段匹配
$exists:匹配包含查询字段的文档
{ field : {$exists: <boolean>} }
比如上方的:
db.members.find({points:{$exists:true}});返回结果:
[{_id: ObjectId('6891a8c446238faadaeec4ae'),nickName: '曹操',points: 1000},{_id: ObjectId('6891a8c446238faadaeec4af'),nickName: '刘备',points: 500}
]
4、排序&分页查询&正则表达式查询
1、指定排序
在 MongoDB 中使用 sort() 方法对数据进行排序
指定按收藏数(favCount)降序返回(1 为升序排列,而 -1 是用于降序排列)
db.books.find({type:"travel"}).sort({favCount:-1})
当同时应用 sort, skip, limit 时 ,应用的顺序为 sort, skip, limit
2、分页查询
- skip 用于指定跳过记录数
- limit 用于限定返回结果数量
可以在执行find命令的同时指定skip、limit参数,以此实现分页的功能。
比如,假定每页大小为8条,查询第3页的book文档:
db.books.find().skip(8).limit(4)
1、处理分页问题 – 巧分页
数据量大的时候,应该避免使用skip/limit形式的分页。
替代方案:基于 游标分页使用查询条件+唯一排序条件;
例如:
第一页:
db.posts.find({}).sort({_id: 1}).limit(20);第二页:
db.posts.find({_id: {$gt: }}).sort({_id: 1}).limit(20);第三页:
db.posts.find({_id: {$gt: }}).sort({_id: 1}).limit(20);
2、处理分页问题 – 避免使用 count
尽可能不要计算总页数,特别是数据量大和查询条件不能完整命中索引时。
考虑以下场景:假设集合总共有 1000w 条数据,在没有索引的情况下考虑以下查询:
4.0+ 弃用方案:
(警告被丢弃:DeprecationWarning: Collection.count() is deprecated. Use countDocuments or estimatedDocumentCount)
//查询集合 coll中字段x等于100的文档,并限制返回结果最多 50 条
db.coll.find({x: 100}).limit(50); //统计集合 coll中字段x等于100的文档总数
db.coll.count({x: 100});
- 前者只需要遍历前 n 条,直到找到 50 条 x=100 的文档即可结束;
- 后者需要遍历完 1000w 条找到所有符合要求的文档才能得到结果。 为了计算总页数而进行的 count() 往往是拖慢页面整体加载速度的原因
方案1:使用 countDocuments()
(精确计数)
// 计算所有文档数量(等效于旧版 count())
db.members.countDocuments({});// 计算满足条件的文档数量(如 status: "A")
db.members.countDocuments({ status: "A" });
方案2:使用聚合管道 $count
// 统计跳过 1 条后限制 1 条的文档数(实际应用场景较少)
db.members.aggregate([{ $skip: 1 },{ $limit: 1 },{ $count: "total" }
]);
方案3:使用 estimatedDocumentCount()
(快速估算)
快速估算集合文档总数(忽略 skip/limit)
db.members.estimatedDocumentCount();
3、正则表达式匹配查询
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
//使用正则表达式查找type包含 so 字符串的book
db.books.find({type:{$regex:"so"}})//或者
db.books.find({type:/so/})
6、更新文档
update命令对指定的数据进行更新,命令的格式如下:
db.collection.update(query,update,options)
- query:更新的查询条件;
- update:更新的动作及新的内容;
- options:更新的选项
可选:
- upsert:如果不存在update的记录,是否插入新的记录。默认false,不插入
- multi: 是否按条件查询出的多条记录全部更新。 默认false,只更新找到的第一条记录
- writeConcern :决定一个写操作落到多少个节点上才算成功。
1、更新文档的操作符
操作符 | 格式 | 描述 |
$set | {$set:{field:value}} | 指定一个键并更新值,若键不存在则创建 |
$unset | {$unset : {field : 1 }} | 删除一个键 |
$inc | {$inc : {field : value } } | 对数值类型进行增减 |
$rename | {$rename : {old_field_name : new_field_name } } | 修改字段名称 |
$push | { $push : {field : value } } | 将数值追加到数组中,若数组不存在则会进行初始化 |
$pushAll | {$pushAll : {field : value_array }} | 追加多个值到一个数组字段内 |
$pull | {$pull : {field : _value } } | 从数组中删除指定的元素 |
$addToSet | {$addToSet : {field : value } } | 添加元素到数组中,具有排重功能 |
$pop | {$pop : {field : 1 }} | 删除数组的第一个或最后一个元素 |
2、更新单个文档
// 将 _id=1 的文档的 price 改为 699
db.products.update({ _id: 1 },{ $set: { price: 699 } }
)
3、增减数值
// 将 _id=2 的 stock 减少 2
db.products.update({ _id: 2 },{ $inc: { stock: -2 } }
)
4、更新多个文档
使用multi选项
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新
// 将所有 tags 包含 "electronics" 的文档 price 增加 50
db.products.update({ tags: "electronics" },{ $inc: { price: 50 } },{ multi: true }
)
update命令的选项配置较多,为了简化使用还可以使用一些快捷命令:
- updateOne:更新单个文档。
// 将第一个 name 为 "Phone" 的文档的 price 改为 699 db.products.updateOne({ name: "Phone" },{ $set: { price: 699 } } );
- updateMany:更新多个文档。
// 将所有 price < 800 的文档的 stock 增加 5 db.products.updateMany({ price: { $lt: 800 } },{ $inc: { stock: 5 } } );
- replaceOne:替换单个文档。
// 将 _id: 1 的文档完全替换为新内容 db.products.replaceOne({ _id: 1 },{ name: "Smartphone", price: 799, color: "black" } );
5、upsert命令
upsert是一种特殊的更新,其表现为如果目标文档不存在,则执行插入命令。
旧指令:
// 如果 _id=3 不存在,则插入新文档
db.products.update({ _id: 3 },{ $set: { name: "Tablet", price: 299 } },{ upsert: true }
)
现代指令:
// 使用 updateOne 替代
db.books.updateOne({ title: "my book" },{ $set: { tags: ["nosql", "mongodb"],type: "none",author: "fox" }},{ upsert: true }
);
结果:
{acknowledged: true, // 操作已确认insertedId: ObjectId('6891c426ad6f8a20a28faffc'), // 新插入文档的 _idmatchedCount: 0, // 匹配的文档数(0 表示未找到)modifiedCount: 0, // 修改的文档数upsertedCount: 1 // 插入的文档数
}
6、无操作符 update
的 replace 语义
update命令中的更新描述(update)通常由操作符描述,如果更新描述中不包含任何操作符,那么MongoDB会实现文档的replace语义
db.books.update({title:"my book"},{justTitle:"my first book"}
)
-
若找到匹配文档,会用
{ justTitle: "my first book" }
完全替换原文档(仅保留_id
)。 -
原文档的其他字段(如
tags
,author
)将被删除!
7、findAndModify命令
findAndModify兼容了查询和修改指定文档的功能,findAndModify只能更新单个文档
//将某个book文档的收藏数(favCount)加1
db.books.findAndModify({query:{_id:ObjectId("61caa09ee0782536660494dd")},update:{$inc:{favCount:1}}
})
该操作会返回符合查询条件的文档数据,并完成对文档的修改。
默认情况下,findAndModify会返回修改前的“旧”数据。如果希望返回修改后的数据,则可以指定new选项
db.books.findAndModify({query:{_id:ObjectId("61caa09ee0782536660494dd")},update:{$inc:{favCount:1}},new: true
})
与findAndModify语义相近的命令如下:
- findOneAndUpdate:更新单个文档并返回更新前(或更新后)的文档。
- findOneAndReplace:替换单个文档并返回替换前(或替换后)的文档。
7、删除文档
1、使用 remove 删除文档
- remove 命令需要配合查询条件使用;
- 匹配查询条件的文档会被删除;
- 指定一个空文档条件会删除所有文档;
示例:
db.user.remove({age:28}) // 删除age 等于28的记录
db.user.remove({age:{$lt:25}}) // 删除age 小于25的记录
db.user.remove( { } ) // 删除所有记录 db.user.remove() //报错
remove命令会删除匹配条件的全部文档,如果希望明确限定只删除一个文档,则需要指定justOne参数,命令格式如下:
db.collection.remove(query,justOne)
例如:删除满足type:novel条件的首条记录
db.books.remove({type:"novel"},true)
// 只删除第一条 name 以 "B" 开头的文档
db.users.remove({ name: /^B/ }, { justOne: true }
);
2、使用 delete 删除文档(推荐)
官方推荐使用 deleteOne() 和 deleteMany() 方法删除文档,语法格式如下:
db.books.deleteMany ({}) //删除集合下全部文档
db.books.deleteMany ({ type:"novel" }) //删除 type等于 novel 的全部文档
db.books.deleteOne ({ type:"novel" }) //删除 type等于novel 的一个文档
注意: remove、deleteMany等命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使用drop命令会更加高效
3、返回被删除文档
remove、deleteOne等命令在删除文档后只会返回确认性的信息,如果希望获得被删除的文档的完整信息,则可以使用findOneAndDelete命令
在 books
集合里,找到第一个类型是“novel”的书,然后把它删掉。在删除之后,它会返回这个被删除的文档
//在 books 集合里,找到第一个类型是“novel”的书,然后把它删掉。在删除之后,它会返回这个被删除的文档
db.books.findOneAndDelete({type:"novel"})
除了在结果中返回删除文档,findOneAndDelete命令还允许定义“删除的顺序”,即按照指定顺序删除找到的第一个文档
//根据 favCount 字段的值从小到大进行排序,删除排在最前面的那一个文档
db.books.findOneAndDelete({type:"novel"},{sort:{favCount:1}})
remove、deleteOne等命令只能按默认顺序删除,利用这个特性,findOneAndDelete可以实现队列的先进先出。