MongoDB聚合:如何获取总记录数?


99

我已经使用聚合从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。因此,我想获取总记录数。如何进行汇总?请给我建议。谢谢


如果只有2,结果将是什么样?
WiredPrairie

看看$面这可能有助于stackoverflow.com/questions/61812361/...
Soham

Answers:


100

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

$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": [
      {...},
      {...},
      {...},
    ]
  }
]

8
有关此文档:docs.mongodb.com/v3.2/reference/operator/aggregation/group / ... ...请注意,使用这种方法,整个非分页结果集必须适合16MB。
btown

7
这是纯金!我一直在努力使这项工作顺利进行。
Henrique Miranda

4
谢了,兄弟们 !我只需要{ $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(在{$group:{}}总计数发现之后插入。–
Liberateur

1
您如何将限制应用于结果集?结果现在是一个嵌套数组
valen

@valen您可以看到代码的最后一行“'results'=> array('$ slice'=> array('$ results',$ skip,$ length))“”在这里您可以应用限制和跳过参数
Anurag pareek

80

从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}]
  }
]

13
从3.4开始,这很好用,这应该是公认的答案
Adam Reis

要将如此多的结果转换为简单的两个字段对象,我需要另一个$project吗?
SerG

1
现在必须是接受的答案。像魅力一样工作。
Arootin Aghazaryan '19

8
这应该是今天公认的答案。但是,在与$ facet一起使用分页时,我发现了性能问题。另一个获得投票赞成的答案也有$ slice的性能问题。我发现最好在管道中使用$ skip和$ limit并单独进行计数。我针对相当大的数据集进行了测试。
Jpepper

57

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

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

3
谢谢。但是,我在编码中使用了“视图”来获取相应组计数的计数(即,组1 => 2个记录,组3 => 5个记录,依此类推)。我想获取记录数(即总计:120条记录)。希望您理解..
user2987836 2013年

33

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

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

1
尽管这可能无法作为“合适的”解决方案,但它可以帮助我调试某些内容-即使不是100%的解决方案,它也可以工作。
约翰·马克思

3
这不是真正的解决方案。
FurkanBaşaran19年

1
TypeError: Parent.aggregate(...).toArray is not a function这是我给这个解决方案的错误。
Mohammad Hossein Shojaeinia

谢谢。这就是我想要的。
skvp

这将获取所有聚合的数据,然后返回该数组的长度。不是一个好习惯。相反,您可以在聚合管道中添加{$ count:'count'}
Aslam Shaik

18

使用$ count聚合管道阶段来获取文档总数:

查询:

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

结果:

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

这就像魅力一样,但是性能方面好吗?
ana.arede

清洁溶液。谢谢
skvp

13

我这样做是这样的:

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);

之后,您不需要var声明或map调用。您的第一个示例的前3行就足够了。
Madbreaks'Apr 4'18

7

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

  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
            });
          }
      });
    }
  });
});

5
//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});

1
通常最好的做法是将解释性文字与代码答案一起包括在内。

3

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

            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) => {})

2

应用汇总后,我需要绝对总数。这对我有用:

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

结果:

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

2

以下是在进行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毫秒。



0

如果您不想分组,请使用以下方法:

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


我认为提出这个问题的人确实希望根据主题进行分组。
mjaggard
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.