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

求助:Mongoose MongoDB中无法在嵌套数组内查询userID

Mongoose关联查询问题:如何筛选关联团队成员包含指定用户的患者

问题描述

  • 存在两个MongoDB模型:Patients和Teams
  • Patients模型的team字段为mongoose.Types.ObjectId,用于关联Teams模型
  • Teams模型的members字段是对象数组,每个成员包含id、name、role属性
  • 需求:查询所有满足以下任一条件的患者:
    1. 患者自身的members数组包含当前登录用户的userID
    2. 患者关联的Teams的members数组包含当前登录用户的userID
  • 尝试过$elemMatch、$in及聚合操作,均未得到预期结果

现有代码

API端点代码

const getAllPatients = async (req, res) => {
  try {
    const { page = 1, limit = 10, userID, tags = null, ...rest } = req.query;
    const filter = {
      ...(tags && { tags: { $in: tags.split(",") } }),
      ...(!isEmptyObject(rest) && { $or: getSearchFilter(rest) }),
    };

    // 问题所在的查询块
    const countPromise = await Patients.count({
      $or: [
        {
          members: { $all: [userID] }, // 此查询可正常匹配患者自身members数组
        },
        {
          "team.members": { $all: [userID] }, // 试图查询关联团队的members数组,但无效
        },
      ],
    });

    const patientsPromise = await Patients.find({
      $or: [
        {
          members: { $all: [userID] }, // 正常工作
        },
        // {
        //   "team.members": { $all: [userID] }, // 未达到预期效果
        // },
      ],
      ...filter
    });

    const result = await Promise.all([countPromise, patientsPromise]);
    const count = result[0];
    const patients = result[1];

    return res.status(200).json({
      data: patients,
      totalPages: Math.ceil(count / limit),
      currentPage: parseInt(page),
    });
  } catch (e) {
    console.log(e.message,"error");
    return res.status(500).json({ error: e.message || `Something went wrong` });
  }
};

Patient模型Schema

const mongoose = require("mongoose");

const patientsSchema = new mongoose.Schema(
  {
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
    gender: {
      type: String,
    },
    birthday: {
      type: String,
    },
    email: {
      type: String,
      required: true,
      validate: {
        validator: function (v) {
          return /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(v);
        },
        message: (props) => `Email ${props.value} is not valid`,
      },
    },
    phone: {
      type: String,
      required: true,
    },
    address1: {
      type: String,
    },
    address2: {
      type: String,
    },
    state: {
      type: String,
    },
    city: {
      type: String,
    },
    zip: {
      type: String,
    },
    tags: {
      type: [mongoose.Types.ObjectId],
      ref: "Tags",
    },
    members: {
      type: [String],
    },
    team: {
      type: mongoose.Types.ObjectId,
      ref: "Teams",
    },
    chatBotEnabled: { type: Boolean, default: true },
    smsConsent: { type: Boolean, default: false },
    path: {},
  },
  {
    timestamps: true,
  }
);

module.exports = mongoose.model("Patients", patientsSchema);

Team模型Schema

const mongoose = require("mongoose");

const teamsSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
      unique: true
    },
    members: {
      type: [{
        "id": String,
        "name": String,
        "role": String
    }],
      required: true,
    },
    description:{
      type:String,
      required: true
    }
  },
  {
    timestamps: true,
  }
);

module.exports = mongoose.model("Teams", teamsSchema);

解决方案

核心原因

原代码中"team.members"查询无效,是因为Patients的team字段仅存储关联团队的ObjectId,而非嵌入完整的团队文档,无法直接通过嵌套路径跨集合查询字段。

方法1:先筛选符合条件的团队ID,再查询患者

此方法性能更优,适合数据量较大的场景:

const getAllPatients = async (req, res) => {
  try {
    const { page = 1, limit = 10, userID, tags = null, ...rest } = req.query;
    const filter = {
      ...(tags && { tags: { $in: tags.split(",").map(id => mongoose.Types.ObjectId(id)) } }),
      ...(!isEmptyObject(rest) && { $or: getSearchFilter(rest) }),
    };

    // 第一步:获取所有包含当前用户的团队ID
    const userTeams = await Teams.find(
      { "members.id": userID },
      { _id: 1 }
    ).lean();
    const teamIds = userTeams.map(team => team._id);

    // 构建最终查询条件
    const query = {
      $or: [
        { members: { $all: [userID] } }, // 匹配患者自身成员
        { team: { $in: teamIds } } // 匹配关联团队在目标列表中的患者
      ],
      ...filter
    };

    // 并行执行计数和查询
    const [count, patients] = await Promise.all([
      Patients.count(query),
      Patients.find(query).populate('team') // 可选:自动关联团队信息
    ]);

    return res.status(200).json({
      data: patients,
      totalPages: Math.ceil(count / limit),
      currentPage: parseInt(page),
    });
  } catch (e) {
    console.log(e.message,"error");
    return res.status(500).json({ error: e.message || `Something went wrong` });
  }
};

方法2:使用聚合管道关联查询

一次聚合完成跨集合关联与筛选,适合需要同时获取团队信息的场景:

const getAllPatients = async (req, res) => {
  try {
    const { page = 1, limit = 10, userID, tags = null, ...rest } = req.query;
    const skip = (page - 1) * limit;

    // 构建聚合管道
    const pipeline = [
      // 关联Teams集合,获取团队信息
      {
        $lookup: {
          from: "teams",
          localField: "team",
          foreignField: "_id",
          as: "teamInfo"
        }
      },
      // 将teamInfo数组转为单个对象(每个患者仅关联一个团队)
      { $unwind: { path: "$teamInfo", preserveNullAndEmptyArrays: true } },
      // 筛选符合条件的患者
      {
        $match: {
          $or: [
            { members: { $all: [userID] } },
            { "teamInfo.members.id": userID }
          ],
          ...(tags && { tags: { $in: tags.split(",").map(id => mongoose.Types.ObjectId(id)) } }),
          ...(!isEmptyObject(rest) && { $or: getSearchFilter(rest) })
        }
      },
      // 分页处理
      { $skip: skip },
      { $limit: parseInt(limit) },
      // 可选:移除不需要的字段,或保留teamInfo返回团队信息
      // { $project: { teamInfo: 0 } }
    ];

    // 执行聚合查询
    const patients = await Patients.aggregate(pipeline);
    // 计算总数量
    const countPipeline = pipeline.filter(stage => stage.$match).concat([{ $count: "total" }]);
    const countResult = await Patients.aggregate(countPipeline);
    const count = countResult.length > 0 ? countResult[0].total : 0;

    return res.status(200).json({
      data: patients,
      totalPages: Math.ceil(count / limit),
      currentPage: parseInt(page),
    });
  } catch (e) {
    console.log(e.message,"error");
    return res.status(500).json({ error: e.message || `Something went wrong` });
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:57:07