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

Mongoose关联查询问题:筛选指定分类及红色库存的商品

问题描述

我拥有三个Mongoose模型:Product、Category和Stock。Product模型的categories数组用于存储Category的ID,stocks数组用于存储Stock的ID。需要查询同时满足以下条件的商品:

  • 商品关联的Category ID等于req.categoryId(由converttoslugtocategoryId中间件从URL路径cell-phone转换而来)
  • 商品关联的Stock中存在颜色为req.query.color(示例值为red)的记录

接口地址:http://localhost:3000/api/products/cell-phone?color=red

模型定义

ProductModel

const mongoose = require("mongoose");
const Stock = require("./Stock");
const ProductModel = new mongoose.Schema({
    name: {
        type: String,
        required: [true, "ürün ad alanı boş bırakılamaz"],
    },
    description: {
        type: String,
        required: [true, "ürün açıklama alanı boş bırakılamaz"]
    },
    slug: String,
    createdAt: {
        type: Date,
        default: Date.now
    },
    properties: [
       String
    ],
    image: {
        type: String,
        default: "default.png"
    },
    images: {
        type: [String],

    },
    size: {
        type: String,
    },
    color: {
        type: String
    },
    price: {
        type: Number,
        default: 0
    },
    supplier: {
        type: mongoose.Schema.ObjectId,
        ref: "Supplier"
    },
    categories:[
        {
            type: mongoose.Schema.ObjectId,
            ref: "Category" 
        }
    ],
    rating: Number,
    comments: [
        {
            type: mongoose.Schema.ObjectId,
            ref: "Comment"
        }
    ],
    stocks: [
        {
            type: mongoose.Schema.ObjectId,
            ref: "Stock"
        }
    ],
    visible: {
        type: Boolean,
        default: true
    }
})

module.exports = mongoose.model("Product", ProductModel)

CategoryModel

const mongoose = require("mongoose");
const sluqify = require("slugify")
const CategoryModel = new mongoose.Schema({
   
    parentId: {
        type: String,
        default: null
    },
    name: {
        type:String
    },
    children: [
        {
            type: mongoose.Schema.ObjectId,
            ref: "Category"
        }
    ],
    properties:[
        {
            type: mongoose.Schema.ObjectId,
            ref:"PropertyOfCategory"
        }
    ],
    
    slug:String
})

module.exports = mongoose.model("Category", CategoryModel)

StockModel

const mongoose = require("mongoose");
const fs = require("fs")
const Product = require("./Product");

const StockModel = new mongoose.Schema({
    product: {
        type: mongoose.Schema.ObjectId,
        ref:"Product"
    },
    size: {
        type: String
    },
    color: {
        type: String
    },
    piece: {
        type:Number, 
        default: 0
    },
    price: {
        type: Number,
        default:0
    },
    base:{
        type:Boolean,
        default:false
    },
    status: {
        type: Boolean,
        default:true
    },
    image:{
        type: String,

    },
    images: {
        type:[String]
    }   
})

StockModel.methods.updateProductBaseStock =  function(productId){
    const product =  Product.findById(productId);
    product.stocks[0].base = true;
        product.size = this.size;
        product.color = this.color;
        product.price = this.price
        product.save();
}
StockModel.methods.removeOtherPictures = function(stockId, picNames){
    fs.readdir(process.cwd()+"/public/uploads", (err, files)=> {
        if(err){
            console.log(`${process.cwd()}/public/uploads yolu bulunamadı: ${err}`)
        }else{
            if(Array.isArray(picNames)){
                picNames.map(picName => {
                    if(files.includes(picName)){
                        fs.unlink(process.cwd()+`/public/uploads/${picName}`, function(err){
                            if(err) console.log("dosya silme işlemi sırasında hatalarla karşılaşıldı: "+ err)
                        })
                    }
                })
            }
        }
    })
}

module.exports = mongoose.model("Stock", StockModel)

尝试过的无效查询

const products = await Product.find({categories: req.categoryId, 'stocks.color': req.query.color}}).populate('stocks')
const products = await Product.find({categories: req.categoryId}).populate({path:"stocks", match:{'color': {'$eq': req.query.color}}}).where({"stocks": {"$ne": []});

错误原因分析
  • 第一个查询:'stocks.color' 写法错误。Product模型的stocks是ObjectId数组,并非嵌入文档,MongoDB无法直接通过该路径关联Stock模型的color字段进行查询。
  • 第二个查询:populate的match仅会过滤返回的Stock数据,但不会过滤Product本身。即使某个Product没有符合颜色条件的Stock,只要它属于目标分类,仍会被返回,只是其stocks数组为空。后续的where({"stocks": {"$ne": []}})也无效,因为这里的stocks是原Product文档中的ID数组,而非populate后的结果。

正确查询方案

方案1:聚合管道查询(推荐)

通过$lookup关联Stock集合,一步完成分类匹配和库存颜色过滤:

const mongoose = require("mongoose");

const products = await Product.aggregate([
  // 匹配属于目标分类的商品
  {
    $match: {
      categories: mongoose.Types.ObjectId(req.categoryId)
    }
  },
  // 关联Stock集合,获取商品对应的所有库存详情
  {
    $lookup: {
      from: "stocks", // Stock模型对应的集合名(Mongoose默认转复数)
      localField: "stocks",
      foreignField: "_id",
      as: "stockDetails"
    }
  },
  // 过滤出存在符合颜色条件库存的商品
  {
    $match: {
      "stockDetails.color": req.query.color
    }
  },
  // 可选:自定义返回字段,保留原Product字段并添加库存详情
  {
    $project: {
      name: 1,
      description: 1,
      price: 1,
      // 按需添加其他需要返回的字段
      stockDetails: 1
    }
  }
]);

方案2:分步查询

先找到符合颜色条件的Stock,再关联查询对应的Product并过滤分类:

const mongoose = require("mongoose");

// 1. 查询所有颜色匹配的Stock,提取对应的Product ID
const targetStocks = await Stock.find({ color: req.query.color }).select("product");
const productIds = targetStocks.map(stock => stock.product);

// 2. 查询属于目标分类且ID在productIds中的商品,可选仅返回符合条件的Stock
const products = await Product.find({
  categories: mongoose.Types.ObjectId(req.categoryId),
  _id: { $in: productIds }
}).populate({
  path: "stocks",
  match: { color: req.query.color } // 仅返回符合颜色条件的库存
});

注意事项

  • 确保req.categoryId被转换为mongoose.Types.ObjectId类型,如果中间件返回的是字符串,需手动转换。
  • 方案1适合数据量较小的场景,逻辑紧凑;方案2适合数据量较大的场景,拆分查询可提升性能。

内容的提问来源于stack exchange,提问作者ali eren gönülçelen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 01:25:13