Получить общее количество документов с помощью MongoDB при использовании лимита

Я заинтересован в оптимизации решения "pagination", над которым я работаю с MongoDB. Моя проблема прямолинейна. Обычно я ограничиваю количество возвращаемых документов с помощью функции limit(). Это заставляет меня выдавать избыточный запрос без функции limit(), чтобы я мог также захватить общее количество документов в запросе, чтобы я мог передать это клиенту, сообщив им, что им придется выдать дополнительный запрос для извлечения остальных документов.

Есть ли способ сконденсировать это в 1 запрос? Получить общее количество документов, но в то же время получить только подмножество с помощью limit()? Есть ли другой способ думать об этой проблеме, чем я приближаюсь к ней?

Ответы

Ответ 1

Нет, другого пути нет. Два запроса - один для подсчета - один с лимитом. Или вам нужно использовать другую базу данных. Apache Solr, например, работает так, как вы хотите. Каждый запрос имеет ограниченные и возвращает totalCount.

Ответ 2

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

Даже вы можете использовать агрегацию $count которая была представлена в mongodb 3.6.

Вы можете использовать агрегацию ниже в mongodb 3.6

db.collection.aggregate([
  { "$facet": {
    "totalData": [
      { "$match": { }},
      { "$skip": 10 },
      { "$limit": 10 }
    ],
    "totalCount": [
      { "$count": "count" }
    ]
  }}
])

Ответ 3

есть способ в монгольде 3.4: $facet

вы можете сделать

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

тогда вы сможете одновременно запускать два агрегата

Ответ 4

Времена изменились, и я считаю, что вы можете достичь того, что спрашивает OP, используя агрегацию с $sort, $group и $project. Для моей системы мне также нужно было получить некоторую информацию о пользователе из моей коллекции 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
      ]
    }
  } }
])

Ответ 5

Все зависит от опыта разбивки на страницы, который вам нужен, независимо от того, нужно ли вам выполнять два запроса.

Вам нужно перечислить каждую страницу или даже целый ряд страниц? Кто-нибудь даже идет на страницу 1051 - концептуально, что это значит?

Theres было много UX по шаблонам разбивки на страницы - Избегайте болей разбивки на страницы, охватывает различные типы разбивки на страницы и их сценарии, и многие из них не нужен запрос count, чтобы узнать, есть ли следующая страница. Например, если вы отобразите 10 элементов на странице, и вы ограничены до 13 - вы узнаете, есть ли другая страница.

Ответ 6

MongoDB позволяет использовать cursor.count() даже при передаче limit() или 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 } 
}

Ответ 7

Вы можете сделать это в одном запросе. Сначала вы запускаете счетчик и внутри этого запускаете функцию limit().

В Node.js и Express.js вам придется использовать его таким образом, чтобы иметь возможность использовать функцию count и вместе с результатом resultArray.

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

    });

});

Ответ 8

Можно получить общий размер результата без эффекта limit() с помощью count(), как здесь указано: Ограничить результаты в MongoDB, но все равно получить полный счет?

В соответствии с документацией вы можете даже контролировать, учитывается ли ограничение/разбиение на страницы при вызове 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)

Ответ 9

Попробуйте как ниже:

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

Ответ 10

Вы можете использовать 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()
 };