如何在Mongoose中编写查询MongoDB特定技能用户的语句
问题描述
我定义了两个Mongoose文档Schema:User和Skill。User的skills属性关联Skill文档,Skill的users属性关联User文档。我希望查询拥有特定技能的用户。
目前编写的代码如下:
export async function getUsersWithSkill(skillName: SkillName): Promise<User[]> { // NOTE : 'CONNECTION' is the mongoose connection to MongoDB const Users = CONNECTION.model<User>('User'); const users: User[] = await Users .find() // does it need a condition in find? .populate({ path: 'skills', match: { name: { $eq: skillName } } }) // or does it need a where clause here? // // the following didn't work // .where({ // skills: { $count : { $gt: 0 }} // }) .exec(); return users; }
这段代码中,populate可以正常匹配指定技能,但无法筛选出真正拥有该技能的用户,尝试的where条件无效。
相关Schema定义:
import { Document, Schema } from 'mongoose'; import { Skill } from './Skill'; export interface User extends Document { firstName: string; lastName: string; skills: Skill[]; } export const UserSchema: Schema<User> = new Schema<User>({ firstName: { type: String, required: true }, lastName: { type: String, required: true }, skills: [{ type: Schema.Types.ObjectId, ref: 'Skill' }] }); export interface Skill extends Document { name: string; users: User[] } export const SkillSchema: Schema<Skill> = new Schema<Skill>({ name: { type: String, required: true, enum: Object.values(SkillName) }, users: [{ type: Schema.Types.ObjectId, ref: 'User' }] }); export enum SkillName { CSHARP = 'CSHARP', FSHARP = 'FSHARP', JAVA = 'JAVA', JAVASCRIPT = 'JAVASCRIPT', TYPESCRIPT = 'TYPESCRIPT', PYTHON = 'PYTHON', PERL = 'PERL', BASH = 'BASH' }
解决方法
方法一:先查询技能ID,再关联查询用户(推荐,性能更优)
这种方式先找到目标技能的_id,再直接查询skills数组包含该ID的用户,避免返回无关数据,效率更高:
export async function getUsersWithSkill(skillName: SkillName): Promise<User[]> { const SkillModel = CONNECTION.model<Skill>('Skill'); const UserModel = CONNECTION.model<User>('User'); // 先找到对应技能的文档,获取其ID const targetSkill = await SkillModel.findOne({ name: skillName }).exec(); if (!targetSkill) { return []; // 没有该技能时返回空数组 } // 查询拥有该技能的用户,并填充skills字段(可选,若需要返回技能详情) const users = await UserModel.find({ skills: targetSkill._id }) .populate('skills') // 若只需要匹配的该技能,可以用populate的match条件 .exec(); return users; }
如果只需要返回用户信息,且不需要填充所有技能,也可以保留populate的match条件:
.populate({ path: 'skills', match: { name: skillName } })
方法二:在现有查询结果中过滤(性能较差,不推荐大数据量)
如果坚持先查询所有用户再处理,可以在populate后过滤掉skills数组为空的用户:
export async function getUsersWithSkill(skillName: SkillName): Promise<User[]> { const UserModel = CONNECTION.model<User>('User'); const allUsers = await UserModel.find() .populate({ path: 'skills', match: { name: skillName } }) .exec(); // 过滤掉skills为空数组的用户 return allUsers.filter(user => user.skills.length > 0); }
方法三:使用聚合查询
通过MongoDB的聚合管道,先关联Skill集合,再过滤匹配的用户:
export async function getUsersWithSkill(skillName: SkillName): Promise<User[]> { const UserModel = CONNECTION.model<User>('User'); const users = await UserModel.aggregate([ // 关联Skill集合 { $lookup: { from: 'skills', // 注意这里是MongoDB中的集合名称,不是模型名 localField: 'skills', foreignField: '_id', as: 'skills' } }, // 过滤出包含目标技能的用户 { $match: { 'skills.name': skillName } } ]).exec(); return users as User[]; }
说明
原代码的问题在于:populate的match条件只会过滤填充的skills数组内容,不会过滤用户本身。所以即使用户没有该技能,依然会被返回,只是其skills字段会是空数组。
最推荐的是方法一,因为它直接在数据库层面筛选出符合条件的用户,避免了查询无关数据,性能最优。
内容的提问来源于stack exchange,提问作者Umar F Khawaja
相关产品推荐
相关产品推荐

