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?
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.
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" }
]
}}
])
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
]
}
} }
])
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
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 }
}
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.
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()
};
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
});
});
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})
})
})
})
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)