290

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

4

32 回答 32

319

我对这个问题的公认答案感到非常失望。这不会扩展。如果您阅读 cursor.skip() 上的细则:

cursor.skip() 方法通常很昂贵,因为它要求服务器在开始返回结果之前从集合或索引的开头步行以获取偏移量或跳过位置。随着偏移量(例如上面的 pageNumber)增加,cursor.skip() 将变得更慢并且更占用 CPU。对于较大的集合,cursor.skip() 可能会成为 IO 绑定。

为了以可扩展的方式实现分页,将 limit() 与至少一个过滤条件结合起来,createdOn 日期适合多种用途。

MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
于 2014-05-13T19:48:13.993 回答
270

在使用 Rodolphe 提供的信息仔细查看 Mongoose API 后,我想出了这个解决方案:

MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
于 2011-04-04T15:15:29.713 回答
144

使用猫鼬、快递和玉进行分页 -这是我博客的链接,其中包含更多详细信息

var perPage = 10
  , page = Math.max(0, req.params.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
            })
        })
    })
于 2013-02-11T22:29:32.743 回答
63

你可以像这样链接:

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

使用执行查询exec

query.exec(callback);
于 2011-04-04T14:40:24.267 回答
47

在这种情况下,您可以将查询page和/或limit作为查询字符串添加到您的 URL。

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

由于它是 aString我们需要将其转换为 aNumber以进行计算。让我们使用该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

于 2016-06-07T02:19:13.600 回答
40

你可以使用一个叫做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);
  }
}
于 2012-10-10T05:47:10.113 回答
28

询问:

search = productName

参数:

page = 1 

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

    page = page - 1

    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);
        });
});
于 2020-04-21T23:09:31.803 回答
17

这是一个示例,你可以试试这个,

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
      });
  });
 });
于 2016-02-02T08:11:01.563 回答
13

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

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){
 });
于 2017-03-12T08:48:51.390 回答
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
    });

我就是这样做的。

于 2014-10-21T08:00:19.533 回答
7

有一些很好的答案给出了使用 skip() 和 limit() 的解决方案,但是,在某些情况下,我们还需要文档计数来生成分页。以下是我们在项目中所做的:

const PaginatePlugin = (schema, options) => {
  options = options || {}
  schema.query.paginate = async function(params) {
    const pagination = {
      limit: options.limit || 10,
      page: 1,
      count: 0
    }
    pagination.limit = parseInt(params.limit) || pagination.limit
    const page = parseInt(params.page)
    pagination.page = page > 0 ? page : pagination.page
    const offset = (pagination.page - 1) * pagination.limit

    const [data, count] = await Promise.all([
      this.limit(pagination.limit).skip(offset),
      this.model.countDocuments(this.getQuery())
    ]);
    pagination.count = count;
    return { data, pagination }
  }
}

mySchema.plugin(PaginatePlugin, { limit: DEFAULT_LIMIT })

// using async/await
const { data, pagination } = await MyModel.find(...)
  .populate(...)
  .sort(...)
  .paginate({ page: 1, limit: 10 })

// or using Promise
MyModel.find(...).paginate(req.query)
  .then(({ data, pagination }) => {

  })
  .catch(err => {

  })
于 2020-09-02T07:06:22.293 回答
6

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

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;
于 2013-02-14T15:50:15.997 回答
6

以上答案成立。

只是对于任何喜欢 async-await 而不是 promise 的人的附加组件!

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
        });
    }
};
于 2018-12-29T03:41:44.217 回答
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: 你得到的最后一个文档 id

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

  1. 如果您不提供该last_doc_id方法,您将获得 5 个最新文档
  2. 如果您提供了,last_doc_id那么您将获得下一个即 5 个文档。
于 2019-02-18T06:54:23.980 回答
5

您也可以使用以下代码行

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

于 2019-02-27T08:30:51.377 回答
4

实现这一点的可靠方法是使用查询字符串从前端传递值。假设我们想要获取第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);

我建议将此逻辑实现到中间件中,以便您可以将其用于各种路由/控制器。

于 2020-01-21T20:21:58.903 回答
4

您可以使用 mongoose-paginate-v2。更多信息请点击这里

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

const mySchema = new mongoose.Schema({
    // your schema code
}); 
mySchema.plugin(mongoosePaginate); 
const myModel = mongoose.model('SampleModel',  mySchema);

myModel.paginate().then({}) // Usage
于 2020-07-16T10:57:47.977 回答
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);
});
于 2016-07-08T11:08:57.173 回答
2

最好的方法 (IMO) 是在有限的集合或文档中使用跳过和限制 BUT。

为了在有限的文档中进行查询,我们可以使用特定的索引,例如 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)
于 2017-09-08T00:44:17.360 回答
2

最简单的分页插件。

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

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

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
于 2018-07-18T09:37:56.560 回答
2

