You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将MongoDB的findOne与aggregate查询合并为单个查询?

合并MongoDB查询:将findOne与aggregate合并为单个查询

问题描述

原本使用两个独立查询获取用户贡献的Launch数据:

const user = await User.findOne({ userId }).lean() || []

const contributions = await Launch.aggregate([
  { $sort: { addedAt: -1 } },
  { $limit: 10 },
  {
    $match: {
      _id: { $in: user.contributions }
    }
  },
  {
    $addFields: {
      activity: 'contribution',
      launchName: '$name',
      launchId: '$_id',
      date: '$addedAt',
      content: '$description'
    }
  }
])

希望将两个查询合并为单个聚合查询,但尝试的$lookup写法报错:

const contributions = await Launch.aggregate([
  { $sort: { addedAt: -1 } },
  { $limit: 10 },
  {
    $lookup: {
      from: 'user',
      let: { id: $user.contributions },
      pipeline: [
        { $match: { $expr: { $in: [$_id, $$user.contributions] } } }
      ],
      localField: '_id',
      foreignField: 'userId',
      as: 'user'
    }
  },
  {
    $addFields: {
      activity: 'contribution',
      launchName: '$name',
      launchId: '$_id',
      date: '$addedAt',
      content: '$description'
    }
  }
])

错误分析

你的写法存在三个核心问题:

  • $lookup模式冲突:同时使用了常规关联(localField/foreignField)和自定义管道(pipeline)模式,这两种模式不能共存,必须二选一。
  • 变量语法错误:let中定义变量时,字段名需要用字符串包裹(如'$contributions'),引用变量时要使用$$变量名格式,你之前的$user.contributions和$$user.contributions写法均不正确。
  • 逻辑顺序颠倒:原逻辑是先获取用户的贡献列表,再匹配Launch并排序取前10;但你尝试的写法先对所有Launch排序取前10,再关联用户,导致结果范围错误。

正确解决方案

方案1:从User集合发起聚合(推荐)

这种方式更贴合原逻辑,先定位目标用户,再关联并筛选Launch数据:

// 替换成实际的用户ID变量
const targetUserId = 'xxx';

const contributions = await User.aggregate([
  // 1. 匹配目标用户
  { $match: { userId: targetUserId } },
  // 2. 关联Launch集合,筛选用户贡献的条目并处理格式
  {
    $lookup: {
      from: 'launches', // 注意:这里是Launch集合的实际名称,通常为复数形式
      let: { contributionIds: '$contributions' },
      pipeline: [
        // 匹配当前Launch的_id在用户贡献列表中
        { $match: { $expr: { $in: ['$_id', '$$contributionIds'] } } },
        // 按添加时间倒序排序
        { $sort: { addedAt: -1 } },
        // 取前10条
        { $limit: 10 },
        // 转换为需要的字段格式
        {
          $addFields: {
            activity: 'contribution',
            launchName: '$name',
            launchId: '$_id',
            date: '$addedAt',
            content: '$description'
          }
        }
      ],
      as: 'contributions'
    }
  },
  // 3. 展开贡献数组,将每个贡献转为独立文档
  { $unwind: '$contributions' },
  // 4. 将贡献数据作为根文档,移除用户相关字段
  { $replaceRoot: { newRoot: '$contributions' } }
])

方案2:从Launch集合发起聚合

如果需要从Launch集合开始查询,可以先获取用户的贡献列表再匹配:

const targetUserId = 'xxx';

const contributions = await Launch.aggregate([
  // 1. 获取目标用户的贡献列表
  {
    $lookup: {
      from: 'users',
      let: { userId: targetUserId },
      pipeline: [
        { $match: { $expr: { $eq: ['$userId', '$$userId'] } } },
        { $project: { contributions: 1, _id: 0 } }
      ],
      as: 'userData'
    }
  },
  // 2. 展开用户数据数组
  { $unwind: '$userData' },
  // 3. 匹配当前Launch在用户贡献列表中
  { $match: { $expr: { $in: ['$_id', '$userData.contributions'] } } },
  // 4. 排序并取前10
  { $sort: { addedAt: -1 } },
  { $limit: 10 },
  // 5. 转换字段格式
  {
    $addFields: {
      activity: 'contribution',
      launchName: '$name',
      launchId: '$_id',
      date: '$addedAt',
      content: '$description'
    }
  },
  // 6. 移除用户数据字段
  { $project: { userData: 0 } }
])

内容的提问来源于stack exchange,提问作者mcool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:35:28