代码之家  ›  专栏  ›  技术社区  ›  Poni

mongodb聚合按分组字段排序

  •  1
  • Poni  · 技术社区  · 7 年前

    MongoDB 4.0版。

    这是数据集( sales-aggregate-test.js ):

    use Test123;
    
    const HOW_MANY_PRODUCTS = 1000
    const HOW_MANY_SALES_PER_PRODUCT = 50
    
    for(let i = 0; i < HOW_MANY_PRODUCTS; i++) {
      const productNumber = (i + 10001)
      const productId = '5bd9d139d96b8fce000' + productNumber
      db.getCollection('products').insert({
        _id: ObjectId(productId),
        title: 'Product ' + productNumber,
      })
    
      for(let j = 0; j < HOW_MANY_SALES_PER_PRODUCT; j++) {
        const saleNumber = (j + 10001)
        const saleId = '5bd9d139d96b8f' + productNumber + saleNumber
        db.getCollection('sales').insert({
          _id: ObjectId(saleId),
          product: ObjectId(productId),
          quantity: i + j + 1,
        })
      }
    }
    

    插入: mongo < ./sales-aggregate-test.js .

    现在这是查询( sales-aggregate-test-actual-query.js ):

    use Test123;
    
    db.getCollection('sales').aggregate(
      [
        {
          $sort: { product: 1, remoteVariantId: 1, quantity: -1, }
        },
    
        {
          $lookup: {
            from:               'products',
            localField:         'product',
            foreignField:        '_id',
            as:                 'productModel',
          }
        },
    
        {
          $unwind: '$productModel'
        },
    
        {
          $match: {
            'productModel.archived': { $ne: true }
          }
        },
    
        {
          $project: {
            product: 1,
            quantity: 1,
          }
        },
    
        //{ $limit: 10 },
    
        {
          $group: {
            _id: '$product',
    
            saleModelsCount: { $sum: 1 },
    
            quantity : { $sum: '$quantity' },
          }
        },
    
        {
          $sort: { quantity: -1, }
        },
        { $limit: 3  },
      ]
      // ,{ allowDiskUse: true }
    )
    

    你想达到什么目的?加快速度:

    { "_id" : ObjectId("5bd9d139d96b8fce00011000"), "saleModelsCount" : 50, "quantity" : 51225 }
    { "_id" : ObjectId("5bd9d139d96b8fce00010999"), "saleModelsCount" : 50, "quantity" : 51175 }
    { "_id" : ObjectId("5bd9d139d96b8fce00010998"), "saleModelsCount" : 50, "quantity" : 51125 }
    

    基本上就是:给我最畅销的产品。由于销售包括数量,我需要先按数量分组,然后再排序。

    现在在这个测试数据集上它是“快的”—只有2.5秒。问题在于真实的数据集,其中的产品模型要大得多,涉及的因素也更多(比如销售模型中的“价格”字段)。

    这个问题似乎是由 $group 和 $sort 阶段。评论出这两个返回很快。只注释一个会使查询变慢。

    我怎样才能更快?开放的建议-一个不同的方法也是可能的。

    1 回复  |  直到 7 年前
        1
  •  1
  •   mickl    7 年前

    一些可能对你有用的想法:

    首先你可以先摆脱 $sort 因为你有另一个在最后的管道阶段,这一个将保证正确的秩序。

    有几种方法可以替代 $lookup + $unwind + $match + $project + $group .

    你可以用 $addFields 具有 $filter 在你之前过滤掉一些元素 $展开 :

    {
        $lookup: {
            from: 'products',
            localField: 'product',
            foreignField: '_id',
            as: 'productModel',
        }
    },
    
    {
        $addFields: {
            productModel: {
                $filter: {
                    input: '$productModel',
                    as: 'model',
                    cond: { $ne: [ '$$model.archived', true ] }
                }
            }
        }
    },
    
    {
        $unwind: '$productModel'
    }
    

    在这种情况下,您可以删除 $匹配 因为这个操作是在嵌套数组中执行的。

    第二种方法可能是 $lookup with custom pipeline ,以便您可以在 $查找 :

    {
        $lookup: {
            from:   'products',
            let: { productId: "$product" },
            pipeline: [
                {
                    $match: { $expr: { $and: [ { $eq: [ "$$productId", "$_id" ] }, { $ne: [ "$archived", true ] } ] } }
                }
            ],
            as: 'productModel',
        }
    }
    

    作为两种情况下的另一种优化,您不需要 $展开 作为你的 productModel 数组被过滤,然后您可以修改 $组 :

    {
        $group: {
            _id: '$product',
            saleModelsCount: { $sum: { $size: "$productModel" } },
            quantity : { $sum: '$quantity' },
        }
    }