J'écris une webapp avec Node.js et mangouste. Comment puis-je paginer les résultats d'un appel .find()
? Je voudrais une fonctionnalité comparable à "LIMIT 50,100"
en SQL.
Après avoir examiné de plus près l'API Mongoose avec les informations fournies par Rodolphe, j'ai trouvé cette solution
MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
Je suis très déçu par les réponses acceptées à cette question. Cela ne sera pas à l'échelle. Si vous lisez les petits caractères sur cursor.skip ():
La méthode cursor.skip () est souvent coûteuse, car le serveur doit parcourir le début de la collection ou de l'index pour obtenir la position de décalage ou de saut avant de commencer à renvoyer le résultat. Au fur et à mesure que le décalage (par exemple, numéro de page ci-dessus) augmente, cursor.skip () devient plus lent et utilise davantage le processeur. Cursor.skip () peut devenir IO lié.
Pour réaliser la pagination de manière évolutive, associez une limite () à au moins un critère de filtre, une date createdOn convient à de nombreuses fins.
MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
Pagination utilisant mangouste, express et jade - http://madhums.me/2012/08/20/pagination-using-mongoose-express-and-jade/
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
})
})
})
Vous pouvez enchaîner comme ça:
var query = Model.find().sort('mykey', 1).skip(2).limit(5)
Exécutez la requête en utilisant exec
query.exec(callback);
Vous pouvez utiliser un petit paquet nommé Mongoose Paginate qui facilite les choses.
$ npm install mongoose-paginate
Après dans vos routes ou contrôleur, ajoutez simplement:
/**
* 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);
}
}
Mieux vaut tard que jamais.
var pageOptions = {
page: req.query.page || 0,
limit: req.query.limit || 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);
})
Dans ce cas, vous pouvez ajouter la requête page
et/ou limit
à votre URL http. Exemple ?page=0&limit=25
BTW La pagination commence par 0
Ceci est un exemple d'exemple que vous pouvez essayer,
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
});
});
});
Essayez d’utiliser la fonction mangouste pour la pagination. Limite est le nombre d'enregistrements par page et le nombre de pages.
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){
});
C'est ce que je l'ai fait sur le code
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
});
C'est comme ça que je l'ai fait.
Voici une version que je joins à tous mes modèles. Cela dépend de soulignement pour plus de commodité et async pour les performances. L'option permet de sélectionner et de trier les champs en utilisant la syntaxe de la mangouste.
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;
}
Attachez-le à votre schéma de modèle.
MySchema.statics.findPaginated = findPaginated;
Le moyen le plus simple et le plus rapide consiste à paginer avec l’objet objectId.
Condition de charge initiale
condition = {limit:12, type:""};
Prendre le premier et le dernier ObjectId à partir des données de réponse
Page suivante condition
condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c662d", lastId:"57762a4c875adce3c38c6615"};
Page suivante condition
condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c6645", lastId:"57762a4c875adce3c38c6675"};
En mangouste
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);
});
La meilleure approche (OMI) consiste à utiliser ignorer et limiter MAIS dans un nombre limité de collections ou de documents.
Pour effectuer la requête dans des documents limités, nous pouvons utiliser un index spécifique, tel que l'index sur un champ de type DATE. Voir ci-dessous
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)
Ceci est un exemple de fonction permettant d’obtenir le résultat du modèle de compétences avec options de pagination et de limitation
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
}
Le plugin le plus simple pour la pagination.
https://www.npmjs.com/package/mongoose-paginate-v2
Ajoutez un plugin à un schéma, puis utilisez la méthode model paginate:
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
Solution de pagination simple et puissante
async getNextDocs(last_doc_id?: string) {
let docs
if (!last_doc_id) {
// get first 5 docs
docs = await MySchema.find().sort({ _id: -1 }).limit(5)
}
else {
// get next 5 docs according to that last document id
docs = await MySchema.find({ _id: { $lt: last_doc_id} }).sort({ _id: -1 }).limit(5)
}
return docs
}
Ci-dessus, la réponse est valable.
Juste un complément pour quiconque est dans l'attente asynchrone plutôt que. promettre !!
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
});
}
};
Vous pouvez écrire une requête comme celle-ci.
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);
}
});
page: numéro de page provenant du client en tant que paramètres de demande.
per_page: nombre de résultats affichés par page
Si vous utilisez la pile MEAN, les articles suivants du blog fournissent la plupart des informations permettant de créer une pagination en tête de réseau à l’aide de bootstrap angular-UI et en utilisant des méthodes de saut et de limitation mongoose dans le backend.
voir: https://techpituwa.wordpress.com/2015/06/06/mean-js-pagination-with-angular-ui-bootstrap/
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});
}
});
});
vous pouvez aussi utiliser la ligne de code suivante
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()
ce code fonctionnera dans la dernière version de mongo
Si vous utilisez la mangouste comme source pour une api reposante, jetez un œil à ' restify-mongoose ' et à ses requêtes. Il a exactement cette fonctionnalité intégrée.
Toute requête sur une collection fournit des en-têtes utiles ici.
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
Donc, en gros, vous obtenez un serveur générique avec un temps de chargement relativement linéaire pour les requêtes sur les collections. C'est génial et c'est quelque chose à regarder si vous voulez aller dans une implémentation propre.
A pu obtenir des résultats avec async/wait aussi.
Exemple de code ci-dessous utilisant un gestionnaire asynchrone avec hapi v17 et 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;
}
}
}
**//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);
});
});
}
Vous pouvez soit utiliser skip () et limit (), mais c'est très inefficace. Une meilleure solution consisterait à trier les champs indexés plus limit (). Chez Wunderflats, nous avons publié une petite bibliothèque ici: https://github.com/wunderflats/goosepage Elle utilise la première méthode.