MongoDB复杂查询 聚合框架

前言

前面已经对MongoDB基础的增删改查进行了分享,在增删改方面基本已经够用了,但是查询方面是远远不够的,find方法只能传入一些查询条件。涉及到稍微复杂点的查询就无法实现了,如分组、排序、映射(起别名)等这些操作的时候就没有办法实现。

所以我接下来给同志们分享专一用于MongoDB复杂查询的框架 ----- 聚合框架

注意: 文档和JSON对象基本上是一个意思,我会在解释的时候说是JSON对象,便于理解,在写标题的写的是文档,文档是正式的概念

如果对基础的查询条件不熟悉的同志,可以查看一下我前面写的文章

MongoDB基础增删改查命令-CSDN博客

目录

聚合框架介绍

框架入口

聚合管道

常见的操作符

常用操作符的操作参数介绍

[条件过滤 match](#条件过滤 match)

操作参数格式

查询操作符

逻辑操作符

案例解析

[字段选择 project](#字段选择 project)

操作参数格式

案例解析

[分组统计 group](#分组统计 group)

操作参数格式

常见的聚合函数

案例解析

[排序 sort](#排序 sort)

操作参数格式

案例解析

[限制结果数 limit和跳过文档skip](#限制结果数 limit和跳过文档skip)

操作参数格式

案例解析

[左连接 lookup和拆分数组字段为多个文档unwind](#左连接 lookup和拆分数组字段为多个文档unwind)

操作参数格式

案例解析


聚合框架介绍

MongoDB的聚合框架是一个基于管道概念的强大工具,它允许用户对集合中的文档进行一系列复杂的数据转换、过滤、分组、排序和计算操作,最终生成所需的结果。

框架入口

复制代码
db.集合.aggregate(聚合管道)

聚合管道

下面我介绍一下aggregate方法的参数,聚合管道

这个参数是一个数组,数组里面记录的是每个聚合阶段,每个聚合阶段把文档进行过滤之后,把结果交给下一个聚合阶段,这就是聚合管道的执行流程

具体的聚合管道的格式

复制代码
[{操作符1: 操作参数1},{操作符2,操作参数2},{操作符3,操作参数3}...]

常见的操作符

MongoDB的操作符和SQL中的关键字对比,快速入门,类比使用

功能 MongoDB 操作符 SQL 关键字/语法
条件过滤 $match WHERE
字段选择 $project SELECT 字段列表
分组统计 $group GROUP BY + 聚合函数
排序 $sort ORDER BY
限制结果数 $limit LIMIT
跳过文档 $skip OFFSET
左外连接 $lookup LEFT JOIN
将数组字段拆分为多条文档 $unwind 配合MongoDB左连接后匹配多个右表数据,此时的匹配到的右表数据是以数组的形式添加到左表中的,$unwinds是为了展开这些右表数据的,是MongoDB特有的

常用操作符的操作参数介绍

条件过滤 $match

条件过滤 $match 类比 SQL中的where关键字

操作参数格式

$match操作符的操作参数格式

复制代码
{查询条件}

查询条件本质就是逻辑操作符和查询操作符的嵌套

查询操作符和逻辑操作符常用列表如下

查询操作符

操作符 说明 示例
$eq 等于 age: { $eq: 25 }
$ne 不等于 age: { $ne: 25 }
$gt 大于 age: { $gt: 25 }
$lt 小于 age: { $lt: 25 }
$in 包含在数组中 name: { $in: ["Alice", "Bob"] }
$regex 正则匹配 name: { $regex: /^A/ }

逻辑操作符

| 操作符 | 说明 | 示例 |
| and | 与 | 显式写法 and: [{查询操作符1}, {查询操作符2}] (推荐)隐式写法 查询操作符1,查询操作符2 |
| or | 或 | or: [{查询操作符1}, {查询操作符2}] |
| not | 单个取反 | not: {查询操作符} |

$nor 多个取反 $nor: [{查询操作符1}, {查询操作符2}]

如果and和or都出现的情况下,推荐使用隐式and和显式or搭配使用,清晰明了

案例解析

复制代码
#案例: 查询status状态是completed 并且 total_price大于500

# MongoDB的语句
{ $match: { status: { $eq: "completed" }, total_price: { $gt: 500 } } }

#对应的SQL语句
WHERE status = 'completed' AND total_price > 500

字段选择 $project

字段选择 $project 类比 select的字段列表

操作参数格式

$project操作符的操作参数格式

复制代码
# 大概的格式如下
{key1: value2, key1: value2...}



# key: value 键值对的写法有三种,保留字段和排除字段不能同时使用
# _id字段是默认保留的,所以_id字段是可以在保留字段语句中显示排除的
# 第一种 保留字段,1代表保留字段
字段名: 1

# 第二种 排除字段,0代表排除字段
字段名: 0

# 第三种 保留字段并对字段重命名
字段新名字: "$原字段名"

案例解析

复制代码
# 案例: 在查询一张表的时候,保留order_id、cust_id、uname字段,并且把uname重命名为username
# MongoDB的语句
{$project: {_id: 0, order_id: 1, cust_id: 1, username: "$uname"}}

# 对应的SQL语句
SELECT order_id, cust_id, uname as username

分组统计 $group

分组统计 $group 类比 GROUP BY + 聚合函数

操作参数格式

$group操作符的操作参数格式

复制代码
{
  $group: {
    _id: {$分组字段1,$分组字段2,$分组字段3...},
    聚合字段名称1: {聚合函数1},
    聚合字段名称2: {聚合函数2},
    聚合字段名称3: {聚合函数3},
    ...
  }
}


#分组字段只有一个的时候,形式为:      _id: $分组字段

常见的聚合函数

功能 MongoDB 操作符 SQL 关键字/语法
计数 $sum: 1 COUNT(*)
字段名的总和 $sum: $字段名 SUM(字段名)
字段名的平均值 $avg: $字段名 AVG(字段名)
字段名的最大值 $max: $字段名 MAX(字段名)

案例解析

复制代码
#案例: 在表中使用country字段进行分组统计,统计出每组的行数,每组的平均年龄
# MongoDB的语句
{
    $group: {
      _id: "$country",
      totalUsers: { $sum: 1 }, 
      avgAge: { $avg: "$age" }
    }
  }


# 对应的SQL语句
select country,count(*) as totalUsers,AVG(age) as avgAge
from 表名
group by country

排序 $sort

排序 $sort 类比 order by

操作参数格式

$sort``操作符的操作参数格式

复制代码
# key是指排序的字段名  value中1是升序,-1是降序
{ $sort: { key1: value1, key2: value2, ... } }

案例解析

复制代码
# 案例,对查询到的数据先age字段降序排序,再按price字段升序排序
# MongoDB的语句
{ $sort: { age: -1, price: 1 } }

# SQL语句
ORDER BY age DESC, price ASC

# SQL中的用法比较固定,只能在数据查询的结果中进行排序
# 但是MongoDB中聚合查询是管道过滤的形式,只考虑拿着现有的数据进行排序过滤,因此可以在任何一步进行操作

限制结果数 limit和跳过文档skip

限制结果数 $limit 类比 limit

跳过文档数 $skip 类比 offset

操作参数格式

$limit``操作符和$skip的操作参数格式

复制代码
{ $limit: 返回前多少JSON对象的个数 }

{ $skip: 跳过前多少JSON对象的个数 }

# 这两个数据常常搭配用于分页

# 在SQL中类似
limit 返回前多少行的个数
offset 跳过前多少行的个数

SQL分页形式是 limit 返回前多少行的个数 offset 跳过前多少行的个数
经常简写为 limit 返回前多少行的个数 跳过前多少行的个数

案例解析

复制代码
# 例如: SQL语句,跳过前20行,取接下来的10行
limit 10 20    

# 写成MongoDB语句,是有顺序的,需要先跳过,在取值
{$skip: 20},{$limit: 10}

左连接 lookup和拆分数组字段为多个文档unwind

左连接 $lookup 类比 left join

拆分数组字段为多个文档$unwind 则是为了后续连接之后的数组拆分的过程

这两个是搭配使用的

操作参数格式

lookup操作符和unwind操作符的格式

复制代码
# $lookup的格式
# MongoDB语句
{
    $lookup: {
      from: "右表名称",
      localField: "左表的关联字段",
      foreignField: "右表关联字段",
      as: "匹配到的多条右表JSON对象的数组名称,这里自己自定义取名"
    }
}

对应的SQL语句
from 左表名称 left join 右表名称 on 左表的关联字段=右表关联字段


# $unwind的格式
# 基础格式
{ $unwind: "需要展开的数组名称" }

# 带有选项的格式
{ $unwind: {path: "需要展开的数组名称", 选项键值对}}

# 常用的选项键值对
preserveNullAndEmptyArrays: true     # 控制是否保留空数组或 null 值的文档(默认 false,即过滤掉)
includeArrayIndex: "添加存储原数组元素的索引位置,自定义字段名称"
等等

案例解析

复制代码
# 案例: 左表匹配多条右表的例子

# 场景说明
# 左表(orders):一条订单可能关联多个客户(例如"团购订单"或"家庭订单")
# 右表(customers):多个客户可能属于同一个订单(通过 order_id 关联)

# 原始数据
# orders 集合(左表)
    # { "_id": 1, "order_id": 1001, "amount": 500 }  // 订单 1001
    # { "_id": 2, "order_id": 1002, "amount": 300 }  // 订单 1002(无关联客户)
# customers 集合(右表)
    # { "_id": 200, "name": "Alice", "email": "alice@example.com", "order_id": 1001 }  // 属于订单 1001
    # { "_id": 201, "name": "Bob", "email": "bob@example.com", "order_id": 1001 }    // 属于订单 1001
    # { "_id": 202, "name": "Charlie", "email": "charlie@example.com", "order_id": 1001 } // 属于订单 1001
    # { "_id": 203, "name": "Dave", "email": "dave@example.com", "order_id": 999 }   // 无关联订单

# 执行 $lookup(左表匹配右表的多条记录)
    db.orders.aggregate([
      {
        $lookup: {
          from: "customers",
          localField: "order_id",  // 左表的 order_id
          foreignField: "order_id", // 右表的 order_id
          as: "customer_info"      // 匹配的客户信息存入数组,自定义的名称
        }
      }
    ])

# 匹配结果(customer_info 是数组,可能包含多条客户数据)
    #订单 1001 匹配到 3 个客户(Alice、Bob、Charlie)
    {
      "_id": 1,
      "order_id": 1001,
      "amount": 500,
      "customer_info": [
        { "_id": 200, "name": "Alice", "email": "alice@example.com", "order_id":1001 },
        { "_id": 201, "name": "Bob", "email": "bob@example.com", "order_id":1001 },
        { "_id": 202, "name": "Charlie", "email": "charlie@example.com", "order_id":1001}
      ]
    }

    #订单 1002 无匹配客户
    {
      "_id": 2,
      "order_id": 1002,
      "amount": 300,
      "customer_info": []  // 空数组
    }

# 执行 $unwind(展开多条客户记录)
    {
      $unwind: {
        path: "$customer_info",
        preserveNullAndEmptyArrays: true  // 保留无匹配的订单
      }
    }
# 匹配结果(订单 1001 被展开为 3 条文档,每条对应一个客户)
    # 订单 1001 + Alice
    {
      "_id": 1,
      "order_id": 1001,
      "amount": 500,
      "customer_info": { "_id": 200, "name": "Alice", "email": "alice@example.com", "order_id": 1001 }
    }

    # 订单 1001 + Bob
    {
      "_id": 1,
      "order_id": 1001,
      "amount": 500,
      "customer_info": { "_id": 201, "name": "Bob", "email": "bob@example.com", "order_id": 1001 }
    }

    # 订单 1001 + Charlie
    {
      "_id": 1,
      "order_id": 1001,
      "amount": 500,
      "customer_info": { "_id": 202, "name": "Charlie", "email": "charlie@example.com", "order_id": 1001 }
    }

    #订单 1002 无匹配(保留原文档,customer_info 为 null 或不存在)
    {
      "_id": 2,
      "order_id": 1002,
      "amount": 300,
      "customer_info": null
    }
# 最终 $project(提取关键字段)
    {
      $project: {
        order_id: 1,
        amount: 1,
        customer_name: "$customer_info.name",
        customer_email: "$customer_info.email"
      }
    }
# 最终的处理结果
    # 订单 1001 + Alice
    { "order_id": 1001, "amount": 500, "customer_name": "Alice", "customer_email": "alice@example.com" }

    # 订单 1001 + Bob
    { "order_id": 1001, "amount": 500, "customer_name": "Bob", "customer_email": "bob@example.com" }

    # 订单 1001 + Charlie
    { "order_id": 1001, "amount": 500, "customer_name": "Charlie", "customer_email": "charlie@example.com" }

    #订单 1002 无匹配
    { "order_id": 1002, "amount": 300, "customer_name": null, "customer_email": null }
相关推荐
写写闲篇儿3 小时前
Python+MongoDB高效开发组合
linux·python·mongodb
scheduleTTe5 小时前
SQL增查
数据库·sql
浮生带你学Java6 小时前
2025Java面试题及答案整理( 2025年 7 月最新版,持续更新)
java·开发语言·数据库·面试·职场和发展
期待のcode6 小时前
图片上传实现
java·前端·javascript·数据库·servlet·交互
小毛驴8507 小时前
redis 如何持久化
数据库·redis·缓存
吗喽1543451887 小时前
用python实现自动化布尔盲注
数据库·python·自动化
MC皮蛋侠客7 小时前
Ubuntu安装Mongodb
linux·mongodb·ubuntu
hbrown7 小时前
Flask+LayUI开发手记(十一):选项集合的数据库扩展类
前端·数据库·python·layui
云资源服务商9 小时前
探索阿里云DMS:解锁高效数据管理新姿势
数据库·阿里云·oracle·云计算