求助:Mongoose MongoDB中无法在嵌套数组内查询userID
Mongoose关联查询问题:如何筛选关联团队成员包含指定用户的患者
问题描述
- 存在两个MongoDB模型:
Patients和Teams Patients模型的team字段为mongoose.Types.ObjectId,用于关联Teams模型Teams模型的members字段是对象数组,每个成员包含id、name、role属性- 需求:查询所有满足以下任一条件的患者:
- 患者自身的
members数组包含当前登录用户的userID - 患者关联的
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
相关产品推荐
相关产品推荐

