【问题标题】:MongoDB Aggregation: How to get total records count?MongoDB聚合:如何获取总记录数?
【发布时间】:2013-12-19 07:54:42
【问题描述】:

我使用聚合从 mongodb 获取记录。

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),
  array('$skip' => $skip),
  array('$limit' => $limit),
));

如果我无限制地执行此查询,则将获取 10 条记录。但我想将限制保持为 2。所以我想获得总记录数。我该如何处理聚合?请给我建议。谢谢

【问题讨论】:

标签: mongodb


【解决方案1】:

从 v.3.4 开始(我认为)MongoDB 现在有了一个名为“facet”的新聚合管道运算符,用他们自己的话来说:

在同一输入文档集的单个阶段内处理多个聚合管道。每个子管道在输出文档中都有自己的字段,其结果存储为文档数组。

在这种特殊情况下,这意味着可以执行以下操作:

$result = $collection->aggregate([
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  {
    $facet: {
      paginatedResults: [{ $skip: skipPage }, { $limit: perPage }],
      totalCount: [
        {
          $count: 'count'
        }
      ]
    }
  }
]);

结果将是(总共有 100 个结果):

[
  {
    "paginatedResults":[{...},{...},{...}, ...],
    "totalCount":[{"count":100}]
  }
]

【讨论】:

  • 这很好用,从 3.4 开始,这应该是公认的答案
  • 要将如此多的结果转换为简单的两个字段对象,我需要另一个 $project?
  • 这应该是今天公认的答案。但是,当使用 $facet 进行分页时,我发现了性能问题。另一个投票赞成的答案也存在 $slice 的性能问题。我发现在管道中使用 $skip 和 $limit 并单独调用 count 会更好。我在相当大的数据集上对此进行了测试。
  • @SerG 你可以 $unwind the totalCount
【解决方案2】:

这是在单个查询中同时获得分页结果和结果总数的最常见问题之一。当我最终实现它时,我无法解释我的感受 LOL。

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),

// get total, AND preserve the results
  array('$group' => array('_id' => null, 'total' => array( '$sum' => 1 ), 'results' => array( '$push' => '$$ROOT' ) ),
// apply limit and offset
  array('$project' => array( 'total' => 1, 'results' => array( '$slice' => array( '$results', $skip, $length ) ) ) )
))

结果将如下所示:

[
  {
    "_id": null,
    "total": ...,
    "results": [
      {...},
      {...},
      {...},
    ]
  }
]

