关于mongodb:如何在Node.js中对Mongoose进行分页?

How to paginate with Mongoose in Node.js?

我正在用Node.js和猫鼬编写一个Webapp。 如何对从.find()调用获得的结果进行分页? 我想要一个与SQL中的"LIMIT 50,100"相当的功能。


我对这个问题接受的答案感到非常失望。这不会扩展。如果您在cursor.skip()上阅读了精美的印刷品,则:

The cursor.skip() method is often expensive because it requires the server to walk from the beginning of the collection or index to get the offset or skip position before beginning to return result. As offset (e.g. pageNumber above) increases, cursor.skip() will become slower and more CPU intensive. With larger collections, cursor.skip() may become IO bound.

为了以可缩放的方式实现分页,将limit()与至少一个过滤条件组合在一起,createdOn日期适合许多目的。

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


通过Rodolphe提供的信息仔细研究了Mongoose API之后,我找到了以下解决方案:

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


使用猫鼬,快递和玉石进行分页-这是我的博客链接,详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
            })
        })
    })


您可以像这样链接:

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

使用exec执行查询

1
query.exec(callback);


迟到总比不到好。

1
2
3
4
5
6
7
8
9
10
11
12
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添加到您的http网址。样本?page=0&limit=25

BTW
分页从0开始


您可以使用一个名为Mongoose Paginate的小程序包,该程序包更加轻松。

1
$ npm install mongoose-paginate

在路由或控制器中之后,只需添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
      });
  });
 });

尝试使用猫鼬功能进行分页。限制是每页的记录数和页数。

1
2
3
4
5
6
7
8
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){
 });

这就是我在代码上所做的

1
2
3
4
5
6
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
    });

这就是我做到的方式。


这是我附加到所有模型的版本。为了方便起见,它取决于下划线,对于性能,它取决于异步。 opts允许使用猫鼬语法进行字段选择和排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
}

将其附加到您的模型架构。

1
MySchema.statics.findPaginated = findPaginated;

您也可以使用以下代码行

1
2
3
4
5
6
7
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()

该代码将在最新版本的mongo中工作


上面的答案很好。

Just an add-on for anyone who is into async-await rather than
promise !!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
        });
    }
};

最简单,最快捷的方法是使用objectId进行分页
例;

初始负载条件

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

从响应数据中获取第一个和最后一个ObjectId

页面下一个条件

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

页面下一个条件

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

在猫鼬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
});

最佳方法(IMO)是在有限的馆藏或文档中使用跳过和限制BUT。

为了在有限的文档中进行查询,我们可以在DATE类型字段上使用特定的索引,例如index。看到下面

1
2
3
4
5
6
7
8
9
10
11
12
13
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)


最简单的分页插件。

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

将插件添加到架构,然后使用模型分页方法:

1
2
3
4
5
6
7
8
9
10
11
12
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


这是获取带有分页和限制选项的技能模型结果的示例函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 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

}


简单而强大的分页解决方案

last_doc_id:您获得的最后一个文档ID

no_of_docs_required:您要提取的文档数,即5、10、50等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}
  • 如果您不向该方法提供最后的文档ID,则会获得5个最新文档
  • 如果您提供了最后一个文档ID,则将获得接下来的5个文档。

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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});
                }
            });
    });

    您可以使用skip()和limit(),但是效率很低。更好的解决方案是对索引字段加limit()进行排序。
    Wunderflats的我们在这里发布了一个小库:https://github.com/wunderflats/goosepage
    它使用第一种方式。


    您可以这样编写查询。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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引导程序创建分页,并在后端使用猫鼬跳过和限制方法。

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


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    **//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);
                });

        });
    }

    如果您使用猫鼬作为静态API的来源,请查看
    'restify-mongoose'及其查询。它完全内置了此功能。

    集合上的任何查询均会提供有助于此处的标头

    1
    2
    3
    4
    5
    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

    因此,基本上,您将获得具有相对线性加载时间的通用服务器,以查询集合。如果您想进入自己的实现中,那真是太棒了。


    使用这个简单的插件。

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

    安装

    1
    npm install mongoose-paginate-v2

    用法
    将插件添加到架构,然后使用模型分页方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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


    能够通过async / await达到结果。

    下面的代码示例使用带有hapi v17和mongoose v5的异步处理程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    {
                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;
                   }

                }
            }