使用限制时,使用MongoDB获取总文档数

时间:2014-02-15 20:33:32

标签: mongodb pagination mongodb-query aggregation-framework casbah

我有兴趣优化我正在使用MongoDB的“分页”解决方案。我的问题很直接。我通常使用limit()功能限制返回的文档数量。这迫使我发出一个没有limit()函数的冗余查询,以便我也可以捕获查询中的文档总数,这样我就可以将其传递给客户端,让他们知道他们必须发出一个其他请求以检索其余文件。

有没有办法将此缩减为1个查询?获取文档总数但同时仅使用limit()检索子集?是否有不同的方式来思考这个问题而不是我接近它?

13 个答案:

答案 0 :(得分:15)

,没有别的办法。两个查询 - 一个用于计数 - 一个用于限制。或者您必须使用其他数据库。例如Apache Solr就像您想要的那样工作。每个查询都有限返回totalCount。

答案 1 :(得分:9)

Mongodb 3.4 引入了$facet聚合

  

在单个阶段处理多个聚合管道   在同一组输入文档上。

使用$facet$group可以找到$limit的文档,并可以获得总数。

您可以在mongodb 3.4

中使用以下聚合
db.collection.aggregate([
  { "$facet": {
    "totalData": [
      { "$match": { }},
      { "$skip": 10 },
      { "$limit": 10 }
    ],
    "totalCount": [
      { "$group": {
        "_id": null,
        "count": { "$sum": 1 }
      }}
    ]
  }}
])

甚至您都可以使用mongodb 3.6 中引入的$count聚合。

您可以在mongodb 3.6

中使用以下聚合
db.collection.aggregate([
  { "$facet": {
    "totalData": [
      { "$match": { }},
      { "$skip": 10 },
      { "$limit": 10 }
    ],
    "totalCount": [
      { "$count": "count" }
    ]
  }}
])

答案 2 :(得分:6)

Mongodb 3.4中有一种方法:$ facet

你可以做到

db.collection.aggregate([
  {
    $facet: {
      data: [{ $match: {} }],
      total: { $count: 'total' }
    }
  }
])

那么你将能够同时运行两个聚合

答案 3 :(得分:5)

时代已经发生变化,我相信通过使用$sort$group$project汇总,您可以实现OP的要求。对于我的系统,我还需要从我的users集合中获取一些用户信息。希望这也可以回答任何问题。下面是一个聚合管道。最后三个对象(排序,组和项目)处理总计数,然后提供分页功能。

