mongodb Mongoose:如何使用populate查找

r8uurelv  于 2023-10-16  发布在  Go
关注(0)|答案(2)|浏览(143)

我想知道如何查询数据库,以获取满足“store”{name}字段的所有结果,但此“store”保存为“_id”,我使用.populate()获取信息
/**示例:await product.find({store:{name:“specific store”}})*/

const products = await Product.find({ 'store.name' : "Ejemplo1" })
                .populate('store', 'name')

这些计划如下:

const storeSchema = mongoose.Schema({
   _id:"it is added automatically by mongoose",
   name: String
})
export default model('Store', storeSchema);

const productSchema = mongoose.Schema({
   store: {
     type: Schema.Types.ObjectId,
     ref: "Store"
   }
})

基本上,我想实现的是从DB中提取具有特定'商店'的所有产品,但我仍然无法做到这一点,我感谢任何帮助来解决这个问题,无论是参考还是合适的查询示例,提前感谢
我试过这个:

const p = await Product.find({})
  .populate({
    path: 'store',
    match: { name: { $eq: 'Store1' } },
    select: 'name -_id',
  })

但它返回整个集合,而不进行过滤,我收到这样的东西:

[{
  _id:1,
  ...
  store:null,
  ...
}, {
  _id:2,
  ...
  store:{name:"Store1"},
  ...
}, {
  _id:3,
  ...
  store:{name:"Store2"},
  ...
}]
1mrurvl1

1mrurvl11#

正如@M.CaglarTUFAN所提到的,你不能根据子文档来过滤父文档。重构模式是一个不错的选择。
但是,如果你无法做到这一点,你可以使用$lookup$match使用mongodb聚合框架来实现你想要的结果,如下所示:

const products = await Product.aggregate([
   { 
      $lookup: { //< Lookup is like a Left Join in SQL
         from: "stores", 
         localField: "store", 
         foreignField: "_id", 
         as: "store_details" //< You are adding this field to the output
      } 
   }, 
   { 
      $match: { //< Now from all the results only give me back the store Ejemplo1
         'store_details.name': 'Ejemplo1' 
      } 
   }
]).exec();

如果您的产品集合很大(数百万个文档),请记住这将对所有文档执行$lookup,因此效率可能很低。但是,如果不重构模式,这是对数据库的最佳单次调用。
第二个选项涉及2个数据库查询。
1.从stores集合中获取所需存储的_id
1.然后使用该结果查询products集合并填充匹配记录。

dgtucam1

dgtucam12#

首先想到的解决方案是使用Array.filter()方法手动过滤数据。这就像下面所示的那样简单:

const products = await Product.find({}).populate({
  path: 'store',
  match: { name: { $eq: 'Store1' } },
  select: 'name -_id'
});
const productsFiltered = products.filter(
  product => product.store !== null
);

正如你所看到的,这可以很好地帮助少量的检索数据,并且很容易实现。但是如果你想在数据库级别上应用这样的过滤器,那么我们需要改变你的产品的模式。因为根据Mongoose文档,不可能在查找时过滤数据。我引用以下理由:
例如,假设你populate()一个故事的authorauthor不满足match。那么故事的author将是null

const story = await Story.
  findOne({ title: 'Casino Royale' }).
  populate({ path: 'author', name: { $ne: 'Ian Fleming' } }).
  exec();
story.author; // `null`

一般来说,没有办法让populate()根据故事author的属性过滤故事。例如,下面的查询不会返回任何结果,即使author被填充。

const story = await Story.
  findOne({ 'author.name': 'Ian Fleming' }).
  populate('author').
  exec();
story; // null

如果你想按作者的名字过滤文章,你应该使用denormalization
我建议您阅读MongoDB的官方博客文章。
我们可以应用博客文章中提到的不同类型的反规范化,但我将提供其中一种类型的代码。首先,我们需要更改您的产品架构:

const productSchema = mongoose.Schema({
   store: {
     id: Schema.Types.ObjectId,
     name: String
   }
});

我们没有将store字段定义为Store模型的引用,而是反规范化并添加了id(进行应用程序级连接)和name(过滤)字段。之后,我们可以轻松地获取和过滤指定商店名称的产品:

const p = await Product.find({
  'store.name': 'Store1'
}, 'name -_id');

这样,我们将始终通过store.name字段过滤结果。如果我们想提供idname字段以外的存储数据,我们需要进行应用程序级连接:

// Map each product to retrieve only their store ids and filter out to have unique store ids
const storeIds = p
  .map(
    product => product.store.id
  )
  .filter(
    (value, index, array) => array.indexOf(value) === index
  );

// Retrieve stores with given store ids
const stores = await Store.find({ _id: { $in: storeIds } });

// Join stores with each product's store.id
p.forEach(
  product => {
    product.store = stores.find(store => store._id.toString() == product.store.id.toString())
  }
);

正如你所看到的,使用反规范化有一些优点和缺点。当你想更新一个商店的名称时,你还需要更新该商店的所有占用产品。这种更新可能是昂贵的。由于这个原因,如果你要对产品进行大量的读取操作(使用商店名称过滤),而对商店进行不频繁的名称更新,那么反规范化是一个很好的选择,那么这是一个很好的解决方案。

相关问题