MongoDB聚合查询总结

点击名片关注 阿尘blog,一起学习,一起成长

本文主要整理MongoDB聚合查询相关内容

1、MongoDB聚合查询简介

1.1 聚合查询概述

聚合查询定义

聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。

聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。MongoDB中聚合(aggregate)主要用于处理数据(例如分组统计平均值、求和、最大值等),并返回计算后的数据结果,类似mysql语句中的 求和、分组、多表查询

1.2 聚合查询种类

在MongoDB中,有三种方式计算聚合:Pipeline 和 MapReduce以及单一聚合方法。

Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占用过多的系统内存。

2、聚合管道方法

2.1 聚合管道概述

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;

2.2聚合管道操作

2.2.1 管道步骤与语法

每个管道有多个步骤(stage)组成

①:接受一系列文档(原始数据);

②:每个步骤对这些文档进行一系列运算;

③:结果文档输出给下一个步骤;

聚合管道语法

db.collection.aggregate(
    pipline,
    {option}
);
2.2.1 管道运算符
步骤作用SQL等价
$match过滤WHERE/HAVING
$project投影AS
$sort排序ORDER BY
$group分组GROUP BY
$skip/$limit结果限制SKIP/LIMIT
$lookup左外连接LEFT OUTER JION
$countstage文档计数
$sum求和sum()/count()
$avg求均值avg
$first第一个limit 0,1
$last最后一个
$unwind展开数组N/A
$grephLookup图搜索N/A
$facet/$bucket分页搜索N/A
2.2.3 查询示例

数据准备

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"
    }
])

d7001266b8a184e681d776ffbf42a40b.png

示例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:不显示

结果:

c310e9b5004795773ee9617b2cbff2ed.png

示例3:按照总价降序排列订单

db.orders.aggregate([{
'$sort':{'total':-1}
}
])

9e142c0143adf9022d11f629177531a0.png

示例4:skip的使用,跳过前两个文档再进行排序

db.orders.aggregate([{
... "$skip":2},
... {"$sort":{"total":-1}}
... ])

5f7a2062e3391b7be0bd46b2f31c39bb.png

示例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"} ])

2a3ddf748dbfe26921ca7ea05bc5440b.png

好了管道的操作就简单介绍到这,接下来 看看其他的

3、Map-Reduce

3.1 Map-Reduce概述

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 的并发问题。

69a4fdb85d9d9b09088575d504f517bd.png

3.2 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 } )

9b5d952b3aa1ef9fd4c89e75b94124fd.png

当然也可以使用聚合管道运算符来替代,重写map-reduce操作,不需要自定义函数

db.orders.aggregate([
   { $group: { _id: "$cust_id", value: { $sum: "$price" } } },
   { $out: "agg_sum" }
])

可以用$merge替代$out

接下来我们验证一下

db.agg_sum.find().sort({value:-1})

92115965f0a86749803ff6c7c125e14f.png

完全OK

4、单一聚合方法

4.1 单一聚合方法概述

MongoDB还提供db.collection.estimatedDocumentCount(), db.collecticpn.count(和db.collection.distinct()。所有这些操作都聚合来自单个集合的文档。虽然这些操作提供了对常见聚合过程的简单访问,但它们缺乏聚合管道和map-reduce的灵活性和功能。

6d50afb53f8d4db194d50e3737245985.png

参考文章:

https://docs.mongoing.com/aggregation

https://blog.csdn.net/HelloWorld20161112/article/details/131253505

扫描二维码关注阿尘blog,一起交流学习

0079dc0eff6475da6dfaeaefb28a5849.png

历史文章索引

MongoDB数据库整理

MongoDB常用操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是阿尘呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值