1、文章概述
1.1 基本概念
MongoDB是一种非关系型数据库,数据最终存储为BSON(Binary JSON)类型。MongoDB包含三个重要逻辑概念:数据库、集合和文档,与关系型数据库相关概念映射如下图:
1.2 数据准备
1.2.1 逻辑设计
bookdb数据库包含library与book两个集合,相关字段说明如下图:
1.2.2 新增数据
// 创建数据库use bookdb// 新增图书馆db.library.insertMany([{"_id": 1,"library_name": "图书馆_1"},{"_id": 2,"library_name": "图书馆_2"},{"_id": 3,"library_name": "图书馆_3"}])// 新增图书db.book.insertMany([{"_id": 1,"book_type": 1,"book_name": "图书_1","book_price": 10,"book_count": 100,"borrowers": [{"name": "张三","id": 100}, {"name": "李四","id": 200}],"library_id": 1},{"_id": 2,"book_type": 2,"book_name": "图书_2","book_price": 20,"book_count": 100,"borrowers": [{"name": "张三","id": 100}],"library_id": 2},{"_id": 3,"book_type": 1,"book_name": "图书_3","book_price": 30,"book_count": 100,"borrowers": [{"name": "张三","id": 100}, {"name": "王五","id": 300}],"library_id": 2}])
2、聚合概念
2.1 基本语法
MongoDB聚合语法第一个参数是管道:文档在上个管道处理完后传递给下个管道,第二个参数是选项:设置聚合操作特性
db.collection.aggregate(pipeline, options)
2.2 聚合管道
常用聚合管道有以下九种类型:
$project:对文档进行投影
$limit:输出管道内前N个文档
$skip:跳过管道内前N个文档
$sort:对文档进行排序
$out:输出管道中文档
$match:对文档进行筛选
$unwind:铺平文档中的数组字段
$lookup:对文档进行查询
$group:对文档进行分组
3、聚合实例
3.1 project
1表示展示某字段
0表示不展示某字段
借阅人编号和姓名拆分成两个数组
db.book.aggregate({$project: {"_id": 0,"book_name": 1,"borrowerIds": "$borrowers.id","borrowerNames": "$borrowers.name"}})---------------------------------------------------------// 1{"book_name": "图书_1","borrowerIds": [100,200],"borrowerNames": ["张三","李四"]}// 2{"book_name": "图书_2","borrowerIds": [100],"borrowerNames": ["张三"]}// 3{"book_name": "图书_3","borrowerIds": [100,300],"borrowerNames": ["张三","王五"]}
3.2 limit
只展示一个投影结果
db.book.aggregate([{$project: {"_id": 0,"book_name": 1,"borrowerIds": "$borrowers.id","borrowerNames": "$borrowers.name"}},{$limit: 1}])---------------------------------------------------------// 1{"book_name": "图书_1","borrowerIds": [100,200],"borrowerNames": ["张三","李四"]}
3.3 skip
跳过一个且只展示一个投影结果
db.book.aggregate([{$project: {"_id": 0,"book_name": 1,"borrowerIds": "$borrowers.id","borrowerNames": "$borrowers.name"}},{$skip: 1},{$limit: 1}])---------------------------------------------------------// 1{"book_name": "图书_2","borrowerIds": [100],"borrowerNames": ["张三"]}
3.4 sort
db.book.aggregate([{$project: {"_id": 1,"book_name": 1,"library_id": 1}},{$sort: {"library_id": - 1, // 降序"_id": 1 // 升序}}])---------------------------------------------------------// 1{"_id": 2,"book_name": "图书_2","library_id": 2}// 2{"_id": 3,"book_name": "图书_3","library_id": 2}// 3{"_id": 1,"book_name": "图书_1","library_id": 1}
MongoDB内存排序有100M限制,如果排序数据过多需要设置选项allowDiskUse=true,表示数据可以写入临时文件进行排序
db.book.aggregate([{$project: {"_id": 1,"book_name": 1,"library_id": 1}},{$sort: {"library_id": - 1,"_id": 1}}], {allowDiskUse: true})
3.5 out
投影结果输出到新集合
db.book.aggregate([{$project: {"_id": 0,"book_name": 1,"borrowerIds": "$borrowers.id","borrowerNames": "$borrowers.name"}},{$out: "newCollection"}])db.newCollection.find()---------------------------------------------------------// 1{"_id": ObjectId("62bec0636f9c37787b9590b9"),"book_name": "图书_1","borrowerIds": [100,200],"borrowerNames": ["张三","李四"]}// 2{"_id": ObjectId("62bec0636f9c37787b9590ba"),"book_name": "图书_2","borrowerIds": [100],"borrowerNames": ["张三"]}// 3{"_id": ObjectId("62bec0636f9c37787b9590bb"),"book_name": "图书_3","borrowerIds": [100,300],"borrowerNames": ["张三","王五"]}
3.6 match
where book_name = xxx
db.book.aggregate([{$match: {"book_name": "图书_2"}},{$project: {"_id": 1,"book_name": 1,"library_id": 1}}])---------------------------------------------------------// 1{"_id": 2,"book_name": "图书_2","library_id": 2}where library_id = 2 and price > 15db.book.aggregate([{$match: {$and: [{"library_id": 2},{"book_price": {$gt: 25}}]}},{$project: {"_id": 1,"book_name": 1,"library_id": 1,"book_price": 1}}])---------------------------------------------------------// 1{"_id": 3,"book_name": "图书_3","book_price": 30,"library_id": 2}
3.7 unwind
文档按照借阅人数组铺平
includeArrayIndex表示索引
db.book.aggregate([{$unwind: {path: "$borrowers",includeArrayIndex: "idx"}}])---------------------------------------------------------// 1{"_id": 1,"book_type": 1,"book_name": "图书_1","book_price": 10,"book_count": 100,"borrowers": {"name": "张三","id": 100},"library_id": 1,"idx": NumberLong("0")}// 2{"_id": 1,"book_type": 1,"book_name": "图书_1","book_price": 10,"book_count": 100,"borrowers": {"name": "李四","id": 200},"library_id": 1,"idx": NumberLong("1")}// 3{"_id": 2,"book_type": 2,"book_name": "图书_2","book_price": 20,"book_count": 100,"borrowers": {"name": "张三","id": 100},"library_id": 2,"idx": NumberLong("0")}// 4{"_id": 3,"book_type": 1,"book_name": "图书_3","book_price": 30,"book_count": 100,"borrowers": {"name": "张三","id": 100},"library_id": 2,"idx": NumberLong("0")}// 5{"_id": 3,"book_type": 1,"book_name": "图书_3","book_price": 30,"book_count": 100,"borrowers": {"name": "王五","id": 300},"library_id": 2,"idx": NumberLong("1")}
3.8 lookup
查询图书馆有哪些图书
lookup可以实现连表查询
MongoDB 3.4之前聚合语法:
- from:待关联集合【book】
- localField: 本集合关联键【library】
- foreignField:待关联键【book】
- as:待关联集合数据【book】
```sql
db.library.aggregate([
{
} ])$lookup:{from: "book",localField: "_id",foreignField: "library_id",as: "books_info"}
// 1 { “_id”: 1, “library_name”: “图书馆_1”, “books_info”: [ { “_id”: 1, “book_type”: 1, “book_name”: “图书_1”, “book_price”: 10, “book_count”: 100, “borrowers”: [ { “name”: “张三”, “id”: 100 }, { “name”: “李四”, “id”: 200 } ], “library_id”: 1 } ] }
// 2 { “_id”: 2, “library_name”: “图书馆_2”, “books_info”: [ { “_id”: 2, “book_type”: 2, “book_name”: “图书_2”, “book_price”: 20, “book_count”: 100, “borrowers”: [ { “name”: “张三”, “id”: 100 } ], “library_id”: 2 }, { “_id”: 3, “book_type”: 1, “book_name”: “图书_3”, “book_price”: 30, “book_count”: 100, “borrowers”: [ { “name”: “张三”, “id”: 100 }, { “name”: “王五”, “id”: 300 } ], “library_id”: 2 } ] }
// 3 { “_id”: 3, “library_name”: “图书馆_3”, “books_info”: [ ] }
MongoDB 3.4之后聚合语法:- **from:待关联集合【book】**- **let:声明本集合字段在管道使用**- **pipeline:操作管道**```sqldb.library.aggregate([{$lookup:{from: "book",let: {"lid": "$_id"},pipeline: [{$match: {$expr: {$and: [{$eq: ["$$lid", "$library_id"]}]}}}],as: "books_info"}}])---------------------------------------------------------// 1{"_id": 1,"library_name": "图书馆_1","books_info": [{"_id": 1,"book_type": 1,"book_name": "图书_1","book_price": 10,"book_count": 100,"borrowers": [{"name": "张三","id": 100},{"name": "李四","id": 200}],"library_id": 1}]}// 2{"_id": 2,"library_name": "图书馆_2","books_info": [{"_id": 2,"book_type": 2,"book_name": "图书_2","book_price": 20,"book_count": 100,"borrowers": [{"name": "张三","id": 100}],"library_id": 2},{"_id": 3,"book_type": 1,"book_name": "图书_3","book_price": 30,"book_count": 100,"borrowers": [{"name": "张三","id": 100},{"name": "王五","id": 300}],"library_id": 2}]}// 3{"_id": 3,"library_name": "图书馆_3","books_info": [ ]}
新增价格大于20查询条件
db.library.aggregate([{$lookup:{from: "book",let: {"lid": "$_id"},pipeline: [{$match: {$expr: {$and: [{$eq: ["$$lid", "$library_id"]},{$gt: ["$book_price", 20]}]}}}],as: "books_info"}}])---------------------------------------------------------// 1{"_id": 1,"library_name": "图书馆_1","books_info": [ ]}// 2{"_id": 2,"library_name": "图书馆_2","books_info": [{"_id": 3,"book_type": 1,"book_name": "图书_3","book_price": 30,"book_count": 100,"borrowers": [{"name": "张三","id": 100},{"name": "王五","id": 300}],"library_id": 2}]}// 3{"_id": 3,"library_name": "图书馆_3","books_info": [ ]}
3.9 group
3.9.1 简单统计
_id:图书类型作为分组键
count:每个类型有多少种书
db.book.aggregate([{$group: {_id: "$book_type",count: {$sum: 1}}}])---------------------------------------------------------// 1{"_id": 2,"count": 1}// 2{"_id": 1,"count": 2}
3.9.2 复杂统计
_id:图书类型作为分组键
type_count:每个类型有多少种书
type_book_count:每个类型有多少本书
minTotalPrice:每个类型总价最小值
maxTotalPrice:每个类型总价最大值
totalPrice:每个类型总价
avgPrice:每个类型平均价
db.book.aggregate([{$group: {_id: "$book_type",type_count: {$sum: 1},type_book_count: {$sum: "$book_count"},minTotalPrice: {$min: {$multiply: ["$book_price", "$book_count"]}},maxTotalPrice: {$max: {$multiply: ["$book_price", "$book_count"]}},totalPrice: {$sum: {$multiply: ["$book_price", "$book_count"]}},avgPrice: {$avg: "$book_price"}}}])---------------------------------------------------------// 1{"_id": 2,"type_count": 1,"type_book_count": 100,"minTotalPrice": 2000,"maxTotalPrice": 2000,"totalPrice": 2000,"avgPrice": 20}// 2{"_id": 1,"type_count": 2,"type_book_count": 200,"minTotalPrice": 1000,"maxTotalPrice": 3000,"totalPrice": 4000,"avgPrice": 20}
3.9.3 空分组键
_id:空分组键表示统计全量数据
db.book.aggregate([{$group: {_id: null,type_count: {$sum: 1},type_book_count: {$sum: "$book_count"},minTotalPrice: {$min: {$multiply: ["$book_price", "$book_count"]}},maxTotalPrice: {$max: {$multiply: ["$book_price", "$book_count"]}},totalPrice: {$sum: {$multiply: ["$book_price", "$book_count"]}},avgPrice: {$avg: "$book_price"}}}])---------------------------------------------------------// 1{"_id": null,"type_count": 3,"type_book_count": 300,"minTotalPrice": 1000,"maxTotalPrice": 3000,"totalPrice": 6000,"avgPrice": 20}
4 、总结
第一介绍了MongoDB与关系型数据库关系,并且准备本文需要的测试数据,第二介绍了聚合语法和聚合管道相关概念,第三通过实例介绍了如何使用聚合操作。
