web-dev-qa-db-fra.com

Obtenez un nombre total de documents avec MongoDB lors de l'utilisation de limit

Je souhaite optimiser une solution de "pagination" sur laquelle je travaille avec MongoDB. Mon problème est simple. Je limite généralement le nombre de documents retournés à l'aide de la fonctionnalité limit(). Cela m'oblige à émettre une requête redondante sans la fonction limit() afin que je puisse également capturer le nombre total de documents dans la requête afin que je puisse le transmettre au client en lui faisant savoir qu'il devra émettre une ou des demandes supplémentaires pour récupérer le reste des documents.

Existe-t-il un moyen de condenser cela en 1 requête? Obtenez le nombre total de documents mais en même temps ne récupérez qu'un sous-ensemble en utilisant limit()? Y a-t-il une manière différente de penser à ce problème que je ne l'aborde?

39
randombits

Non, il n'y a pas d'autre moyen. Deux requêtes - une pour le compte - une avec limite. Ou vous devez utiliser une base de données différente. Apache Solr, par exemple, fonctionne comme vous le souhaitez. Chaque requête est limitée et renvoie totalCount.

23
heinob

Mongodb 3.4 a introduit $facet agrégation

qui traite plusieurs pipelines d'agrégation en une seule étape sur le même ensemble de documents d'entrée.

Utilisation de $facet et $group vous pouvez trouver des documents avec $limit et peut obtenir le nombre total.

Vous pouvez utiliser l'agrégation ci-dessous dans mongodb 3.4

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

Même vous pouvez utiliser $count agrégation qui a été introduite dans mongodb 3.6 .

Vous pouvez utiliser l'agrégation ci-dessous dans mongodb 3.6

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

Les temps ont changé et je pense que vous pouvez obtenir ce que l'OP demande en utilisant l'agrégation avec $sort, $group et $project. Pour mon système, je devais également récupérer des informations utilisateur de ma collection users. J'espère que cela pourra également répondre à toutes les questions à ce sujet. Vous trouverez ci-dessous un tuyau d'agrégation. Les trois derniers objets (tri, groupe et projet) permettent de gérer le nombre total, puis de fournir des capacités de pagination.

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

il y a un moyen dans Mongodb 3.4: $ facet

tu peux faire

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

alors vous pourrez exécuter deux agrégats en même temps

7
Matthew

MongoDB vous permet d'utiliser cursor.count() même lorsque vous passez limit() ou skip().

Disons que vous avez un db.collection avec 10 articles.

Tu peux faire:

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 } 
}
3
Daniel Varela

Tout dépend de l'expérience de pagination dont vous avez besoin pour savoir si vous devez ou non effectuer deux requêtes.

Avez-vous besoin de répertorier chaque page ou même une série de pages? Est-ce que quelqu'un va même à la page 1051 - conceptuellement qu'est-ce que cela signifie réellement?

Il y a eu beaucoup d'UX sur les modèles de pagination - Évitez les douleurs de pagination couvre différents types de pagination et leurs scénarios et beaucoup n'ont pas besoin d'une requête de décompte pour savoir s'il y a une page suivante. Par exemple, si vous affichez 10 éléments sur une page et que vous limitez à 13 - vous saurez s'il y a une autre page.

3
Ross

Par défaut, la méthode count () ignore les effets des curseurs cursor.skip () et cursor.limit () ( MongoDB docs )

Comme la méthode de comptage exclut les effets de limite et de saut, vous pouvez utiliser cursor.count() pour obtenir le nombre total

 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()
 };
3
Suraj Chandola

Vous pouvez le faire en une seule requête. D'abord, vous exécutez un compte et à l'intérieur de celui-ci exécutez la fonction limit ().

Dans Node.js et Express.js, vous devrez l'utiliser comme ceci pour pouvoir utiliser la fonction "count" avec le "résultat" de toArray.

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

Ensuite, vous pouvez exécuter deux fonctions comme suit (l'une imbriquée dans l'autre)

curFind.count(function (e, count) {

// Use count here

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

    // Use result here and count here

    });

});
0
v1000

Essayez comme ci-dessous:

cursor.count (false, fonction (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})
        })
    })
 })
0
surinder singh

Il est possible d'obtenir la taille totale du résultat sans l'effet de limit() en utilisant count() comme indiqué ici: Limiter les résultats dans MongoDB mais toujours obtenir le nombre total?

Selon la documentation, vous pouvez même contrôler si la limite/pagination est prise en compte lors de l'appel de count(): https://docs.mongodb.com/manual/reference/method/cursor.count/ # cursor.count

Edit: contrairement à ce qui est écrit ailleurs - les documents indiquent clairement que "L'opération n'effectue pas la requête mais compte plutôt les résultats qui seraient retournés par la requête" . Ce qui - d'après ma compréhension - signifie qu'une seule requête est exécutée.

Exemple:

> 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)
0
mrechtien