如何使用 MongoDB 聚合查询

发表于 2023-02-17

聚合查询步骤

  • $match
    • 过滤、条件匹配。等价:WHERE 做条件匹配
    • 语法:{$match: {<expression>}}
      • 例子:{$match: {"ord_date": {$gt: ISODate('2013-01-01')}}}
      • 说明:匹配 ord_date 日期大于 2013-01-01 的数据。
  • $project
    • 投影、字段映射。等价:AS 给字段设置别名、新的名称。
    • 语法:{$project: {"<field>": <0 | 1>, "<new_field>": {<expression>}}}
      • 例子:{$project: {"amount": 1, "amountAddId": {$add: ["$amount", "$_id"]}}}
      • 说明:将字段 amount_id 相加并设置为新的字段 amountAddId
  • $sort
    • 排序。等价:ORDER BY 指定字段按升序或者降序排序。
    • 语法:{$sort: {"<field>": <-1 | 1>}}
      • 参数值说明:-1 -- 倒序、1 -- 正序
      • 例子:{$sort: {"amount": 1}}
      • 说明:按 amount 升序排序。
  • $group
    • 分组。等价:GROUP BY
    • 语法:{$group: {"_id": <group_field>, "<new_field>": {<group_operator>}}}
      • 例子:{$group: {"_id": "$status", "total": {$sum: "$amount"}}}
      • 说明:根据字段 status (如果指定的值为 null 或任何常数值,那么会针对所有数据计算)分组并对 amount 求和
  • $skip
    • 结果限制。等价:LIMIT offset
    • 语法:{$skip: <offset_number>}
      • 例子:{$skip: 1}
      • 说明:返回数据跳过前 1 条数据。
  • $limit
    • 结果限制。等价:LIMIT 0, size
    • 语法:{$limit: <page_size_number>}
      • 例子:{$limit: 1}
      • 说明:返回数据只返回 1 条数据。
  • $lookup
    • 左外连接
    • 等价:LEFT JOIN
  • $unwind
    • 展开数组
    • 语法:{$unwind: "$<field>"}
      • 例子:{$unwind: "$item"}
  • $graphLookup
    • 图搜索
  • $bucket
    • 分面搜索(统计查询)
    • 语法:{$bucket: {"groupBy": <expression>, "boundaries": [ <lowerbound1>, ... ], "default": <literal>, "output": {<output1>: { <$accumulator expression> }, ...}}}
      • 区间取值范围时:[index_0, index_1)、[index_1, index_2)、...[index_n, index_m)
      • 例子:{$bucket: {groupBy: "$amount", boundaries: [ 0, 50, 100, 150 ], default: "Other", output: {"count": {$sum: 1}}}}
  • $facet
    • 分面搜索(统计查询)
    • 语法:{$facet: { <outputField>: [ <stage1>, <stage2>, ... ], ...}}
      • 可以整合多个聚合查询
      • 例子:db.order.aggregate([{$facet : {"amountGrou": [{$bucket: {groupBy: "$amount",boundaries: [ 0, 50, 100, 150 ],default: "Other",output: {"count": {$sum: 1}}}}],"statusGrou": [{$bucket: {groupBy: "$status",boundaries: [ "A", "C", "D" ],default: "Other",output: {"count": {$sum: 1}}}}]}}])

聚合查询语法

  • db.collection.aggregate([{<stage>}, ...])

聚合查询执行流程

[{<stage_1>}, {<stage_2>}, {<stage_3>}]
#################################################################
# 根据步骤数组中既定的步骤逐步执行 # 执行完所有步骤得到的就是最终数据 #
#              ||               #              ||               #
#              ||               #              ||               #
#             \  /              #             \  /              #
#              \/               #              \/               #
1{data}
2{data}  --->  <stage_1>  --->  <stage_2>  --->  <stage_3>  --->  结果
3{data}
...

2023021701

聚合查询应用

数据

