Mongo:查找有0个关联文档的文档,成本较低

Das*_*uss 2 mongoose mongodb mongodb-query aggregation-framework

我有Orders 和Shops。

db={
  "orders": [
    {
      "_id": 1,
      "shop": 1,
      "price": 11
    },
    {
      "_id": 2,
      "shop": 2,
      "price": 101
    },
    
  ],
  "shops": [
    {
      "_id": 1,
      
    },
    {
      "_id": 2,
      
    },
    {
      "_id": 3,
      
    },
    
  ],
  
}
Run Code Online (Sandbox Code Playgroud)

我想找出哪些商店的订单数为0。

我是这样做的

db.shops.aggregate([
  {
    $lookup: {
      from: "orders",
      let: {
        shop: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $eq: [
                "$shop",
                "$$shop"
              ]
            },
            
          },
          
        },
        
      ],
      as: "orders",
      
    },
    
  },
  {
    $project: {
      user: "$user",
      orderCnt: {
        $size: "$orders"
      }
    }
  },
  {
    $match: {
      orderCnt: {
        $eq: 0
      }
    }
  },
  
])
Run Code Online (Sandbox Code Playgroud)

这有效。我也尝试过其他几种方法。但它总是很贵!

如何更高效地找到0订单的店铺?有数千个订单和数千个商店,这需要很长时间。

蒙戈游乐场

Tom*_*ert 5

正如您所提到的,当前的方法实际上$lookup只是在每个文档上使用,只是为了“不使用”结果,这里有明显的(巨大的)开销,并且可以很好地优化。

我会选择以下两种解决方案之一:

  1. (我推荐的选项较少,但不需要预处理)如果集合shop中的字段order不存在,则在该字段上创建索引。shop _ids现在首先从集合中获取所有内容,然后在集合上的查询order中使用它:findshop
const shopIds = await mongo.getCollection('orders').distinct('shop');
const shopsWithNoOrders = await mongo.getCollection('shops').find({ _id: {$nin: shopIds }});
Run Code Online (Sandbox Code Playgroud)

这种方法将比您当前的方法快得多,但是它确实存在一些问题,其中最大的问题是运算符$nin

不等式运算符 $nin 的选择性不是很强,因为它通常匹配索引的很大一部分。因此,在许多情况下,带有索引的 $nin 查询的性能可能并不比必须扫描集合中所有文档的 $nin 查询好。另请参见查询选择性。

基本上性能还有待提高。

  1. 我推荐的解决方案需要一些预处理,这需要更改商店模式并添加新字段“ activeOrders”,本质上我们将在每个商店上维护一个计数器,用于显示当前有多少订单。

这意味着对于每个订单插入和删除,您将需要更新商店的活动订单,这应该不会太难维护,并且是一种非常常见的访问模式,但是如果不了解您的应用程序,则很难保证这很容易实现。

一旦activeOrders维护了该字段,您就可以在其上构建索引并仅使用简单的查询:

const shopsWithNoOrders = await mongo.getCollection('shops').find({ activeOrders: 0 });
Run Code Online (Sandbox Code Playgroud)