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


232

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


Используйте пропустить и ограничить свойство при поиске данных из коллекции.
Арун Сахани

Эта ссылка может быть использована для вас в деталях. laxmanchavda.blogspot.com/2018/06/…
laxman

Ответы:


278

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

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

Чтобы добиться масштабируемости нумерации страниц, объедините limit () и хотя бы один критерий фильтра. Дата createOn подходит для многих целей.

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

105
Но как бы вы получили вторую страницу из этого запроса без пропуска? Если вы просматриваете 10 результатов на странице, а результатов 100, как тогда вы определяете значение смещения или пропуска? Вы не отвечаете на вопрос о нумерации страниц, поэтому не можете быть «разочарованы», хотя это и есть серьезное предупреждение. Хотя та же проблема в смещении MySQL, предел. Он должен пройти по дереву до смещения, прежде чем возвращать результаты. Я бы взял это с недолгой солью, если ваши результирующие наборы меньше 1 мили и нет никакого заметного снижения производительности, используйте skip ().
Lex

13
Я нуб, когда дело доходит до mongoose / mongodb, но чтобы ответить на вопрос Лекса, кажется, что, поскольку результаты упорядочены по ' -createdOn', вы бы заменили значение request.createdOnBeforeна наименьшее значение, createdOnвозвращенное в предыдущем наборе результатов, а затем запрос.
Терри Льюис

9
@JoeFrambach Запрос, основанный на selectedOn, кажется проблематичным. Пропустить был встроен по причине. Документы только предупреждают о снижении производительности при циклическом переходе по индексу btree, что имеет место для всех СУБД. Для пользователей вопрос "что-то сопоставимое MySQL с LIMIT 50,100" .skip является совершенно правильным.
Lex

8
Хотя это интересно, проблема с этим ответом, как отмечает комментарий @Lex, заключается в том, что вы можете пропустить только «вперед» или «назад» в результатах - у вас не может быть «страниц», к которым вы можете перейти (например, Страница 1, Страница 2). , Страница 3) без выполнения нескольких последовательных запросов, чтобы определить, с чего начинать нумерацию страниц, что, как я подозреваю, в большинстве случаев будет медленнее, чем просто использование пропуска. Конечно, вам может не понадобиться добавлять возможность пропуска на определенные страницы.
Иэн Коллинз

20
Этот ответ содержит интересные моменты, но он не отвечает на первоначальный вопрос.
steampowered

228

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

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

21
А как насчет "считать"? Вам нужно это знать, сколько страниц есть.
Алексей Саатчи

36
Не масштабируется.
Крис Хинкль

4
Объяснение Криса Хинкля, почему это не масштабируется: stackoverflow.com/a/23640287/165330 .
IMME

7
@ChrisHinkle Это похоже на все СУБД. Комментарий Лекса ниже связанного ответа, кажется, объясняет больше.
csotiriou

2
@ Авид да. Я использовал идентификатор для этого. что вы там делаете - отправляете последний идентификатор записи обратно на сервер и получаете некоторые записи с идентификатором больше отправленного. Как Id индексируется так, это будет намного быстрее
Джордж Бэйли

108

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

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
Спасибо за публикацию вашего ответа! Пожалуйста, внимательно прочитайте FAQ по саморекламе . Также обратите внимание, что необходимо размещать заявление об отказе от ответственности при каждой ссылке на ваш собственный сайт / продукт.
Эндрю Барбер

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)
Monfa.red

55

Вы можете цепочкой просто так:

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

Выполните запрос, используя exec

query.exec(callback);

Спасибо за ваш ответ, как обратный звонок с результатом добавляется к этому?
Томас

2
execFind (function (... например: 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
примечание: это не будет работать в mongoose , но будет работать в mongodb-native-driver.
Джесси

39

В этом случае вы можете добавить запрос pageи / или limitваш URL в виде строки запроса.

Например:
?page=0&limit=25 // this would be added onto your URL: http:localhost:5000?page=0&limit=25

Поскольку это было бы Stringнам, нам нужно преобразовать его в a Numberдля наших расчетов. Давайте сделаем это, используя parseIntметод, и давайте также предоставим некоторые значения по умолчанию.

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

Кстати, нумерация страниц начинается с0


5
пожалуйста, добавьте `{page: parseInt (req.query.page) || 0, ...} к параметру.
imalik8088

@ imalik8088 Спасибо, однако строковые параметры AFAIK обрабатываются автоматически mongoose .
CENT1PEDE

1
Ожидал поведения, но в моем случае оно не могло
скрыться

@ imalik8088 Это странно. Может быть, если бы вы могли показать ошибку воспроизведения, я могу отредактировать свой ответ. Спасибо.
CENT1PEDE

2
Приведет ли это к тому, что мангуста найдет все записи перед применением условий?
FluffyBeing

37

Вы можете использовать небольшую упаковку под названием 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);
  }
}

2
Это оптимизировано?
Ардженто

16

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

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

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

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

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

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
Как получить общее количество страниц
Rhushikesh

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

@Rhushikesh получить счет и разделить его по пределу
edthethird

count()устарела. использованиеcountDocuments()
Руслан

7

Запрос;
search = productName,

Params;
страница = 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);
    });
});

Спасибо за этот ответ; Я попробовал это сначала после прочтения темы, потому что это был один из последних. Однако, когда я реализовал это, я обнаружил ошибку - как она написана сейчас, она никогда не вернет первую страницу результатов, так как ВСЕГДА будет иметь значение пропуска. Попробуйте добавить «page = page-1» перед вызовом Product.find ().
Interog

6

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

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;

6

Простое и мощное решение для нумерации страниц

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: последний идентификатор документа, который вы получаете

no_of_docs_required: количество документов, которые вы хотите получить, т.е. 5, 10, 50 и т. д.

  1. Если вы не предоставите last_doc_idметод, то получите 5 последних документов
  2. Если вы предоставили, last_doc_idвы получите следующие 5 документов.

5

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

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

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

Вы также можете использовать следующую строку кода

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()

этот код будет работать в последней версии Монго


3

Надежный подход для реализации этого заключается в передаче значений из внешнего интерфейса с помощью строки запроса . Допустим, мы хотим получить страницу № 2, а также ограничить вывод 25 результатами .
Строка запроса будет выглядеть так:?page=2&limit=25 // this would be added onto your URL: http:localhost:5000?page=2&limit=25

Давайте посмотрим код:

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

Я бы предложил реализовать эту логику в промежуточном программном обеспечении, чтобы вы могли использовать ее для различных маршрутов / контроллеров.


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

2

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

Чтобы сделать запрос в ограниченных документах, мы можем использовать определенный индекс, такой как индекс в поле типа 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

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

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

этот плагин сломан с мангуст v5.5.5
Исаак Пак

1

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

 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

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

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

страница: номер страницы, поступающей от клиента в качестве параметров запроса.
per_page: на странице нет результатов

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

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


0

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


0

Если вы используете mongoose в качестве источника для успокоительного 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
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

Используйте этот простой плагин.

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

Монтаж

npm install mongoose-paginate-v2
Использование Добавьте плагин в схему, а затем используйте метод paginate модели:

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


Этот плагин был «предложен» уже в другом ответе. Также было бы полезно узнать, что вы являетесь соавтором этого пакета.
lukas_o

@lukas_o Да. Я создатель плагина.
Аравиндом NC

0

в соответствии с

Крис Хинкль

ответ:

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

Использование ts-mongoose-pagination

    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

Также удалось достичь результатов с помощью 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;
               }

            }
        }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.