{ "_id": 1, "cust_id": "abc1", "ord_date": ISODate('2012-11-02T17:04:11.102Z'), "status": "A", "amount": 50, "item": [20, 30] }
{ "_id": 2, "cust_id": "xyz1", "ord_date": ISODate('2013-10-01T17:04:11.102Z'), "status": "A", "amount": 100, "item": [60, 40] }
{ "_id": 3, "cust_id": "xyz1", "ord_date": ISODate('2013-10-12T17:04:11.102Z'), "status": "D", "amount": 25, "item": [20, 5] }
{ "_id": 4, "cust_id": "xyz1", "ord_date": ISODate('2013-10-11T17:04:11.102Z'), "status": "D", "amount": 125, "item": [120, 5] }
{ "_id": 5, "cust_id": "abc1", "ord_date": ISODate('2013-11-12T17:04:11.102Z'), "status": "A", "amount": 25, "item": [10, 15] }

根据日期范围查询订单

db.order.aggregate([
    {
        $match: {
            "ord_date": {
                # ISODate 将字符串转成 Date 对象
                $gt: ISODate('2013-01-01'),
                $lt: ISODate('2013-12-31')
            }
        }
    }
])

统计订单的总金额

db.order.aggregate([
    {
        $group: {
            # 将所有数据作为一个分组
            # 如:_id: "status" 根据状态分组
            "_id": null,
            "total": {
                $sum: "$amount"
            }
        }
    }
])

分页查询:一页 10 条,第二页的数据

db.order.aggregate([
    {
        $skip: 10
    },
    {
        $limit: 10
    }
])

按明细金额(item)查看所有信息

db.order.aggregate([
    {
        ########### 展开数据 ###########
        # { ... "item": [10, 15] }
        #           ||
        #          \  /
        #           \/
        # { ... "item": 10 }
        # { ... "item": 15 }
        $unwind: "$item"
    }
])

按金额排序

db.order.aggregate([
    {
        $sort: {
            "amount": 1
        }
    }
])

只显示订单的金额

db.order.aggregate([
    {
        $project: {
            # 显示 amount 字段
            "amount": 1,
            # 不显示 _id 字段
            "_id": 0,
            # 定义一个 amountAddId = amount + id
            "amountAddId": {
                $add: [
                    "$amount",
                    "$_id"
                ]
            }
        }
    }
])

按金额分段统计订单数量

db.order.aggregate([
    {
        # # 以 amount 做分段统计
        $bucket: {
            groupBy: "$amount",
            boundaries: [ 0, 50, 100, 150 ],
            default: "Other",
            output: {
                "count": {
                    $sum: 1
                }
            }
        }
    }
])

按金额和状态两个维度统计订单数量

db.order.aggregate([
    {
        $facet : {
            # 以 amount 做分段统计
            "amountGrou": [
                {
                    $bucket: {
                        groupBy: "$amount",
                        # 分段数据 Other、[0, 50)、[50, 100)、[100, 150)、Other
                        boundaries: [ 0, 50, 100, 150 ],
                        # 默认 amountGrou _id 值(未落在分段的数据组)
                        default: "Other",
                        output: {
                            # 输出字段
                            "count": {
                                # 加法器累加
                                $sum: 1
                            }
                        }
                    }
                }
            ],
            "statusGrou": [
                {
                    $bucket: {
                        groupBy: "$status",
                        boundaries: [ "A", "C", "D" ],
                        default: "Other",
                        output: {
                            "count": {
                                $sum: 1
                            }
                        }
                    }
                }
            ]
        }
    }
])

统计某个时间段的金额总数

db.order.aggregate([
    {
        $match: {
            "ord_date": {
                # ISODate 将字符串转成 Date 对象
                $gt: ISODate('2013-01-01'),
                $lt: ISODate('2013-12-31')
            }
        }
    },
    {
        $group: {
            # 将所有数据作为一个分组
            # 如:_id: "status" 根据状态分组
            # 如:—id: null 或者 0 等常数,将会计算所有数据
            "_id": null,
            "total": {
                $sum: "$amount"
            }
        }
    },
    {
        $project: {
            # 不显示 _id 字段
            "_id": 0
        }
    }
])