我找到了一种非常有效的方式并自己实现了,我认为这种方式是最好的,原因如下:

  • 它不使用跳过;
  • 它使用 ID 来查询文档。Ids 在 MongoDB 中默认被索引,使得它们的查询速度非常快;
  • 它使用精益查询,众所周知,这些查询非常高效,因为它们从 Mongoose 中删除了很多“魔法”,并返回了一个来自 MongoDB 的“原始”文档;
  • 它不依赖于任何可能包含漏洞或具有易受攻击依赖项的第三方包。

唯一需要注意的是 Mongoose 的一些方法,比如.save()不能很好地与精益查询一起工作,这些方法在这个很棒的博客文章中列出,我真的推荐这个系列,因为它考虑了很多方面,比如类型安全(防止严重错误)和 PUT/PATCH。

我将提供一些上下文,这是一个 Pokémon 存储库,分页工作如下: API 从req.bodyExpress 对象接收 unsafeId,我们需要将其转换为字符串以防止 NoSQL 注入(它可能是一个带有邪恶的对象过滤器),这个 unsafeId 可以是一个空字符串,也可以是上一页最后一项的 ID,它是这样的:

 /**
   * @description GET All with pagination, will return 200 in success
   * and receives the last ID of the previous page or undefined for the first page
   * Note: You should take care, read and consider about Off-By-One error
   * @param {string|undefined|unknown} unsafeId - An entire page that comes after this ID will be returned
   */
  async readPages(unsafeId) {
    try {
      const id = String(unsafeId || '');
      let criteria;
      if (id) {
        criteria = {_id: {$gt: id}};
      } // else criteria is undefined

      // This query looks a bit redundant on `lean`, I just really wanted to make sure it is lean
      const pokemon = await PokemonSchema.find(
          criteria || {},
      ).setOptions({lean: true}).limit(15).lean();

      // This would throw on an empty page
      // if (pokemon.length < 1) {
      //  throw new PokemonNotFound();
      // }

      return pokemon;
    } catch (error) {
      // In this implementation, any error that is not defined by us
      // will not return on the API to prevent information disclosure.
      // our errors have this property, that indicate
      // that no sensitive information is contained within this object
      if (error.returnErrorResponse) {
        throw error;
      } // else
      console.error(error.message);
      throw new InternalServerError();
    }
  }

现在,为了使用它并避免前端出现 Off-By-One 错误,您可以按照以下方式进行操作,考虑到这pokemons是从 API 返回的 Pokémons 文档数组:

// Page zero
const pokemons = await fetchWithPagination({'page': undefined});
// Page one
// You can also use a fixed number of pages instead of `pokemons.length`
// But `pokemon.length` is more reliable (and a bit slower)
// You will have trouble with the last page if you use it with a constant
// predefined number 
const id = pokemons[pokemons.length - 1]._id;

if (!id) {
    throw new Error('Last element from page zero has no ID');
} // else

const page2 = await fetchWithPagination({'page': id});

作为此处的注释,Mongoose ID 始终是连续的,这意味着任何较新的 ID 将始终大于旧 ID,这是此答案的基础。

此方法已针对 Off-By-One 错误进行了测试,例如,页面的最后一个元素可以作为下一个页面的第一个元素(重复)返回,或者在前一页的最后一个元素和当前页面的第一页可能会消失。

当您完成所有页面并在最后一个元素(不存在的元素)之后请求一个页面时,响应将是一个带有 200(OK)的空数组,这太棒了!

于 2021-08-08T20:25:05.037 回答
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

}

于 2018-09-19T12:33:36.367 回答
1

使用 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)
于 2020-03-29T03:21:04.977 回答
1
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.
于 2020-03-30T17:13:52.597 回答
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 引导程序在前端创建分页并在后端使用猫鼬跳过和限制方法的大量信息。

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

于 2015-06-06T17:07:58.547 回答
0

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

于 2015-12-05T19:27:21.790 回答
0

如果您使用 mongoose 作为 restful 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

所以基本上你会得到一个通用服务器,它具有相对线性的加载时间来查询集合。如果您想进入自己的实现,那太棒了,值得一看。

于 2016-03-08T10:53:44.447 回答
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});
            }
        });
});
于 2017-07-14T16:16:33.350 回答
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);
            });

    });
}
于 2018-07-06T09:07:03.057 回答
0

使用这个简单的插件。

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

安装

npm install mongoose-paginate-v2
用法将插件添加到模式,然后使用模型分页方法:

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

于 2019-09-18T15:49:48.457 回答
0

MongoDB 官方博客有一个关于分页的条目,他们在那里解释了为什么“跳过”可能很慢并提供了替代方案: https: //www.mongodb.com/blog/post/paging-with-the-bucket-pattern--第1部分

于 2021-06-28T21:43:10.910 回答
-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;
               }

            }
        }
于 2019-01-18T14:44:31.143 回答