MongoDB复杂查询 聚合框架
前言
前面已经对MongoDB基础的增删改查进行了分享,在增删改方面基本已经够用了,但是查询方面是远远不够的,find方法只能传入一些查询条件。涉及到稍微复杂点的查询就无法实现了,如分组、排序、映射(起别名)等这些操作的时候就没有办法实现。
所以我接下来给同志们分享专一用于MongoDB复杂查询的框架 ----- 聚合框架
注意: 文档和JSON对象基本上是一个意思,我会在解释的时候说是JSON对象,便于理解,在写标题的写的是文档,文档是正式的概念
如果对基础的查询条件不熟悉的同志,可以查看一下我前面写的文章
MongoDB基础增删改查命令-CSDN博客
目录
聚合框架介绍
框架入口
聚合管道
常见的操作符
常用操作符的操作参数介绍
条件过滤 $match
操作参数格式
查询操作符
逻辑操作符
案例解析
字段选择 $project
操作参数格式
案例解析
分组统计 $group
操作参数格式
常见的聚合函数
案例解析
排序 $sort
操作参数格式
案例解析
限制结果数 $limit和跳过文档$skip
操作参数格式
案例解析
左连接 $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_idforeignField: "order_id", // 右表的 order_idas: "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 }