C'est ce qu'on appelle la « pagination vers l'avant », un concept que vous pouvez utiliser pour « feuilleter efficacement » les résultats dans une direction « vers l'avant » lorsque vous utilisez des résultats « triés ».
Logique JavaScript incluse (car elle fonctionne dans le shell), mais pas difficile à traduire.
Le concept en général :
{ "_id": 1, "a": 3 },
{ "_id": 2, "a": 3 },
{ "_id": 3, "a": 3 },
{ "_id": 4, "a": 2 },
{ "_id": 5, "a": 1 },
{ "_id": 6, "a": 0 }
Considérez ces documents "déjà triés" (pour plus de commodité) comme un exemple de résultats que nous voulons "pager" par "deux" éléments par page.
Dans un premier temps, vous faites quelque chose comme ceci :
var lastVal = null,
lastSeen = [];
db.collection.find().sort({ "a": -1 }).limit(2).forEach(function(doc) {
if ( lastVal != doc.a ) {
lastSeen = [];
}
lastVal = doc.a;
lastSeen.push( doc._id );
// do something useful with each document matched
});
Maintenant, ces lastVal
et lastSeen
sont quelque chose que vous stockez dans quelque chose comme une "variable de session" à laquelle vous pouvez accéder lors de la prochaine requête en termes d'applications Web, ou quelque chose de similaire là où ce n'est pas le cas.
Cependant, ils doivent contenir la toute dernière valeur sur laquelle vous avez trié et la liste des _id
"uniques" valeurs qui ont été vues depuis que cette valeur n'a pas changé. D'où :
lastVal = 3,
lastSeen = [1,2];
Le fait est que lorsque la demande de "page suivante" arrive, vous souhaitez utiliser ces variables pour quelque chose comme ceci :
var lastVal = 3,
lastSeen = [1,2];
db.collection.find({
"_id": { "$nin": lastSeen },
"a": { "$lte": lastVal }
}).sort({ "a": -1 }).limit(2).forEach(function(doc) {
if ( lastVal != doc.a ) {
lastSeen = [];
}
lastVal = doc.a;
lastSeen.push( doc._id );
// do something useful with each document matched
});
Cela "exclut" toutes les valeurs de _id
qui sont enregistrés dans lastSeen
de la liste des résultats, et assurez-vous que tous les résultats doivent être "inférieurs ou égaux" (ordre décroissant) au lastVal
enregistré pour le champ de tri "a".
Cela donne les deux résultats suivants dans la collection :
{ "_id": 3, "a": 3 },
{ "_id": 4, "a": 2 },
Mais après traitement, nos valeurs ressemblent maintenant à ceci :
lastVal = 2,
lastSeen = [4];
Alors maintenant, la logique suit que vous n'avez pas besoin d'exclure l'autre _id
valeurs vues auparavant puisque vous ne recherchez vraiment que des valeurs de "a" qui sont "inférieures ou égales" au lastVal
et puisqu'il n'y avait qu'"un" _id
la valeur vue à cette valeur n'exclut que celle-là.
Cela donne bien sûr la page suivante sur l'utilisation du même code que ci-dessus :
{ "_id": 5, "a": 1 },
{ "_id": 6, "a": 0 }
C'est le moyen le plus efficace de "faire suivre la page" dans les résultats en général et c'est particulièrement utile pour une pagination efficace des résultats "triés".
Si toutefois vous souhaitez "sauter" à la page 20
ou une action similaire à n'importe quel stade, ce n'est pas pour vous. Vous êtes coincé avec le traditionnel .skip()
et .limit()
approche pour pouvoir le faire par "numéro de page" puisqu'il n'y a pas d'autre moyen rationnel de "calculer" cela.
Tout dépend donc de la manière dont votre application implémente la "pagination" et de ce avec quoi vous pouvez vivre. Le .skip()
et .limit()
L'approche souffre des performances de "saut" et peut être évitée en utilisant l'approche ici.
D'un autre côté, si vous voulez "passer à la page", alors "sauter" est votre seule véritable option, sauf si vous souhaitez créer un "cache" de résultats. Mais c'est un tout autre problème.