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

Mongoose聚合查询按关联字段过滤返回空数组问题排查

Mongoose聚合查询返回空数组的原因及解决方案

问题场景

我编写了两个Mongoose聚合函数,第二个函数从真实存在的通知中提取出lounge_id,但将该ID传入第一个函数时,始终返回空数组。相关代码与Model定义如下:

第一个函数(获取指定 lounge 的通知)

const getNotificationsForLounge = async (lounge_id) => {
  try {
    const notifications = await Notification.aggregate([
      {
        $match: {
          "lounge_join_request.lounge": lounge_id,
          lounge_join_request: { $ne: null },
        },
      },
    ]);
    console.log("🚀 ~ notifications:", notifications);
    return notifications;
  } catch (error) {
    console.log("🚀 ~ error:", error);
  }
};

第二个函数(获取随机带 lounge 加入请求的通知并调用第一个函数)

const getNotificationWithRandomLoungeJoinRequest = async () => {
  try {
    const notification = await Notification.aggregate([
      {
        $match: {
          category: "LOUNGE_JOIN_REQUEST",
          lounge_join_request: { $ne: null },
        },
      },
      { $sample: { size: 1 } },
      {
        $lookup: {
          from: "lounge_join_requests",
          localField: "lounge_join_request",
          foreignField: "_id",
          as: "lounge_join_request",
        },
      },
      { $project: { _id: 0, lounge_id: "$lounge_join_request.lounge" } },
    ]);
    const lounge_id = notification[0].lounge_id[0];
    console.log(
      "🚀 ~ file: loungeServices.js:663 ~ getNotificationWithRandomLoungeJoinRequest ~ lounge_id:",
      lounge_id
    );

    return await getNotificationsForLounge(lounge_id);
  } catch (error) {
    console.log("🚀 ~ error:", error);
  }
};

相关Model定义

Notification.js

const NotificationSchema = new Schema({
  lounge_join_request: {
    type: Schema.Types.ObjectId,
    ref: "lounge_join_requests",
    default: null,
  },
});

module.exports = Notification = mongoose.model(
  "notification",
  NotificationSchema
);

LoungeJoinRequest.js

const LoungeJoinRequestSchema = new Schema({
  lounge: {
    type: Schema.Types.ObjectId,
    ref: "lounge",
    required: true,
  },
});

module.exports = LoungeJoinRequest = mongoose.model(
  "lounge_join_requests",
  LoungeJoinRequestSchema
);

Lounge.js

const LoungeSchema = new Schema(
  {
    name: {
      type: String,
    },
  }
);

module.exports = Channel = mongoose.model("lounge", LoungeSchema);

问题原因

核心错误在于第一个函数的查询逻辑:

  • Notification集合中的lounge_join_request字段仅存储**lounge_join_requests集合的ObjectId引用**,并非包含lounge字段的嵌套文档。你直接用"lounge_join_request.lounge"作为匹配条件,相当于在Notification文档中查找一个不存在的嵌套字段,自然返回空数组。
  • 第二个函数能拿到正确的lounge_id,是因为它通过$lookup关联了lounge_join_requests集合,获取到完整的请求文档后才提取出lounge字段,但第一个函数没有做任何关联查询。

解决方案

提供两种可行的修正方案:

方案一:聚合查询+关联查询

const getNotificationsForLounge = async (lounge_id) => {
  try {
    const notifications = await Notification.aggregate([
      // 先过滤掉没有关联请求的通知
      {
        $match: {
          lounge_join_request: { $ne: null }
        }
      },
      // 关联lounge_join_requests集合获取完整请求数据
      {
        $lookup: {
          from: "lounge_join_requests",
          localField: "lounge_join_request",
          foreignField: "_id",
          as: "lounge_join_request"
        }
      },
      // 过滤出关联请求中lounge匹配目标ID的记录
      {
        $match: {
          "lounge_join_request.lounge": lounge_id
        }
      },
      // 将关联的数组转为单个对象(一对一关联场景)
      {
        $unwind: "$lounge_join_request"
      }
    ]);
    console.log("🚀 ~ notifications:", notifications);
    return notifications;
  } catch (error) {
    console.log("🚀 ~ error:", error);
  }
};

方案二:Mongoose populate+find查询(更简洁)

const getNotificationsForLounge = async (lounge_id) => {
  try {
    const notifications = await Notification.find({
      lounge_join_request: { $ne: null }
    })
    .populate({
      path: "lounge_join_request",
      // 仅关联匹配目标lounge_id的请求
      match: { lounge: lounge_id }
    })
    // 过滤掉关联后请求为空的记录(即不匹配的通知)
    .then(docs => docs.filter(doc => doc.lounge_join_request !== null));
    
    console.log("🚀 ~ notifications:", notifications);
    return notifications;
  } catch (error) {
    console.log("🚀 ~ error:", error);
  }
};

补充说明

  • 方案一适合复杂查询场景,纯聚合逻辑性能更优;方案二利用Mongoose的ORM特性,代码更简洁易读。
  • 第二个函数中notification[0].lounge_id[0]的写法是正确的,因为$lookup返回的是数组,若想简化可在聚合阶段添加$unwind操作,避免多层数组取值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:55:02