MongoDB的聚合查询:aggregat操作详解

前言

在MongoDB中,aggregate 是用于进行数据聚合操作的强大工具。它允许你对文档集合进行多个数据处理步骤,从而进行复杂的数据分析和转换。aggregate的基本思想是将一系列数据处理操作连接在一起,以生成所需的结果

基本语法

aggregate方法的基本语法如下:

js 复制代码
db.collection.aggregate(pipeline, options)
  • collection: 你要对其进行数据聚合的集合(数据表)。
  • pipeline: 一个由多个聚合阶段组成的数组,每个阶段代表一个特定的数据处理操作
  • options(可选):一个对象,用于指定其他选项,例如超时、允许磁盘使用等。

聚合管道中的各个阶段是按顺序执行的,每个阶段可以执行不同的操作,如筛选、变换、分组、排序等。

常用的聚合阶段

以下是一些常用的聚合阶段以及它们的作用:

注:示例中的 db.* 是数据的集合(数据表

1. $match:筛选文档

$match 阶段用于筛选文档,只保留满足指定条件的文档。

js 复制代码
// 仅选择日期在2023年1月1日之后的销售记录
db.sales.aggregate([
    { $match: { date: { $gte: ISODate("2023-01-01") } } } 
])

对比SQL:

js 复制代码
SELECT * FROM sales WHERE date >= '2023-01-01';

2. $project:重塑文档的结构

$project 阶段用于重塑文档的结构,可以包括字段的重命名、字段的新增、字段的删除等。

js 复制代码
db.products.aggregate([
    {
        $project: {
            productName: "$name", // 重命名字段
            price: 1,
            discountPrice: { $multiply: ["$price", 0.9] } // 添加计算字段
        }
    }
])

对比SQL:

js 复制代码
SELECT name AS productName, price, price * 0.9 AS discountPrice FROM products;

3. $group:将文档分组

$group 阶段用于将文档分组,并通常与累积操作(如求和、平均值)一起使用。

js 复制代码
db.sales.aggregate([
    {
        $group: {
            _id: "$product", // 根据产品分组
            totalAmount: { $sum: "$amount" } // 计算总销售额
        }
    }
])

对比SQL:

js 复制代码
SELECT product, SUM(amount) AS totalAmount FROM sales GROUP BY product;

4. $sort:对文档进行排序

$sort 阶段用于对文档进行排序。

js 复制代码
db.contacts.aggregate([
    { $sort: { lastName: 1, firstName: 1 } } // 按姓氏和名字升序排序
])

对比SQL:

js 复制代码
SELECT * FROM contacts ORDER BY lastName, firstName;

5. $limit:限制输出文档的数量

$limit 阶段用于限制输出文档的数量。

js 复制代码
db.logdata.aggregate([
    { $limit: 10 } // 仅获取前10条日志记录
])

对比SQL:

js 复制代码
SELECT * FROM logdata LIMIT 10;

6. $skip:跳过前几个文档

$skip 阶段用于跳过输出结果中的前几个文档。

js 复制代码
db.orders.aggregate([
    { $skip: 5 } // 跳过前5个订单
])

对比SQL:

js 复制代码
SELECT * FROM orders OFFSET 5;

7. $unwind:展开数组字段

$unwind 阶段用于展开数组字段,将包含数组的文档拆分成多个文档。

js 复制代码
db.articles.aggregate([
    { $unwind: "$tags" } // 将文章按标签展开为多个文档
])

对比SQL(SQL中不直接等效,需要使用JOIN来处理类似情况):

js 复制代码
// 假设articles和tags是不同的表
SELECT articles.*, tags.tag
FROM articles
JOIN tags ON articles.id = tags.article_id;

8. ✨ $lookup:执行左连接

$lookup 阶段用于执行左连接,将两个集合的数据合并在一起。

js 复制代码
db.orders.aggregate([
    {
        $lookup: {
            from: "customers", // 关联的集合
            localField: "customerId", // 本集合字段
            foreignField: "_id", // 关联集合字段
            as: "customerInfo" // 存储结果的字段
        }
    }
])

对比SQL(SQL的JOIN操作与MongoDB的$lookup类似):

js 复制代码
SELECT orders.*, customers.*
FROM orders
LEFT JOIN customers ON orders.customerId = customers.id;

这些MongoDB的聚合阶段可以组合使用,以满足特定的数据处理需求。

多个聚合阶段组合使用

假设我们有一个名为 customers 的集合( 客户数据表 )和一个名为 orders 的集合( 订单数据表 ),包含客户订单的信息。

用户信息文档(用户数据)如下:

json 复制代码
// customers 集合
[
 {
   "_id": 101,
   "name": "John Doe",
   "email": "john.doe@example.com"
 },
 // 其他客户信息...
]

订单文档集合( 订单数据 )如下:

json 复制代码
// orders 集合
[
 {
  "_id": 1,
  "customerId": 101, // 客户标识字段
  "orderDate": ISODate("2023-02-15T08:00:00Z"),
  "totalAmount": 500
 },
// 其他订单信息
]

现在我想实现的目标是:将订单与客户联系起来,获取每个客户的总订单金额,并按订单金额降序排列

js 复制代码
// 定义分页参数
const pageSize = 10; // 每页显示的数量
const currentPage = 1; // 当前页码(从 1 开始)

db.orders.aggregate([
    // 阶段 1: 按 customerId 分组订单,计算每个客户的总订单金额
    {
        $group: {
            _id: "$customerId", // 必须
            // 自定义字段使用聚合操作符:$sum 计算(订单表的totalAmount字段的)总和
            totalOrders: { $sum: "$totalAmount" } 
        }
    },
    
    // 阶段 2: 加入客户信息,将订单信息和客户信息合并
    {
        $lookup: {
            from: "customers",   // 客户信息存储在 "customers" 集合中
            localField: "customerId",  // (当前)通过订单的 "customerId" 字段关联
            foreignField: "_id", // (目标)通过客户的 "_id" 字段关联
            as: "customerInfo" // 输出到字段重命名
        }
    },

    // 阶段 3: 将分组后端数据重新格式化输出文档,删除不必要的字段
    {
        $project: {
            _id: 0,  // 不包括 _id 字段
            customerId: "$_id",
            totalOrders: 1,
            customerInfo: 1
        }
    },

    // 阶段 4: 按总订单金额降序排列
    {
        $sort: { totalOrders: -1 }
    },

    // 阶段 5: 添加分页支持 - 跳过 (skip) 某数量的文档
    {
        $skip: (currentPage - 1) * pageSize
    },

    // 阶段 6: 添加分页支持 - 限制 (limit) 返回的文档数量
    {
        $limit: pageSize
    },

    // 阶段 7: 获取总条数 
    // 注意这里不是文档所有条数是查询出来多少条(当前页的文档数量)
    // 总文档数(表所有数据数量)可以单独使用:db.orders.countDocuments()  来查询
    {
        $count: "totalItems"
    }

])

查询结果如下:

json 复制代码
{
    "currentPage": 1, // 当前页码
    "pageSize": 10,   // 每页显示的数量
    "totalItems": 50, // 总条数
    "data": [ // 包含查询结果的数组
        [
            {
                "customerId": 101,
                "totalOrders": 2500,
                "customerInfo": [
                    {
                        "_id": 101,
                        "name": "John Doe",
                        "email": "john.doe@example.com"
                    }
                ]
            },
            {
                "customerId": 102,
                "totalOrders": 1800,
                "customerInfo": [
                    {
                        "_id": 102,
                        "name": "Jane Smith",
                        "email": "jane.smith@example.com"
                    }
                ]
            },
            // 其他客户的信息...
        ]
    ]
}

最后

内容有写得不对或者不明白之处,欢迎指正交流学习哦:v:

完 ~~

参考资料

相关推荐
涡能增压发动积9 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
Wenweno0o9 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
于慨9 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz9 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
swg3213219 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
从前慢丶9 小时前
前端交互规范(Web 端)
前端
tyung9 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald10 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
@yanyu66610 小时前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
CHU72903510 小时前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序