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:

完 ~~

参考资料

相关推荐
GHUIJS1 分钟前
【vue3】vue3.5
前端·javascript·vue.js
-seventy-11 分钟前
对 JavaScript 原型的理解
javascript·原型
&白帝&29 分钟前
uniapp中使用picker-view选择时间
前端·uni-app
白总Server35 分钟前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
谢尔登35 分钟前
Babel
前端·react.js·node.js
ling1s36 分钟前
C#基础(13)结构体
前端·c#
卸任42 分钟前
使用高阶组件封装路由拦截逻辑
前端·react.js
计算机学姐1 小时前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py
lxcw1 小时前
npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED
前端·npm·node.js
秋沐1 小时前
vue中的slot插槽,彻底搞懂及使用
前端·javascript·vue.js