点击名片关注 阿尘blog,一起学习,一起成长
本文主要整理MongoDB聚合查询相关内容
聚合查询定义
聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。
聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。MongoDB中聚合(aggregate)主要用于处理数据(例如分组统计平均值、求和、最大值等),并返回计算后的数据结果,类似mysql语句中的 求和、分组、多表查询
在MongoDB中,有三种方式计算聚合:Pipeline 和 MapReduce以及单一聚合方法。
Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占用过多的系统内存。
MongoDB 的Aggregation framework是以数据处理管道的概念为蓝本的。文档进入多阶段管道,将文档转换为聚合结果。MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理,管道操作是可以重复的。
示例
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
第一阶段:$match阶段按status字段过滤文档,并将status等于"A"的文档传递到下一阶段。第二阶段:$group阶段按cust_id字段将文档分组,以计算每个唯一值cust_id的金额总和。
以上查询语句相当于如下MySQL语句
select cust_id as _id, sum(amount) as total from orders
where status like "%A%"
group by cust_id;
每个管道有多个步骤(stage)组成
①:接受一系列文档(原始数据);
②:每个步骤对这些文档进行一系列运算;
③:结果文档输出给下一个步骤;
聚合管道语法
db.collection.aggregate(
pipline,
{option}
);
步骤 | 作用 | SQL等价 |
---|---|---|
$match | 过滤 | WHERE/HAVING |
$project | 投影 | AS |
$sort | 排序 | ORDER BY |
$group | 分组 | GROUP BY |
$skip/$limit | 结果限制 | SKIP/LIMIT |
$lookup | 左外连接 | LEFT OUTER JION |
$count | stage文档计数 | |
$sum | 求和 | sum()/count() |
$avg | 求均值 | avg |
$first | 第一个 | limit 0,1 |
$last | 最后一个 | |
$unwind | 展开数组 | N/A |
$grephLookup | 图搜索 | N/A |
$facet/$bucket | 分页搜索 | N/A |
数据准备
db.orders.insert(
[
{
"street": "西兴街道",
"city": "杭州",
"state": "浙江省",
"country": "中国",
"zip": "24344-1715",
"phone": "18866668888",
"name": "李白",
"userId": "3573",
"orderDate": "2019-01-02 03:20:08.805",
"status": "completed",
"shippingFee": 8.00,
"orderLines": [{
"product": "iPhone5",
"sku": "2001",
"qty": 1,
"price": 100.00,
"cost": 100.00
},
{
"product": "iPhone5s",
"sku": "2002",
"qty": 2,
"price": 200.00,
"cost": 400.00
},
{
"product": "iPhone6",
"sku": "2003",
"qty": 1,
"price": 300.00,
"cost": 300.00
},
{
"product": "iPhone6s",
"sku": "2004",
"qty": 2,
"price": 400.00,
"cost": 800.00
},
{
"product": "iPhone8",
"sku": "2005",
"qty": 2,
"price": 500.00,
"cost": 1000.00
}
],
"total": 2600
},
{
"street": "长河街道",
"city": "杭州",
"state": "浙江省",
"country": "中国",
"zip": "24344-1716",
"phone": "18866668881",
"name": "杜甫",
"userId": "3574",
"orderDate": "2019-02-02 13:20:08.805",
"status": "completed",
"shippingFee": 5.00,
"orderLines": [{
"product": "iPhone5",
"sku": "2001",
"qty": 1,
"price": 100.00,
"cost": 100.00
},
{
"product": "iPhone5s",
"sku": "2002",
"qty": 2,
"price": 200.00,
"cost": 400.00
},
{
"product": "iPhone6",
"sku": "2003",
"qty": 1,
"price": 300.00,
"cost": 300.00
},
{
"product": "iPhone6s",
"sku": "2004",
"qty": 2,
"price": 400.00,
"cost": 800.00
},
{
"product": "iPhone8",
"sku": "2005",
"qty": 2,
"price": 500.00,
"cost": 1000.00
}
],
"total": 2600
},
{
"street": "浦沿街道",
"city": "杭州",
"state": "浙江省",
"country": "中国",
"zip": "24344-1717",
"phone": "18866668882",
"name": "王安石",
"userId": "3575",
"orderDate": "2019-03-02 14:20:08.805",
"status": "completed",
"shippingFee": 20.00,
"orderLines": [{
"product": "iPhone5",
"sku": "2001",
"qty": 1,
"price": 100.00,
"cost": 100.00
},
{
"product": "iPhone5s",
"sku": "2002",
"qty": 2,
"price": 200.00,
"cost": 400.00
},
{
"product": "iPhone6",
"sku": "2003",
"qty": 1,
"price": 300.00,
"cost": 300.00
},
{
"product": "iPhone6s",
"sku": "2004",
"qty": 2,
"price": 400.00,
"cost": 800.00
},
{
"product": "iPhone12 ProMax",
"sku": "2006",
"qty": 1,
"price": 1500.00,
"cost": 1500.00
}
],
"total": 3100
},
{
"street": "长庆街道",
"city": "杭州",
"state": "浙江省",
"country": "中国",
"zip": "24344-1717",
"phone": "18866668883",
"name": "苏东坡",
"userId": "3576",
"orderDate": "2019-04-02 15:20:08.805",
"status": "completed",
"shippingFee": 10.00,
"orderLines": [
{
"product": "iPhone6s",
"sku": "2004",
"qty": 2,
"price": 400.00,
"cost": 800.00
},
{
"product": "iPhone12 ProMax",
"sku": "2006",
"qty": 1,
"price": 1500.00,
"cost": 1500.00
}
],
"total": 2300
}
]
)
示例1:查询订单总价大于1000的订单总数
步骤:
1、用$match匹配订单总额大于1000的传入下一个stage
2、用$count计算管道中剩余的文档数(2),并将其赋值给count字段
db.orders.aggregate([
{
"$match": {
"orderLines.cost": {
"$gt": 1000
}
}
},
{
"$count": "count"
}
])
示例2:查询2019年第一季度(1月1日-3月31日)已完成订单(completed)的订单总金额和订单总数
db.orders.aggregate(
[
{
'$match': {
'status': 'completed',
'orderDate': {
'$gte': '2019-01-01',
'$lt': '2019-04-01'
}
}
}, {
'$group': {
'_id': null,
'total': {
'$sum': '$total'
},
'shippingFee': {
'$sum': '$shippingFee'
},
'count': {
'$sum': 1
}
}
}, {
'$project': {
'count':'$count',
'grandTotal': {
'$add': [
'$total', '$shippingFee'
]
},
'_id': 0
}
}
]
)
代码说明:
第一步:用$match匹配相关数据传给下一个stage
第二部:用$group进行分组求和,分组id=null表示把当前stage剩余所有文档分成一个组求和其中'$sum': '$total'表示对total变量进行求和,'$sum': 1相当于$count,对当前文档进行计数
第三部:用project重新将需要的结果表达出来,其中'_id': 0,就是过滤掉id,不然输出结果还是有id
默认情况下_id字段是被包含的。1:显示、0:不显示
结果:
示例3:按照总价降序排列订单
db.orders.aggregate([{
'$sort':{'total':-1}
}
])
示例4:skip的使用,跳过前两个文档再进行排序
db.orders.aggregate([{
... "$skip":2},
... {"$sort":{"total":-1}}
... ])
示例5:$unwind的使用,以之前grade为例,将achen的乘积展开
测试数据:
db.grade.insert([
{ "_id" : "20230714", "name" : "Achen", "Grade" : [{ "object":"Chinese" ,"score": 100}, {"object":"Math","score": 99}, {"object":"English" ,"score": 100 } ]},
{ "_id" : "20230715", "name" : "qiqi", "Grade" : [{ "object":"Chinese" ,"score":100}, {"object":"Math","score": 100},{"object":"English" ,"score": 101 }] },
{ "_id" : "20230716", "name" : "nannan", "Grade" : [{ "object":"Chinese" ,"score":100}, {"object":"Math","score": 100},{"object":"English" ,"score": 99 }] }])
查询:
db.grade.aggregate([ {"$match":{ "name":"Achen"}}, {"$unwind":"$Grade"} ])
好了管道的操作就简单介绍到这,接下来 看看其他的
map-reduce操作有两个阶段:一个map阶段,它处理每个文档并为每个输入文档发出一个或多个对象,以及将map操作的输出组合在一起的reduce阶段。可选地,map-reduce可以具有最终化阶段以对结果进行最终修改。与其他聚合操作一样,map-reduce可以指定查询条件以选择输入文档以及对结果排序和限制。Map-reduce使用自定义JavaScript函数来执行map和reduce操作,以及可选的finalize操作。与聚合管道相比,自定义JavaScript提供了很大的灵活性,但通常情况下map-reduce比聚合管道效率低,而且更复杂。Map-reduce可以在分片集合sharded collection上运行。Map-reduce操作也可以输出到分片集合。有关详细信息,请参阅聚合管道和分片集合和Map-Reduce和Sharded CCollections
从 MongoDB 2.4 开始,在 map-reduce 操作中无法访问某些mongoshell 函数和属性。 MongoDB 2.4 还支持多个 JavaScript 操作以在同一时间运行。在 MongoDB 2.4 之前,JavaScript code 在单个线程中执行,引发了 map-reduce 的并发问题。
数据准备
db.orders.insertMany([
{ _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" },
{ _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" },
{ _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"},
{ _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }
])
需求:返回每个客户的总价格
定义map函数来处理每个输出文档
下面定义一个函数返回客户id和他的价格(需要JavaScript基础)
var mapFunction1 = function(){
emit(this.cust_id,this.price);
};
接下来定义定义一个函数通过传入参数,计算每个客户的价格和
var sumFunction = function(cust_id,sumprice){
return Array.sum(sumprice);
};
最后将order使用上述两个函数进行map-reduce
db.orders.mapReduce(
mapFunction1,
sumFunction,
{ out: "map_sum_example" }
)
这样会将结果输出到一个新的指定集合map_sum_example
下面我们就可以进行用这个集合进行查询
db.map_sum_example.find().sort( { _id: 1 } )
当然也可以使用聚合管道运算符来替代,重写map-reduce操作,不需要自定义函数
db.orders.aggregate([
{ $group: { _id: "$cust_id", value: { $sum: "$price" } } },
{ $out: "agg_sum" }
])
可以用$merge替代$out
接下来我们验证一下
db.agg_sum.find().sort({value:-1})
完全OK
MongoDB还提供db.collection.estimatedDocumentCount(), db.collecticpn.count(和db.collection.distinct()。所有这些操作都聚合来自单个集合的文档。虽然这些操作提供了对常见聚合过程的简单访问,但它们缺乏聚合管道和map-reduce的灵活性和功能。
参考文章:
https://docs.mongoing.com/aggregation
https://blog.csdn.net/HelloWorld20161112/article/details/131253505
扫描二维码关注阿尘blog,一起交流学习
历史文章索引
MongoDB数据库整理
MongoDB常用操作