Mongoose 中的 aggregate 用法--随笔

2024-10-21
JavaScript
189

Mongoose 中的 aggregate() 是一种强大的工具,用于对 MongoDB 数据库中的集合进行复杂的数据查询和处理操作。它利用 MongoDB 的 aggregation pipeline 来执行数据聚合操作,比如过滤、分组、排序、投影、连接(lookup)等。下面将详细介绍 aggregate() 的使用方法。

1. 基本语法

Model.aggregate(pipeline, options)
  • pipeline:数组,包含了一系列的数据处理阶段(stages),每个阶段都表示一种数据处理操作。

  • options(可选):一些额外的选项,用于控制聚合操作的行为,比如 allowDiskUse(允许使用磁盘进行操作)。

2. 常用的 Aggregation Pipeline 阶段

  • $match:过滤数据,相当于 find() 中的条件过滤。

  • $group:分组数据,常用于统计或聚合。

  • $project:修改文档的结构,选择性输出字段。

  • $sort:排序数据。

  • $limit:限制返回的文档数。

  • $lookup:关联(连接)另一个集合的数据,类似于 SQL 的 JOIN

  • $unwind:展开数组字段中的每个元素为独立的文档。

3. 基本示例

假设有一个 User 模型,我们来演示一些常见的 aggregate() 用法:

示例 1:$match$group 统计用户的年龄分布

User.aggregate([
  {
    $match: { isActive: true }  // 只选择活跃的用户
  },
  {
    $group: {
      _id: "$age",              // 根据年龄分组
      total: { $sum: 1 }        // 统计每个年龄段的人数
    }
  }
])
.then(result => console.log(result))
.catch(err => console.error(err));

这个管道会先过滤出 isActivetrue 的用户,然后根据 age 进行分组,并统计每个年龄段的用户数。

示例 2:$project$sort 选取特定字段并排序

User.aggregate([
  {
    $project: {                 // 只返回 name 和 age 字段
      name: 1,
      age: 1,
      _id: 0                    // 不返回 _id 字段
    }
  },
  {
    $sort: { age: -1 }          // 按年龄从大到小排序
  }
])
.then(result => console.log(result))
.catch(err => console.error(err));

这个管道将只返回 nameage 字段,并按年龄进行降序排列。

示例 3:$lookup 实现关联查询(JOIN 操作)

假设有两个集合 orderscustomers,你希望将每个订单与对应的客户数据关联起来:

Order.aggregate([
  {
    $lookup: {
      from: "customers",              // 关联的集合名称
      localField: "customerId",       // 当前集合中的字段
      foreignField: "_id",            // 关联集合中的字段
      as: "customerDetails"           // 输出的关联数据放入 customerDetails 数组中
    }
  },
  {
    $unwind: "$customerDetails"       // 将 customerDetails 数组展开成文档
  }
])
.then(result => console.log(result))
.catch(err => console.error(err));

这个聚合管道将 orders 集合中的 customerIdcustomers 集合中的 _id 进行匹配,返回每个订单对应的客户详情。

示例 4:$unwind 处理数组字段

如果文档中的某个字段是一个数组,比如 User 文档中有一个 hobbies 数组,想要对每个兴趣进行统计,可以用 $unwind

User.aggregate([
  {
    $unwind: "$hobbies"                // 将每个兴趣项展开
  },
  {
    $group: {
      _id: "$hobbies",                 // 按兴趣分组
      total: { $sum: 1 }               // 统计每个兴趣的人数
    }
  },
  {
    $sort: { total: -1 }               // 按兴趣人数排序
  }
])
.then(result => console.log(result))
.catch(err => console.error(err));

这个例子会统计所有用户的兴趣爱好,并按兴趣人数降序排列。

4. 使用 Aggregation 中的管道变量

MongoDB 的聚合框架允许你在管道中使用变量,比如 $ 前缀的变量可以引用文档字段。例如,想要统计每个用户年龄是否超过 30 岁:

User.aggregate([
  {
    $project: {
      name: 1,
      isOver30: { $gt: ["$age", 30] }   // 计算年龄是否超过30岁
    }
  }
])
.then(result => console.log(result))
.catch(err => console.error(err));

5. 聚合选项

可以通过在 aggregate() 方法中传入选项来控制聚合行为。例如,当数据量较大时,启用 allowDiskUse 选项允许 MongoDB 使用磁盘进行处理:

User.aggregate(pipeline, { allowDiskUse: true })
.then(result => console.log(result))
.catch(err => console.error(err));

问题记录

  1. 使用 $lookup localField 为字符串,foreignField 为 ObjectId,匹配不上,需要做一定的转换

解决方案 1:将 **article_id** 转换为 **ObjectId**

可以使用 $addFields 将字符串的 article_id 转换为 ObjectId,然后再进行 $lookup 匹配。

js复制代码db.comments.aggregate([
  {
    $addFields: {
      articleObjectId: { $toObjectId: "$article_id" }  // 将字符串 article_id 转换为 ObjectId
    }
  },
  {
    $lookup: {
      from: 'articles',
      localField: 'articleObjectId',                  // 使用转换后的 ObjectId 进行匹配
      foreignField: '_id',
      as: 'articleDetails'
    }
  }
])

这个管道首先通过 $addFields 创建了一个新的字段 articleObjectId,它将 article_id 字符串转换成 ObjectId,然后在 $lookup 阶段使用这个新的字段进行匹配。

解决方案 2:将 **articles** 集合中的 **_id** 转换为字符串

如果你无法修改 comments 集合的 article_id,可以在 $lookup 阶段将 articles 集合中的 _id 转换为字符串来进行匹配:

js复制代码db.comments.aggregate([
  {
    $lookup: {
      from: 'articles',
      let: { articleIdStr: "$article_id" },          // 把 comment 中的 article_id 传递到 $lookup
      pipeline: [
        {
          $addFields: { _idStr: { $toString: "$_id" } } // 将 articles 中的 _id 转换为字符串
        },
        {
          $match: { $expr: { $eq: ["$_idStr", "$$articleIdStr"] } } // 匹配字符串
        }
      ],
      as: 'articleDetails'
    }
  }
])

总结

mongoose.aggregate() 是一种强大的工具,允许你执行复杂的数据操作。通过管道(pipeline)将多个操作组合在一起,你可以轻松进行过滤、排序、分组、关联等操作。结合 MongoDB 的 Aggregation Pipeline,它可以处理大量复杂的查询和聚合需求。