Come impaginare con Mongoose in Node.js?


Risposte:


278

Sono molto deluso dalle risposte accettate in questa domanda. Questo non si ridimensionerà. Se leggi la stampa fine su cursor.skip ():

Il metodo cursor.skip () è spesso costoso perché richiede che il server passi dall'inizio della raccolta o dell'indice per ottenere l'offset o saltare la posizione prima di iniziare a restituire il risultato. Man mano che aumenta l'offset (ad es. Il numero di pagina sopra), cursor.skip () diventerà più lento e richiederà più CPU. Con raccolte più grandi, cursore.skip () può diventare IO-legato.

Per ottenere l'impaginazione in modo scalabile, combinare un limite () insieme ad almeno un criterio di filtro, una data di creazione adatta a molti scopi.

MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )

105
Ma come otterresti la seconda pagina da quella query senza saltare? Se stai visualizzando 10 risultati per pagina e ci sono 100 risultati, come definisci l'offset o salta il valore? Non stai rispondendo alla domanda di impaginazione, quindi non puoi essere "deluso", sebbene sia una valida avvertenza. Sebbene lo stesso problema sia nell'offset di MySQL, limita. Deve attraversare l'albero fino all'offset prima di restituire i risultati. Lo prenderei con un pizzico di sale, se i tuoi set di risultati sono inferiori a 1mil e non ci sono hit di prestazioni conservabili, usa skip ().
Lex,

13
Sono un noob quando si tratta di mongoose / mongodb, ma per rispondere alla domanda di Lex, sembra che, poiché i risultati sono ordinati per " -createdOn", si sostituirà il valore di request.createdOnBeforecon il valore minimo createdOnrestituito nel set di risultati precedente, e quindi requery.
Terry Lewis,

9
@JoeFrambach Le richieste basate su createOn sembrano problematiche. Skip è stato incorporato per un motivo. I documenti sono solo un avvertimento dell'hit di prestazioni del ciclo attraverso l'indice btree, come nel caso di tutti i DBMS. Per la domanda degli utenti "qualcosa di paragonabile a MySQL a LIMIT 50.100" .skip ha esattamente ragione.
Lex,

8
Sebbene interessante, un problema con questa risposta, come osserva il commento di @Lex, è che puoi solo saltare "avanti" o "indietro" attraverso i risultati - non puoi avere "pagine" alle quali puoi saltare (es. Pagina 1, Pagina 2 , Pagina 3) senza fare più query sequenziali per capire da dove iniziare l'impaginazione, che sospetto che sarà più lento nella maggior parte dei casi rispetto all'uso di skip. Naturalmente potrebbe non essere necessario aggiungere la possibilità di saltare a pagine specifiche.
Iain Collins,

20
Questa risposta contiene punti interessanti, ma non risponde alla domanda originale posta.
potenziato a vapore il

227

Dopo aver esaminato più da vicino l'API Mongoose con le informazioni fornite da Rodolphe, ho capito questa soluzione:

MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });

21
Che dire di "contare"? Ne hai bisogno per sapere quante pagine ci sono.
Aleksey Saatchi,

36
Non si ridimensiona.
Chris Hinkle,

4
La spiegazione di Chris Hinkle perché questo non si ridimensiona: stackoverflow.com/a/23640287/165330 .
imme

7
@ChrisHinkle Questo sembra essere il caso di tutti i DBMS. Il commento di Lex sotto la risposta collegata sembra spiegare di più.
csotiriou,

2
@Avij sì. Ho usato l'identificatore per quello. quello che fai lì è rispedire l'ultimo ID record al server e ottenere alcuni record con ID maggiore di quello inviato. Dato che l'ID è indicizzato, sarà molto più veloce
George Bailey,

108

Impaginazione con mangusta, espresso e giada - Ecco un link al mio blog con maggiori dettagli

var perPage = 10
  , page = Math.max(0, req.param('page'))

Event.find()
    .select('name')
    .limit(perPage)
    .skip(perPage * page)
    .sort({
        name: 'asc'
    })
    .exec(function(err, events) {
        Event.count().exec(function(err, count) {
            res.render('events', {
                events: events,
                page: page,
                pages: count / perPage
            })
        })
    })

