MongoDB - 聚合框架(总计数)

时间:2013-07-20 23:09:37

标签: mongodb mongodb-query

在MongoDB上运行正常的“查找”查询时,我可以通过在返回的游标上运行“count”来获取总结果计数(无论是否有限制)。因此,即使我将结果集限制为10(例如),我仍然可以知道结果总数为53(例如,再次)。

如果我理解正确,聚合框架不会返回游标而只返回结果。因此,如果我使用$limit管道运算符,无论上述限制如何,我如何知道结果总数?

我想我可以运行聚合两次(一次通过$group计算结果,一次使用$limit计算实际有限结果),但这似乎效率低下。

另一种方法可能是在$group操作之前将结果总数附加到文档(通过$limit),但这似乎也是低效的,因为这个数字将附加到每个文档(而不是只为集合返回一次)。

我在这里遗漏了什么吗?有任何想法吗?谢谢!

例如,如果这是查询:

db.article.aggregate(
    { $group : {
        _id : "$author",
        posts : { $sum : 1 }
    }},
    { $sort : { posts: -1 } },
    { $limit : 5 }
);

我怎么知道有多少结果可用($limit之前)?结果不是游标,所以我不能只计算它。

7 个答案:

答案 0 :(得分:3)

Assaf,在不久的将来,聚合框架会有一些增强功能,可以让你轻松地在一次通过中进行计算,但是现在,最好通过并行运行两个查询来执行计算:一个用于聚合顶级作者的#posts,另一个聚合用于计算所有作者的总帖子。另请注意,如果您只需要对文档进行计数,则使用count函数是执行计算的一种非常有效的方法。 MongoDB在btree索引中缓存计数,允许非常快速地查询查询。

如果这些汇总结果变慢,则有几种策略。首先,请记住,如果适用,您希望使用$ match启动查询以减少结果集。 $ matches也可以通过索引加速。其次,您可以将这些计算作为预聚合执行。每次用户访问应用程序的某些部分时,不是可能运行这些聚合,而是让聚合在后台定期运行,并将聚合存储在包含预聚合值的集合中。这样,您的网页就可以简单地查询此集合中预先计算的值。

答案 1 :(得分:3)

使用推送和切片有一个解决方案:https://stackoverflow.com/a/39784851/4752635(@emaniacs也在这里提到它。)

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

  1. 首先进行过滤,然后按ID分组以获取已过滤元素的数量。不要在这里过滤,这是不必要的。
  2. 过滤,排序和分页的第二个查询。
  3. 我已经使用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
                });
              }
          });
        }
      });
    });
    

答案 2 :(得分:1)

我使用aggregate().toArray().length

获得总计数

答案 3 :(得分:0)

如果您不想并行运行两个查询(一个用于汇总顶级作者的#posts,另一个用于计算所有作者的总帖子),您只需删除管道上的$ limit即可在结果上你可以使用

totalCount = results.length;
results.slice(number of skip,number of skip + number of limit);

前:

db.article.aggregate([
    { $group : {
        _id : "$author",
        posts : { $sum : 1 }
    }},
    { $sort : { posts: -1 } }
    //{$skip : yourSkip},    //--remove this
    //{ $limit : yourLimit }, // remove this too
]).exec(function(err, results){
  var totalCount = results.length;//--GEt total count here
   results.slice(yourSkip,yourSkip+yourLimit);
});

答案 4 :(得分:0)

就我而言,我们使用$ out stage将结果集从aggeration转储到temp / cache表中,然后计算它。并且,由于我们需要对结果进行排序和分页,我们在临时表上添加索引并在会话中保存表名,在会话结束/缓存超时时删除表。

答案 5 :(得分:0)

我遇到了同样的问题,并通过 $ project $ slice $$ ROOT 解决了问题。

db.article.aggregate(
{ $group : {
    _id : '$author',
    posts : { $sum : 1 },
    articles: {$push: '$$ROOT'},
}},
{ $sort : { posts: -1 } },
{ $project: {total: '$posts', articles: {$slice: ['$articles', from, to]}},
).toArray(function(err, result){
    var articles = result[0].articles;
    var total = result[0].total;
});

您需要声明fromto变量。

https://docs.mongodb.com/manual/reference/operator/aggregation/slice/

答案 6 :(得分:0)

$ facets聚合操作可用于Mongo版本> = 3.4。 这允许在多个子管道中的管道的特定阶段进行分叉,在这种情况下允许构建一个子管道来计算文档的数量,另一个用于排序,跳过,限制。

这样可以避免在多个请求中多次制作相同的舞台。