web-dev-qa-db-fra.com

MongoDB - pagination

Lors de l'utilisation de MongoDB, existe-t-il des modèles spéciaux pour la fabrication, par exemple une vue paginée? dites un blog qui répertorie les 10 derniers articles où vous pouvez revenir en arrière vers des articles plus anciens.

Ou peut-on le résoudre avec un index sur par exemple blogpost.publishdate et simplement sauter et limiter le résultat?

74
Roger Johansson

L'utilisation de skip + limit n'est pas un bon moyen de faire de la pagination lorsque les performances sont un problème, ou avec de grandes collections; il deviendra de plus en plus lent à mesure que vous augmentez le nombre de pages. L'utilisation de skip oblige le serveur à parcourir tous les documents (ou valeurs d'index) de 0 à la valeur de décalage (skip).

Il est préférable d'utiliser une requête de plage (+ limite) où vous passez la valeur de plage de la dernière page. Par exemple, si vous triez par "date de publication", vous passeriez simplement la dernière valeur "date de publication" comme critère pour la requête pour obtenir la page de données suivante.

92
Scott Hernandez
  1. La pagination basée sur une plage est difficile à implémenter si vous devez trier des éléments de plusieurs façons.
  2. N'oubliez pas que si la valeur de champ du paramètre de tri n'est pas unique, la pagination basée sur la plage deviendra irréalisable.

Solution possible: essayez de simplifier la conception, en vous demandant si nous ne pouvons trier que par identifiant ou par une valeur unique?

Et si nous le pouvons, nous pouvons utiliser une page basée sur une plage.

La manière courante est d'utiliser sort (), skip () et limit () pour implémenter la pagination ce qui est décrit ci-dessus.

11
jackalope

C'est la solution que j'ai utilisée lorsque ma collection est devenue trop volumineuse pour être renvoyée en une seule requête. Il tire parti de l'ordre inhérent du _id champ et vous permet de parcourir une collection par taille de lot spécifiée.

Le voici sous forme de module npm, mongoose-paging , le code complet est ci-dessous:

function promiseWhile(condition, action) {
  return new Promise(function(resolve, reject) {
    process.nextTick(function loop() {
      if(!condition()) {
        resolve();
      } else {
        action().then(loop).catch(reject);
      }
    });
  });
}

function findPaged(query, fields, options, iterator, cb) {
  var Model  = this,
    step     = options.step,
    cursor   = null,
    length   = null;

  promiseWhile(function() {
    return ( length===null || length > 0 );
  }, function() {
    return new Promise(function(resolve, reject) {

        if(cursor) query['_id'] = { $gt: cursor };

        Model.find(query, fields, options).sort({_id: 1}).limit(step).exec(function(err, items) {
          if(err) {
            reject(err);
          } else {
            length  = items.length;
            if(length > 0) {
              cursor  = items[length - 1]._id;
              iterator(items, function(err) {
                if(err) {
                  reject(err);
                } else {
                  resolve();
                }
              });
            } else {
              resolve();
            }
          }
        });
      });
  }).then(cb).catch(cb);

}

module.exports = function(schema) {
  schema.statics.findPaged = findPaged;
};

Attachez-le à votre modèle comme ceci:

MySchema.plugin(findPaged);

Ensuite, interrogez comme ceci:

MyModel.findPaged(
  // mongoose query object, leave blank for all
  {source: 'email'},
  // fields to return, leave blank for all
  ['subject', 'message'],
  // number of results per page
  {step: 100},
  // iterator to call on each set of results
  function(results, cb) {
    console.log(results);
    // this is called repeatedly while until there are no more results.
    // results is an array of maximum length 100 containing the
    // results of your query

    // if all goes well
    cb();

    // if your async stuff has an error
    cb(err);
  },
  // function to call when finished looping
  function(err) {
    throw err;
    // this is called once there are no more results (err is null),
    // or if there is an error (then err is set)
  }
);
5
mz3

La pagination basée sur la plage est faisable, mais vous devez être intelligent sur la façon dont vous min/max la requête.

Si vous pouvez vous le permettre, essayez de mettre en cache les résultats d'une requête dans un fichier ou une collection temporaire. Grâce aux collections TTL dans MongoDB, vous pouvez insérer vos résultats dans deux collections.

  1. Recherche + Requête Utilisateur + Paramètres (TTL quel qu'il soit)
  2. Résultats de la requête (TTL quel que soit + intervalle de nettoyage + 1)

L'utilisation des deux garantit que vous n'obtiendrez pas de résultats partiels lorsque TTL est proche de l'heure actuelle. Vous pouvez utiliser un compteur simple lorsque vous stockez les résultats pour effectuer une requête de plage TRÈS simple à ce moment-là.

1
whardier

Voici un exemple de récupération d'une liste de User documents classés par CreatedDate (où pageIndex est basée sur zéro) à l'aide du pilote C # officiel.

public void List<User> GetUsers() 
{
  var connectionString = "<a connection string>";
  var client = new MongoClient(connectionString);
  var server = client.GetServer();
  var database = server.GetDatabase("<a database name>");

  var sortBy = SortBy<User>.Descending(u => u.CreatedDate);
  var collection = database.GetCollection<User>("Users");
  var cursor = collection.FindAll();
  cursor.SetSortOrder(sortBy);

  cursor.Skip = pageIndex * pageSize;
  cursor.Limit = pageSize;
  return cursor.ToList();
}

Toutes les opérations de tri et de pagination se font côté serveur. Bien que ce soit un exemple en C #, je suppose que la même chose peut être appliquée à d'autres ports de langue.

Voir http://docs.mongodb.org/ecosystem/tutorial/use-csharp-driver/#modifying-a-cursor-before-enumerating-it .

1
Alex Ho
    // file:ad-hoc.js
    // an example of using the less binary as pager in the bash Shell
    //
    // call on the Shell by:
    // mongo localhost:27017/mydb ad-hoc.js | less
    //
    // note ad-hoc.js must be in your current directory
    // replace the 27017 wit the port of your mongodb instance
    // replace the mydb with the name of the db you want to query
    //
    // create the connection obj
    conn = new Mongo();

    // set the db of the connection
    // replace the mydb with the name of the db you want to query
    db = conn.getDB("mydb");

    // replace the products with the name of the collection
    // populate my the products collection
    // this is just for demo purposes - you will probably have your data already
    for (var i=0;i<1000;i++ ) {
    db.products.insert(
        [
            { _id: i, item: "lamp", qty: 50, type: "desk" },
        ],
        { ordered: true }
    )
    }


    // replace the products with the name of the collection
    cursor = db.products.find();

    // print the collection contents
    while ( cursor.hasNext() ) {
        printjson( cursor.next() );
    }
    // eof file: ad-hoc.js
0
Yordan Georgiev