26
Grazie per aver pubblicato la tua risposta! Assicurati di leggere attentamente le FAQ sull'autopromozione . Si noti inoltre che è necessario pubblicare un disclaimer ogni volta che si collega al proprio sito / prodotto.
Andrew Barber,

Math.max(0, undefined)tornerà undefined, Questo ha funzionato per me:let limit = Math.abs(req.query.limit) || 10; let page = (Math.abs(req.query.page) || 1) - 1; Schema.find().limit(limit).skip(limit * page)
Monfa.red

55

Puoi concatenarti così:

var query = Model.find().sort('mykey', 1).skip(2).limit(5)

Eseguire la query utilizzando exec

query.exec(callback);

Grazie per la risposta, come viene aggiunta la richiamata con il risultato?
Thomas,

2
execFind (funzione (... ad esempio: var page = req.param('p'); var per_page = 10; if (page == null) { page = 0; } Location.count({}, function(err, count) { Location.find({}).skip(page*per_page).limit(per_page).execFind(function(err, locations) { res.render('index', { locations: locations }); }); });
todd

9
nota: questo non funzionerà in mangusta , ma funzionerà in mongodb-native-driver.
Jesse,

39

In questo caso, puoi aggiungere la query pagee / o limital tuo URL come stringa di query.

Per esempio:
?page=0&limit=25 // this would be added onto your URL: http:localhost:5000?page=0&limit=25

Dal momento che sarebbe un, Stringdobbiamo convertirlo in un Numberper i nostri calcoli. Facciamolo utilizzando il parseIntmetodo e forniamo anche alcuni valori predefiniti.

const pageOptions = {
    page: parseInt(req.query.page, 10) || 0,
    limit: parseInt(req.query.limit, 10) || 10
}

sexyModel.find()
    .skip(pageOptions.page * pageOptions.limit)
    .limit(pageOptions.limit)
    .exec(function (err, doc) {
        if(err) { res.status(500).json(err); return; };
        res.status(200).json(doc);
    });

La paginazione BTW inizia con0


5
si prega di aggiungere la `{pagina: parseInt (req.query.page) || 0, ...} al parametro.
imalik8088,

@ imalik8088 Grazie, tuttavia i parametri della stringa AFAIK vengono gestiti automaticamente da mongoose.
CENT1PEDE,

1
Aspettavo il comportamento, ma nel mio caso non è stato possibile
nascondere

@ imalik8088 È strano. Forse se potessi mostrare un errore di riproduzione posso modificare la mia risposta. Grazie.
CENT1PEDE,

2
Ciò indurrebbe la mangusta a trovare ogni record prima di applicare le condizioni?
FluffyBeing

37

Puoi usare un piccolo pacchetto chiamato Mongoose Paginate che lo rende più semplice.

$ npm install mongoose-paginate

Dopo nei percorsi o nel controller, basta aggiungere:

/**
 * querying for `all` {} items in `MyModel`
 * paginating by second page, 10 items per page (10 results, page 2)
 **/

MyModel.paginate({}, 2, 10, function(error, pageCount, paginatedResults) {
  if (error) {
    console.error(error);
  } else {
    console.log('Pages:', pageCount);
    console.log(paginatedResults);
  }
}

2
Questo è ottimizzato?
Argento,

16

Questo è un esempio di esempio che puoi provare,

var _pageNumber = 2,
  _pageSize = 50;

Student.count({},function(err,count){
  Student.find({}, null, {
    sort: {
      Name: 1
    }
  }).skip(_pageNumber > 0 ? ((_pageNumber - 1) * _pageSize) : 0).limit(_pageSize).exec(function(err, docs) {
    if (err)
      res.json(err);
    else
      res.json({
        "TotalCount": count,
        "_Array": docs
      });
  });
 });

11

Prova a utilizzare la funzione mangusta per l'impaginazione. Il limite è il numero di record per pagina e il numero della pagina.

var limit = parseInt(body.limit);
var skip = (parseInt(body.page)-1) * parseInt(limit);

 db.Rankings.find({})
            .sort('-id')
            .limit(limit)
            .skip(skip)
            .exec(function(err,wins){
 });

10

Questo è quello che ho fatto sul codice

var paginate = 20;
var page = pageNumber;
MySchema.find({}).sort('mykey', 1).skip((pageNumber-1)*paginate).limit(paginate)
    .exec(function(err, result) {
        // Write some stuff here
    });

È così che l'ho fatto.


1
Come ottenere il numero totale di pagine
Rhushikesh,

Ciao @Rhushikesh, puoi usare una funzione count () per ottenere il conteggio. Ma sembra che debba essere un'altra query dal database. Dettagli qui mongoosejs.com/docs/api.html#model_Model.count
Indra Santosa

@Rhushikesh ottiene il conteggio e lo divide per il limite
terzo anno

count()è deprecato. usocountDocuments()
Ruslan,

7

Query;
search = productName,

Parametri;
pagina = 1

// Pagination
router.get("/search/:page", (req, res, next) => {
  const resultsPerPage = 5;
  const page = req.params.page >= 1 ? req.params.page : 1;
  const query = req.query.search;

  Product.find({ name: query })
    .select("name")
    .sort({ name: "asc" })
    .limit(resultsPerPage)
    .skip(resultsPerPage * page)
    .then((results) => {
      return res.status(200).send(results);
    })
    .catch((err) => {
      return res.status(500).send(err);
    });
});

Grazie per questa risposta; L'ho provato prima dopo aver letto il thread perché era uno dei più recenti. Tuttavia, quando l'ho implementato, ho scoperto un bug - come è scritto ora, non restituirà mai la prima pagina di risultati, poiché avrà SEMPRE un valore di salto. Prova ad aggiungere "page = page-1" prima della chiamata Product.find ().
Interog,

6

Ecco una versione che allego a tutti i miei modelli. Dipende dal carattere di sottolineatura per praticità e asincrono per le prestazioni. Le opzioni consentono la selezione e l'ordinamento dei campi utilizzando la sintassi della mangusta.

var _ = require('underscore');
var async = require('async');

function findPaginated(filter, opts, cb) {
  var defaults = {skip : 0, limit : 10};
  opts = _.extend({}, defaults, opts);

  filter = _.extend({}, filter);

  var cntQry = this.find(filter);
  var qry = this.find(filter);

  if (opts.sort) {
    qry = qry.sort(opts.sort);
  }
  if (opts.fields) {
    qry = qry.select(opts.fields);
  }

  qry = qry.limit(opts.limit).skip(opts.skip);

  async.parallel(
    [
      function (cb) {
        cntQry.count(cb);
      },
      function (cb) {
        qry.exec(cb);
      }
    ],
    function (err, results) {
      if (err) return cb(err);
      var count = 0, ret = [];

      _.each(results, function (r) {
        if (typeof(r) == 'number') {
          count = r;
        } else if (typeof(r) != 'number') {
          ret = r;
        }
      });

      cb(null, {totalCount : count, results : ret});
    }
  );

  return qry;
}

Allegalo allo schema del modello.

MySchema.statics.findPaginated = findPaginated;

6

Soluzione di impaginazione semplice e potente

async getNextDocs(no_of_docs_required: number, last_doc_id?: string) {
    let docs

    if (!last_doc_id) {
        // get first 5 docs
        docs = await MySchema.find().sort({ _id: -1 }).limit(no_of_docs_required)
    }
    else {
        // get next 5 docs according to that last document id
        docs = await MySchema.find({_id: {$lt: last_doc_id}})
                                    .sort({ _id: -1 }).limit(no_of_docs_required)
    }
    return docs
}

last_doc_id: l'ultimo ID documento che ottieni

no_of_docs_required: il numero di documenti che vuoi recuperare, ad esempio 5, 10, 50 ecc.

  1. Se non fornisci il last_doc_idmetodo, otterrai cioè 5 documenti più recenti
  2. Se hai fornito il last_doc_id, otterrai i successivi 5 documenti.

5

La risposta sopra vale.

Solo un componente aggiuntivo per chiunque sia in asincrono-attende piuttosto che promettere !!

const findAllFoo = async (req, resp, next) => {
    const pageSize = 10;
    const currentPage = 1;

    try {
        const foos = await FooModel.find() // find all documents
            .skip(pageSize * (currentPage - 1)) // we will not retrieve all records, but will skip first 'n' records
            .limit(pageSize); // will limit/restrict the number of records to display

        const numberOfFoos = await FooModel.countDocuments(); // count the number of records for that model

        resp.setHeader('max-records', numberOfFoos);
        resp.status(200).json(foos);

    } catch (err) {
        resp.status(500).json({
            message: err
        });
    }
};

4

puoi usare anche la seguente riga di codice

per_page = parseInt(req.query.per_page) || 10
page_no = parseInt(req.query.page_no) || 1
var pagination = {
  limit: per_page ,
  skip:per_page * (page_no - 1)
}
users = await User.find({<CONDITION>}).limit(pagination.limit).skip(pagination.skip).exec()

questo codice funzionerà nell'ultima versione di mongo


3

Un approccio solido per implementare questo sarebbe quello di passare i valori dal frontend usando una stringa di query . Supponiamo di voler ottenere la pagina 2 e limitare anche l'output a 25 risultati .
La stringa della query sarebbe simile alla seguente:?page=2&limit=25 // this would be added onto your URL: http:localhost:5000?page=2&limit=25

Vediamo il codice:

// We would receive the values with req.query.<<valueName>>  => e.g. req.query.page
// Since it would be a String we need to convert it to a Number in order to do our
// necessary calculations. Let's do it using the parseInt() method and let's also provide some default values:

  const page = parseInt(req.query.page, 10) || 1; // getting the 'page' value
  const limit = parseInt(req.query.limit, 10) || 25; // getting the 'limit' value
  const startIndex = (page - 1) * limit; // this is how we would calculate the start index aka the SKIP value
  const endIndex = page * limit; // this is how we would calculate the end index

// We also need the 'total' and we can get it easily using the Mongoose built-in **countDocuments** method
  const total = await <<modelName>>.countDocuments();

// skip() will return a certain number of results after a certain number of documents.
// limit() is used to specify the maximum number of results to be returned.

// Let's assume that both are set (if that's not the case, the default value will be used for)

  query = query.skip(startIndex).limit(limit);

  // Executing the query
  const results = await query;

  // Pagination result 
 // Let's now prepare an object for the frontend
  const pagination = {};

// If the endIndex is smaller than the total number of documents, we have a next page
  if (endIndex < total) {
    pagination.next = {
      page: page + 1,
      limit
    };
  }

// If the startIndex is greater than 0, we have a previous page
  if (startIndex > 0) {
    pagination.prev = {
      page: page - 1,
      limit
    };
  }

 // Implementing some final touches and making a successful response (Express.js)

const advancedResults = {
    success: true,
    count: results.length,
    pagination,
    data: results
 }
// That's it. All we have to do now is send the `results` to the frontend.
 res.status(200).json(advancedResults);

Suggerirei di implementare questa logica nel middleware in modo che tu possa essere in grado di usarla per vari percorsi / controller.


2

Il modo più semplice e veloce è, impaginare con objectId Esempio;

Condizione di carico iniziale

condition = {limit:12, type:""};

Prendi il primo e l'ultimo ObjectId dai dati di risposta

Pagina prossima condizione

condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c662d", lastId:"57762a4c875adce3c38c6615"};

Pagina prossima condizione

condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c6645", lastId:"57762a4c875adce3c38c6675"};

In mangusta

var condition = {};
    var sort = { _id: 1 };
    if (req.body.type == "next") {
        condition._id = { $gt: req.body.lastId };
    } else if (req.body.type == "prev") {
        sort = { _id: -1 };
        condition._id = { $lt: req.body.firstId };
    }

var query = Model.find(condition, {}, { sort: sort }).limit(req.body.limit);

query.exec(function(err, properties) {
        return res.json({ "result": result);
});

2

L'approccio migliore (IMO) consiste nell'utilizzare salta e limita MA all'interno di raccolte o documenti limitati.

Per eseguire la query all'interno di documenti limitati, possiamo utilizzare un indice specifico come l'indice in un campo del tipo DATA. Vedi sotto

let page = ctx.request.body.page || 1
let size = ctx.request.body.size || 10
let DATE_FROM = ctx.request.body.date_from
let DATE_TO = ctx.request.body.date_to

var start = (parseInt(page) - 1) * parseInt(size)

let result = await Model.find({ created_at: { $lte: DATE_FROM, $gte: DATE_TO } })
    .sort({ _id: -1 })
    .select('<fields>')
    .skip( start )
    .limit( size )        
    .exec(callback)

2

Plugin più semplice per l'impaginazione.

https://www.npmjs.com/package/mongoose-paginate-v2

Aggiungi plug-in a uno schema e quindi utilizza il metodo di paginazione del modello:

var mongoose         = require('mongoose');
var mongoosePaginate = require('mongoose-paginate-v2');

var mySchema = new mongoose.Schema({ 
    /* your schema definition */ 
});

mySchema.plugin(mongoosePaginate);

var myModel = mongoose.model('SampleModel',  mySchema); 

myModel.paginate().then({}) // Usage

questo plugin è rotto con mongoose v5.5.5
Isaac Pak

1

Questa è una funzione di esempio per ottenere il risultato del modello di competenze con opzioni di impaginazione e limite

 export function get_skills(req, res){
     console.log('get_skills');
     var page = req.body.page; // 1 or 2
     var size = req.body.size; // 5 or 10 per page
     var query = {};
     if(page < 0 || page === 0)
     {
        result = {'status': 401,'message':'invalid page number,should start with 1'};
        return res.json(result);
     }
     query.skip = size * (page - 1)
     query.limit = size
     Skills.count({},function(err1,tot_count){ //to get the total count of skills
      if(err1)
      {
         res.json({
            status: 401,
            message:'something went wrong!',
            err: err,
         })
      }
      else 
      {
         Skills.find({},{},query).sort({'name':1}).exec(function(err,skill_doc){
             if(!err)
             {
                 res.json({
                     status: 200,
                     message:'Skills list',
                     data: data,
                     tot_count: tot_count,
                 })
             }
             else
             {
                 res.json({
                      status: 401,
                      message: 'something went wrong',
                      err: err
                 })
             }
        }) //Skills.find end
    }
 });//Skills.count end

}


0

Puoi scrivere query in questo modo.

mySchema.find().skip((page-1)*per_page).limit(per_page).exec(function(err, articles) {
        if (err) {
            return res.status(400).send({
                message: err
            });
        } else {
            res.json(articles);
        }
    });

pagina: numero di pagina proveniente dal client come parametri di richiesta.
per_page: numero di risultati visualizzati per pagina

Se si utilizza lo stack MEAN, il post di blog seguente fornisce molte delle informazioni per creare l'impaginazione nel front-end utilizzando il bootstrap dell'interfaccia utente angolare e utilizzando i metodi di salto e limite della mangusta nel back-end.

vedi: https://techpituwa.wordpress.com/2015/06/06/mean-js-pagination-with-angular-ui-bootstrap/


0

Puoi usare skip () e limit (), ma è molto inefficiente. Una soluzione migliore sarebbe una sorta di campo indicizzato più limit (). Noi di Wunderflats abbiamo pubblicato una piccola libreria qui: https://github.com/wunderflats/goosepage Utilizza il primo modo.


0

Se stai usando la mangusta come fonte per un'API riposante, dai un'occhiata a ' restify-mongoose ' e alle sue domande. Ha esattamente questa funzionalità integrata.

Qualsiasi query su una raccolta fornisce intestazioni che sono utili qui

test-01:~$ curl -s -D - localhost:3330/data?sort=-created -o /dev/null
HTTP/1.1 200 OK
link: </data?sort=-created&p=0>; rel="first", </data?sort=-created&p=1>; rel="next", </data?sort=-created&p=134715>; rel="last"
.....
Response-Time: 37

Quindi in pratica si ottiene un server generico con un tempo di caricamento relativamente lineare per le query alle raccolte. È fantastico e qualcosa da guardare se vuoi entrare in una propria implementazione.


0
app.get("/:page",(req,res)=>{
        post.find({}).then((data)=>{
            let per_page = 5;
            let num_page = Number(req.params.page);
            let max_pages = Math.ceil(data.length/per_page);
            if(num_page == 0 || num_page > max_pages){
                res.render('404');
            }else{
                let starting = per_page*(num_page-1)
                let ending = per_page+starting
                res.render('posts', {posts:data.slice(starting,ending), pages: max_pages, current_page: num_page});
            }
        });
});

0
**//localhost:3000/asanas/?pageNo=1&size=3**

//requiring asanas model
const asanas = require("../models/asanas");


const fetchAllAsanasDao = () => {
    return new Promise((resolve, reject) => {

    var pageNo = parseInt(req.query.pageNo);
    var size = parseInt(req.query.size);
    var query = {};
        if (pageNo < 0 || pageNo === 0) {
            response = {
                "error": true,
                "message": "invalid page number, should start with 1"
            };
            return res.json(response);
        }
        query.skip = size * (pageNo - 1);
        query.limit = size;

  asanas
            .find(pageNo , size , query)
        .then((asanasResult) => {
                resolve(asanasResult);
            })
            .catch((error) => {
                reject(error);
            });

    });
}

0

Usa questo semplice plugin.

https://github.com/WebGangster/mongoose-paginate-v2

Installazione

npm install mongoose-paginate-v2
Utilizzo Aggiungi plug-in a uno schema e quindi utilizza il metodo di paginazione del modello:

const mongoose         = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');

const mySchema = new mongoose.Schema({ 
  /* your schema definition */ 
});

mySchema.plugin(mongoosePaginate);

const myModel = mongoose.model('SampleModel',  mySchema); 

myModel.paginate().then({}) // Usage


Questo plugin è stato "suggerito" già in un'altra risposta. Sarebbe anche utile sapere che sei un collaboratore di questo pacchetto.
lukas_o,

@lukas_o Sì. Sono il creatore del plugin.
Aravind NC,

0

secondo

Chris Hinkle

risposta:

//assume every page has 50 result
const results = (req.query.page * 1) * 50;
MyModel.find( { fieldNumber: { $lte: results} })
.limit( 50 )
.sort( '+fieldNumber' )

//one thing left is create a fieldNumber on the schema thas holds ducument number

0

Usando l'impaginazione ts-mangusta

    const trainers = await Trainer.paginate(
        { user: req.userId },
        {
            perPage: 3,
            page: 1,
            select: '-password, -createdAt -updatedAt -__v',
            sort: { createdAt: -1 },
        }
    )

    return res.status(200).json(trainers)

0
let page,limit,skip,lastPage, query;
 page = req.params.page *1 || 1;  //This is the page,fetch from the server
 limit = req.params.limit * 1 || 1; //  This is the limit ,it also fetch from the server
 skip = (page - 1) * limit;   // Number of skip document
 lastPage = page * limit;   //last index 
 counts = await userModel.countDocuments() //Number of document in the collection

query = query.skip(skip).limit(limit) //current page

const paginate = {}

//For previous page
if(skip > 0) {
   paginate.prev = {
       page: page - 1,
       limit: limit
} 
//For next page
 if(lastPage < counts) {
  paginate.next = {
     page: page + 1,
     limit: limit
}
results = await query //Here is the final results of the query.

-1

È stato in grado di ottenere risultati anche con asincrono / wait.

Esempio di codice riportato di seguito utilizzando un gestore asincrono con hapi v17 e mongoose v5

{
            method: 'GET',
            path: '/api/v1/paintings',
            config: {
                description: 'Get all the paintings',
                tags: ['api', 'v1', 'all paintings']
            },
            handler: async (request, reply) => {
                /*
                 * Grab the querystring parameters
                 * page and limit to handle our pagination
                */
                var pageOptions = {
                    page: parseInt(request.query.page) - 1 || 0, 
                    limit: parseInt(request.query.limit) || 10
                }
                /*
                 * Apply our sort and limit
                */
               try {
                    return await Painting.find()
                        .sort({dateCreated: 1, dateModified: -1})
                        .skip(pageOptions.page * pageOptions.limit)
                        .limit(pageOptions.limit)
                        .exec();
               } catch(err) {
                   return err;
               }

            }
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.