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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 03:33:14