db.posts.aggregate([
  { $match: { public: true },
  { $lookup: {
    from: 'users',
    localField: 'userId',
    foreignField: 'userId',
    as: 'userInfo'
  } },
  { $project: {
    postId: 1,
    title: 1,
    description: 1
    updated: 1,
    userInfo: {
      $let: {
        vars: {
          firstUser: {
            $arrayElemAt: ['$userInfo', 0]
          }
        },
        in: {
          username: '$$firstUser.username'
        }
      }
    }
  } },
  { $sort: { updated: -1 } },
  { $group: {
    _id: null,
    postCount: { $sum: 1 },
    posts: {
      $push: '$$ROOT'
    }
  } },
  { $project: {
    _id: 0,
    postCount: 1,
    posts: {
      $slice: [
        '$posts',
        currentPage ? (currentPage - 1) * RESULTS_PER_PAGE : 0,
        RESULTS_PER_PAGE
      ]
    }
  } }
])

答案 4 :(得分:4)

以下是使用 $facetsMongoDB 3.4+(使用 Mongoose)中执行此操作的方法。此示例根据文档匹配后返回一个 $count

const facetedPipeline = [{
    "$match": { "dateCreated": { $gte: new Date('2021-01-01') } },
    "$project": { 'exclude.some.field': 0 },
  },
  {
    "$facet": {
      "data": [
        { "$skip": 10 },
        { "$limit": 10 }
      ],
      "pagination": [
        { "$count": "total" }
      ]
    }
  }
];

const results = await Model.aggregate(facetedPipeline);

此模式对于获取从 REST API 返回的分页信息很有用。

参考:MongoDB $facet

答案 5 :(得分:3)

这一切都取决于您是否需要进行两次查询所需的分页经验。

您是否需要列出每一页甚至一系列页面?有没有人甚至去第1051页 - 概念上这究竟是什么意思?

在分页模式上有很多用户体验 - Avoid the pains of pagination涵盖各种类型的分页及其场景,许多不需要计数查询来知道是否下一页。例如,如果您在页面上显示10个项目并且限制为13 - 您将知道是否还有另一个页面..

答案 6 :(得分:2)

MongoDB引入了一种新方法,该方法仅获取与给定查询匹配的文档数,其方法如下:

const result = await db.collection('foo').count({name: 'bar'});
console.log('result:', result) // prints the matching doc count

在分页中使用的食谱:

const query = {name: 'bar'};
const skip = (pageNo - 1) * pageSize; // assuming pageNo starts from 1
const limit = pageSize;

const [listResult, countResult] = await Promise.all([
  db.collection('foo')
    .find(query)
    .skip(skip)
    .limit(limit),

  db.collection('foo').count(query)
])

return {
  totalCount: countResult,
  list: listResult
}

有关db.collection.count的更多详细信息,请访问this page

答案 7 :(得分:1)

即使您通过cursor.count()limit(),MongoDB也可以使用skip()

假设你有一个db.collection有10个项目。

你可以这样做:

async function getQuery() {
  let query = await db.collection.find({}).skip(5).limit(5); // returns last 5 items in db
  let countTotal = await query.count() // returns 10-- will not take `skip` or `limit` into consideration
  let countWithConstraints = await query.count(true) // returns 5 -- will take into consideration `skip` and `limit`
  return { query, countTotal } 
}

答案 8 :(得分:0)

您可以在一个查询中执行此操作。首先运行一个计数,并在该运行中运行limit()函数。

在Node.js和Express.js中,你必须像这样使用它才能使用“count”函数和toArray的“结果”。

var curFind = db.collection('tasks').find({query});

然后你可以像这样运行两个函数(一个嵌套在另一个函数中)

curFind.count(function (e, count) {

// Use count here

    curFind.skip(0).limit(10).toArray(function(err, result) {

    // Use result here and count here

    });

});

答案 9 :(得分:0)

可以获得总结果大小而不会limit()使用count()的效果,如下所示: Limiting results in MongoDB but still getting the full count?

根据文档,您甚至可以控制在调用count()时是否考虑限制/分页: https://docs.mongodb.com/manual/reference/method/cursor.count/#cursor.count

编辑:与其他地方的内容形成对比 - 文档明确指出"操作不执行查询,而是计算查询返回的结果" 。根据我的理解,这意味着只执行一个查询。

示例:

> db.createCollection("test")
{ "ok" : 1 }

> db.test.insert([{name: "first"}, {name: "second"}, {name: "third"}, 
{name: "forth"}, {name: "fifth"}])
BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 5,
    "nUpserted" : 0,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})

> db.test.find()
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c8"), "name" : "forth" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c9"), "name" : "fifth" }

> db.test.count()
5

> var result = db.test.find().limit(3)
> result
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }

> result.count()
5 (total result size of the query without limit)

> result.count(1)
3 (result size with limit(3) taken into account)

答案 10 :(得分:0)

试试如下:

  

cursor.count(false,function(err,total){console.log(“total”,total)})

core.db.users.find(query, {}, {skip:0, limit:1}, function(err, cursor){
    if(err)
        return callback(err);

    cursor.toArray(function(err, items){
        if(err)
            return callback(err);

        cursor.count(false, function(err, total){
            if(err)
                return callback(err);

            console.log("cursor", total)

            callback(null, {items: items, total:total})
        })
    })
 })

答案 11 :(得分:0)

您可以使用cursor.count()来获取总数

 const cursor = await database.collection(collectionName).find(query).skip(offset).limit(limit)
 return {
    data: await cursor.toArray(),
    count: await cursor.count() // this will give count of all the documents before .skip() and limit()
 };

答案 12 :(得分:0)

在将聚合用于分页时,请注意。如果API经常用于用户获取数据,则最好使用两个查询。当更多的用户在线访问系统时,这至少比在生产服务器上使用聚合获取数据快50倍。总计和$ facet更适合不太频繁调用的Dashboard,报表和cron作业。