【讨论】:

  • 关于此的文档:docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... 请注意,使用这种方法,整个非分页结果集必须适合 16MB。
  • 这是纯金!我正在努力完成这项工作。
  • 谢谢你!我只是需要{ $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(在{$group:{}} 之后插入以查找总数。
  • 如何对结果集应用限制?结果现在是一个嵌套数组
  • @valen 可以看到最后一行代码" 'results' => array( '$slice' => array( '$results', $skip, $length ) )" 这里可以申请限制和跳过参数
【解决方案3】:

使用它来查找结果集合中的总数。

db.collection.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );

【讨论】:

  • 谢谢。但是,我在编码中使用了“视图”来获取相应组计数的计数(即组 1 => 2 条记录,组 3 => 5 条记录等)。我想获取记录数(即总数:120 条记录)。希望你能理解..
【解决方案4】:

您可以使用 toArray 函数,然后获取其长度以获取总记录数。

db.CollectionName.aggregate([....]).toArray().length

【讨论】:

  • 虽然这可能不是一个“正确”的解决方案,但它帮助我调试了一些东西——它确实有效,即使它不是 100% 的解决方案。
  • 这不是真正的解决方案。
  • TypeError: Parent.aggregate(...).toArray is not a function 这是我在这个解决方案中给出的错误。
  • 这将获取所有聚合数据,然后返回该数组的长度。不是一个好习惯。相反,您可以在聚合管道中添加 {$count: 'count'}
  • 不确定加载所有结果是检索 totalCount 的明智之举。
【解决方案5】:

使用$count aggregation pipeline stage 获取文档总数:

查询:

db.collection.aggregate(
  [
    {
      $match: {
        ...
      }
    },
    {
      $group: {
        ...
      }
    },
    {
      $count: "totalCount"
    }
  ]
)

结果:

{
   "totalCount" : Number of records (some integer value)
}

【讨论】:

  • 这就像一个魅力,但在性能方面它好吗?
【解决方案6】:

以下是在进行 MongoDB 聚合时获取总记录数的一些方法:


  • 使用$count:

    db.collection.aggregate([
       // Other stages here
       { $count: "Total" }
    ])
    

    获取 1000 条记录平均需要 2 毫秒,是最快的方法。


  • 使用.toArray():

    db.collection.aggregate([...]).toArray().length
    

    获取 1000 条记录平均需要 18 毫秒。


  • 使用.itcount():

    db.collection.aggregate([...]).itcount()
    

    获取 1000 条记录平均需要 14 毫秒。

【讨论】:

    【解决方案7】:

    我是这样做的:

    db.collection.aggregate([
         { $match : { score : { $gt : 70, $lte : 90 } } },
         { $group: { _id: null, count: { $sum: 1 } } }
    ] ).map(function(record, index){
            print(index);
     });
    

    聚合将返回数组,因此只需循环它并获取最终索引。

    其他的做法是:

    var count = 0 ;
    db.collection.aggregate([
    { $match : { score : { $gt : 70, $lte : 90 } } },
    { $group: { _id: null, count: { $sum: 1 } } }
    ] ).map(function(record, index){
            count++
     }); 
    print(count);
    

    【讨论】:

    • fwiw 你不需要var 声明,也不需要map 调用。您的第一个示例的前 3 行就足够了。
    【解决方案8】:
    //const total_count = await User.find(query).countDocuments();
    //const users = await User.find(query).skip(+offset).limit(+limit).sort({[sort]: order}).select('-password');
    const result = await User.aggregate([
      {$match : query},
      {$sort: {[sort]:order}},
      {$project: {password: 0, avatarData: 0, tokens: 0}},
      {$facet:{
          users: [{ $skip: +offset }, { $limit: +limit}],
          totalCount: [
            {
              $count: 'count'
            }
          ]
        }}
      ]);
    console.log(JSON.stringify(result));
    console.log(result[0]);
    return res.status(200).json({users: result[0].users, total_count: result[0].totalCount[0].count});
    

    【讨论】:

    • 在代码答案中包含解释性文本通常是一种很好的做法。
    【解决方案9】:

    @Divergent 提供的解决方案确实有效,但根据我的经验,最好有 2 个查询:

    1. 首先进行过滤,然后按 ID 分组以获取过滤元素的数量。不要在这里过滤,没有必要。
    2. 过滤、排序和分页的第二个查询。

    推送 $$ROOT 和使用 $slice 的解决方案会遇到 16MB 的文档内存限制,以用于大型集合。此外,对于大型集合,两个查询一起运行似乎比使用 $$ROOT 推送的查询运行得更快。您也可以并行运行它们,因此您只会受到两个查询中较慢的查询(可能是排序的那个)的限制。

    我已经使用 2 个查询和聚合框架解决了这个解决方案(注意 - 我在这个例子中使用了 node.js,但想法是一样的):

    var aggregation = [
      {
        // If you can match fields at the begining, match as many as early as possible.
        $match: {...}
      },
      {
        // Projection.
        $project: {...}
      },
      {
        // Some things you can match only after projection or grouping, so do it now.
        $match: {...}
      }
    ];
    
    
    // Copy filtering elements from the pipeline - this is the same for both counting number of fileter elements and for pagination queries.
    var aggregationPaginated = aggregation.slice(0);
    
    // Count filtered elements.
    aggregation.push(
      {
        $group: {
          _id: null,
          count: { $sum: 1 }
        }
      }
    );
    
    // Sort in pagination query.
    aggregationPaginated.push(
      {
        $sort: sorting
      }
    );
    
    // Paginate.
    aggregationPaginated.push(
      {
        $limit: skip + length
      },
      {
        $skip: skip
      }
    );
    
    // I use mongoose.
    
    // Get total count.
    model.count(function(errCount, totalCount) {
      // Count filtered.
      model.aggregate(aggregation)
      .allowDiskUse(true)
      .exec(
      function(errFind, documents) {
        if (errFind) {
          // Errors.
          res.status(503);
          return res.json({
            'success': false,
            'response': 'err_counting'
          });
        }
        else {
          // Number of filtered elements.
          var numFiltered = documents[0].count;
    
          // Filter, sort and pagiante.
          model.request.aggregate(aggregationPaginated)
          .allowDiskUse(true)
          .exec(
            function(errFindP, documentsP) {
              if (errFindP) {
                // Errors.
                res.status(503);
                return res.json({
                  'success': false,
                  'response': 'err_pagination'
                });
              }
              else {
                return res.json({
                  'success': true,
                  'recordsTotal': totalCount,
                  'recordsFiltered': numFiltered,
                  'response': documentsP
                });
              }
          });
        }
      });
    });
    

    【讨论】:

      【解决方案10】:

      这可能适用于多个匹配条件

                  const query = [
                      {
                          $facet: {
                          cancelled: [
                              { $match: { orderStatus: 'Cancelled' } },
                              { $count: 'cancelled' }
                          ],
                          pending: [
                              { $match: { orderStatus: 'Pending' } },
                              { $count: 'pending' }
                          ],
                          total: [
                              { $match: { isActive: true } },
                              { $count: 'total' }
                          ]
                          }
                      },
                      {
                          $project: {
                          cancelled: { $arrayElemAt: ['$cancelled.cancelled', 0] },
                          pending: { $arrayElemAt: ['$pending.pending', 0] },
                          total: { $arrayElemAt: ['$total.total', 0] }
                          }
                      }
                      ]
                      Order.aggregate(query, (error, findRes) => {})
      

      【讨论】:

        【解决方案11】:

        应用聚合后我需要绝对总数。这对我有用:

        db.mycollection.aggregate([
            {
                $group: { 
                    _id: { field1: "$field1", field2: "$field2" },
                }
            },
            { 
                $group: { 
                    _id: null, count: { $sum: 1 } 
                } 
            }
        ])
        

        结果:

        {
            "_id" : null,
            "count" : 57.0
        }
        

        【讨论】:

          【解决方案12】:

          如果你不想分组,那么使用下面的方法:

          db.collection.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $count: 'count' } ] );

          【讨论】:

          • 我认为提出问题的人确实想根据主题进行分组。
          【解决方案13】:

          抱歉,我认为您需要两个查询。一个用于总浏览量,另一个用于分组记录。

          你可以找到有用的this answer

          【讨论】:

          【解决方案14】:

          如果你需要匹配嵌套文档,那么

          https://mongoplayground.net/p/DpX6cFhR_mm

          db.collection.aggregate([
            {
              "$unwind": "$tags"
            },
            {
              "$match": {
                "$or": [
                  {
                    "tags.name": "Canada"
                  },
                  {
                    "tags.name": "ABC"
                  }
                ]
              }
            },
            {
              "$group": {
                "_id": null,
                "count": {
                  "$sum": 1
                }
              }
            }
          ])
          

          【讨论】:

            猜你喜欢
            • 2020-10-13
            • 2020-03-05
            • 2020-03-09
            • 1970-01-01
            • 1970-01-01
            • 2019-02-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多