Как разбить на страницы с помощью Mongoose в Node.js?

180

Я пишу webapp с Node.js и mongoose. Как я могу разбивать на страницы результаты, полученные из вызова .find()? Я хотел бы функциональность, сравнимую с "LIMIT 50,100" в SQL.

  • 0
    Используйте пропустить и ограничить свойство при поиске данных из коллекции.
Теги:
mongoose
pagination

21 ответ

182
Лучший ответ

После более пристального изучения API Mongoose с информацией, предоставленной Rodolphe, я понял это решение:

MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
  • 15
    А как насчет "считать"? Вам нужно это знать, сколько страниц есть.
  • 31
    Не масштабируется.
Показать ещё 9 комментариев
257

Я очень разочарован принятыми ответами в этом вопросе. Это не будет масштабироваться. Если вы прочитали мелкий шрифт на cursor.skip():

Метод cursor.skip() часто дорог, потому что он требует, чтобы сервер шел от начала коллекции или индекса, чтобы получить смещение или пропустить позицию, прежде чем начать возвращать результат. По мере увеличения смещения (например, pageNumber выше) курсор .skip() будет работать медленнее и интенсивнее. С большими коллекциями cursor.skip() может стать привязанным к IO.

Чтобы достичь масштабирования с разбивкой по страницам, объедините предел() вместе с по меньшей мере одним критерием фильтра, дата createdOn подходит для многих целей.

MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
  • 77
    Но как бы вы получили вторую страницу из этого запроса без пропуска? Если вы просматриваете 10 результатов на странице, а результатов 100, как тогда вы определяете значение смещения или пропуска? Вы не отвечаете на вопрос о нумерации страниц, поэтому не можете быть «разочарованы», хотя это и есть серьезное предупреждение. Хотя та же проблема в смещении MySQL, предел. Он должен пройти по дереву до смещения, прежде чем возвращать результаты. Я бы взял это с недолгой солью, если ваши результирующие наборы меньше 1 мили и нет никакого заметного снижения производительности, используйте skip ().
  • 8
    Я нуб, когда дело доходит до mongoose / mongodb, но, чтобы ответить на вопрос Лекса, кажется, что, поскольку результаты упорядочены по ' -createdOn ', вы бы заменили значение request.createdOnBefore на наименьшее значение возвращенного createdOn в предыдущем наборе результатов, а затем запрос.
Показать ещё 13 комментариев
74

Разбиение страницы с использованием мангуста, экспресс и нефрита - 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
            })
        })
    })
  • 21
    Спасибо за публикацию вашего ответа! Пожалуйста, внимательно прочитайте FAQ по саморекламе . Также обратите внимание, что необходимо размещать заявление об отказе от ответственности при каждой ссылке на свой собственный сайт / продукт.
  • 0
    Math.max(0, undefined) вернет undefined , у меня сработало: let limit = Math.abs(req.query.limit) || 10; let page = (Math.abs(req.query.page) || 1) - 1; Schema.find().limit(limit).skip(limit * page)
Показать ещё 1 комментарий
50

Вы можете цеплять так:

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

Выполните запрос с помощью exec

query.exec(callback);
  • 0
    Спасибо за ваш ответ, как обратный вызов с результатом добавляется к этому?
  • 2
    execFind (функция (... например: 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 }); }); });
Показать ещё 1 комментарий
30

Вы можете использовать небольшой пакет под названием Mongoose Paginate, который облегчает его.

$ npm install mongoose-paginate

После того, как в ваших маршрутах или контроллере просто добавьте:

/**
 * 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);
  }
}
  • 1
    Это выглядит проще.
25

Лучше поздно, чем никогда.

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);
    })

В этом случае вы можете добавить запрос page и/или limit к вашему URL-адресу http. Пример ?page=0&limit=25

BTW Пагинация начинается с 0

  • 2
    Работает как гладкое желе благодаря @ CENT1PEDE
  • 1
    Мне нравится гладкое желе. Всегда пожалуйста :) @SureshPattu
Показать ещё 12 комментариев
14

Вот пример, который вы можете попробовать,

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
      });
  });
 });
9

Попробуйте использовать функцию мангуста для разбивки на страницы. Ограничение - это количество записей на странице и номер страницы.

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){
 });
8

Это то, что я сделал это в коде

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
    });

Вот как я это сделал.

  • 1
    Как получить общее количество страниц
  • 0
    Привет @Rhushikesh, Вы можете использовать функцию count (), чтобы получить счет. Но, похоже, нужен еще один запрос из базы данных. Подробности здесь mongoosejs.com/docs/api.html#model_Model.count
Показать ещё 1 комментарий
5

Вот версия, которую я прикрепляю ко всем моим моделям. Это зависит от подчеркивания для удобства и асинхронности производительности. Опционы позволяют выбирать и сортировать поля, используя синтаксис мангуста.

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;
}

Прикрепите его к вашей схеме модели.

MySchema.statics.findPaginated = findPaginated;
2

Это примерная функция для получения результата модели умений с опциями пагинации и ограничения

 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

}

2

Самый простой плагин для нумерации страниц.

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

Добавьте плагин в схему, а затем используйте метод 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
2
**//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);
            });

    });
}
2

Наилучший подход (ИМО) заключается в использовании пропусков и ограничений, но в ограниченных коллекциях или документах.

Чтобы сделать запрос в рамках ограниченных документов, мы можем использовать определенный индекс как индекс в поле типа DATE. См. Ниже

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

Самый простой и быстрый способ - разбивать на страницы с помощью objectId Пример

Условие начальной нагрузки

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

Возьмите первый и последний ObjectId из данных ответа

Страница следующего условия

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

Страница следующего условия

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

В мангусте

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);
});
1

Над ответом остается в силе.

Просто дополнение для тех, кто любит асинхронное ожидание, а не обещание!

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
        });
    }
};
0

Также удалось достичь результатов с помощью async/await.

Пример кода ниже с использованием асинхронного обработчика с hapi v17 и 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;
               }

            }
        }
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

Если вы используете мангуст в качестве источника для спокойного api, посмотрите 'restify-mongoose' и его запросы. Он имеет именно эту функциональность.

Любой запрос в коллекции предоставляет заголовки, которые здесь полезны.

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

Таким образом, вы получаете общий сервер с относительно линейным временем загрузки запросов к коллекциям. Это потрясающе и что-то, на что можно смотреть, если вы хотите перейти в собственную реализацию.

0

Вы можете использовать skip() и limit(), но это очень неэффективно. Лучшим решением будет сортировка по индексированному полю плюс limit(). Мы в Wunderflats опубликовали небольшую библиотеку здесь: https://github.com/wunderflats/goosepage Он использует первый способ.

0

Вы можете написать такой запрос.

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: номер страницы, поступающий от клиента в качестве параметров запроса.
per_page: нет результатов, показанных на странице

Если вы используете стек MEAN после сообщения в блоге, он предоставляет большую часть информации для создания разбивки на страницы в конце, используя angular -UI bootstrap и используя методы пропуска mongoose и ограничения в бэкэнд.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню