如何在Node.js中使用Mongoose进行分页?

Tho*_*mas 206 pagination mongoose mongodb node.js

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

Chr*_*kle 264

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

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

为了以可扩展的方式实现分页,将limit()与至少一个过滤标准结合起来,createdOn日期适合于许多目的.

MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
Run Code Online (Sandbox Code Playgroud)

  • 但是如何在不跳过的情况下从该查询中获取第二页?如果您每页查看10个结果,并且有100个结果,那么您如何定义偏移量或跳过值?你没有回答分页的问题,所以你不能'失望',尽管这是一个有效的谨慎.虽然同样的问题是在MySQL偏移,限制.在返回结果之前,必须将树遍历到偏移量.如果您的结果集小于1mil并且没有可保存的性能命中,请使用skip(). (90认同)
  • 这个答案包含有趣的观点,但它没有回答原来提出的问题. (13认同)
  • 当谈到mongoose/mongodb时,我是一个菜鸟,但是为了回答Lex的问题,它出现了 - 因为结果是由'`-createdOn`'排序的,你将用最少的'request.createdOnBefore`替换它的值在前一个结果集中返回的`createdOn`的值,然后重新查询. (10认同)
  • @JoeFrambach基于createdOn的请求似乎有问题.Skip是嵌入式的一个原因.文档仅警告通过btree索引循环的性能,所有DBMS都是这种情况.对于用户提出的问题"可比MySQL到LIMIT 50,100".skip是完全正确的. (9认同)
  • 虽然有趣,但这个答案的一个问题,正如@Lex评论所指出的那样,你只能通过结果跳过"前进"或"后退" - 你不能拥有可以跳到的"页面"(例如,第1页,第2页). ,第3页)没有进行多个*顺序*查询来确定从何处开始分页,我怀疑在大多数情况下比使用跳过更慢.当然,您可能不需要添加跳转到特定页面的功能. (7认同)
  • @SarathNair如果你使用排序会不会中断? (4认同)
  • 这个分页到底如何? (3认同)
  • 您如何解决用户查看记录31-40,然后另一个客户端插入新记录,然后用户点击下一个的问题。现在他们正在查看记录 40-49,并且已经查看了记录 40 两次。 (2认同)
  • @BrianThomas是的,我会在答案中加入.skip().它与MySQL LIMIT 50,100相当,这就是问题所要求的. (2认同)
  • 我个人经历过使用skip时mongoDB的糟糕表现.对于初始页面,它加载速度非常快,但在遍历页面时,您可能会遇到检索数据的缓慢情况.然后我们使用了与Chris提到的类似的方法.由于我们没有createdOn字段,因此我们在_id字段上应用了查询.由于_id已被索引,因此分页速度要快得多. (2认同)
  • 除非我们有某种独特的顺序ID,否则这种方法不起作用.考虑到上面的例子,可以有多个具有相同"createdOn"日期的记录.我们如何知道我们是否在当前页面中包含了最后一个'createdOn'的所有文档,或者其中一些文档正在等待处理?有人提到了非顺序寻呼的问题.除非有数百万份文件,否则我认为我们会更好地使用'.skip'.如果有数百万,我们无论如何都需要对结果进行一些过滤. (2认同)

Tho*_*mas 209

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

MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
Run Code Online (Sandbox Code Playgroud)

  • 不规模. (33认同)
  • 怎么样"数"?您需要知道有多少页面. (19认同)
  • @ChrisHinkle这似乎是所有DBMS的情况.Lex在相关答案下面的评论似乎解释得更多. (5认同)
  • Chris Hinkle解释为什么这不能扩展:http://stackoverflow.com/a/23640287/165330. (4认同)
  • @Avij是的.我已经使用了标识符.你在那里做的是将最后的记录id发送回服务器并获取一些id大于发送的记录.由于Id被编入索引,因此速度会快得多 (2认同)

Mad*_*han 94

使用猫鼬,快递和玉的分页 - 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
            })
        })
    })
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你发布你的答案!请务必仔细阅读[自我推广常见问题解答](http://stackoverflow.com/faq#promotion).另请注意,每次链接到您自己的网站/产品时,您都要*免费发布免责声明. (23认同)
  • 接收页面或限制的用户输入时要小心,其中之一可能是 ?limit=1e10000 ,它的计算结果为无穷大,这会导致 Mongoose 抛出 BadValue 错误 (3认同)
  • `.count` 现已弃用。请改用“countDocuments()”。 (2认同)

Rod*_*phe 55

你可以这样链:

var query = Model.find().sort('mykey', 1).skip(2).limit(5)
Run Code Online (Sandbox Code Playgroud)

使用执行查询 exec

query.exec(callback);
Run Code Online (Sandbox Code Playgroud)

  • 注意:这在__mongoose__中不起作用,但它可以在mongodb-native-driver中使用. (9认同)
  • execFind(function(...例如:`var page = req.param('p'); var per_page = 10; if(page == null){page = 0;} Location.count({},function(错误,计数){Location.find({}).skip(page*per_page).limit(per_page).execFind(function(err,locations){res.render('index',{locations:locations});} );}};` (2认同)

CEN*_*EDE 33

迟到总比不到好.

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);
    })
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您可以将查询page和/或添加limit到您的http网址.样品?page=0&limit=25

BTW 分页开始于0

  • 请添加`{page:parseInt(req.query.page)|| 0,...}到参数. (4认同)
  • 像果冻一样,感谢@ CENT1PEDE (2认同)
  • 我喜欢果冻。不客气:) @SureshPattu (2认同)
  • 这会导致猫鼬在应用条件之前找到每条记录吗? (2认同)

Clé*_*aud 32

你可以使用一个名为Mongoose Paginate的小包装,这样可以更容易.

$ npm install mongoose-paginate
Run Code Online (Sandbox Code Playgroud)

在您的路线或控制器之后,只需添加:

/**
 * 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是优化了吗? (6认同)

小智 22

询问:

search = productName
Run Code Online (Sandbox Code Playgroud)

参数:

page = 1 
Run Code Online (Sandbox Code Playgroud)
// 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);
        });
});
Run Code Online (Sandbox Code Playgroud)

  • 感谢您的回答;我在读完该线程后首先尝试了它,因为它是最近的线程之一。然而,当我实现它时,我发现了一个错误 - 正如现在所写的那样,它永远不会返回结果的第一页,因为它总是有一个跳过值。尝试在 Product.find() 调用之前添加“page = page-1”。 (3认同)
  • 感谢您的回答,但我自己也发现了一个错误,您的页面应该是 page = page - 1; 我发现@interog也发现了......所以请编辑你的答案,这样其他人就会很容易地接受你的答案。 (2认同)

Lib*_*hew 15

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

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
      });
  });
 });
Run Code Online (Sandbox Code Playgroud)


小智 11

尝试使用mongoose功能进行分页.限制是每页的记录数和页数.

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){
 });
Run Code Online (Sandbox Code Playgroud)


小智 9

这就是我在代码上所做的

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
    });
Run Code Online (Sandbox Code Playgroud)

这就是我做的方式.

  • 如何获得总页数 (2认同)

Was*_*siF 7

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 如果您不提供last_doc_id方法,您将获得 ie 5 个最新文档
  2. 如果您提供了,last_doc_id那么您将获得接下来的 5 个文件。


kbe*_*erg 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;
}
Run Code Online (Sandbox Code Playgroud)

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

MySchema.statics.findPaginated = findPaginated;
Run Code Online (Sandbox Code Playgroud)


Sab*_*esh 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
        });
    }
};
Run Code Online (Sandbox Code Playgroud)


Alv*_*van 6

有一些很好的答案给出了使用 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 => {

  })
Run Code Online (Sandbox Code Playgroud)


Apo*_*orv 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()
Run Code Online (Sandbox Code Playgroud)

该代码将在最新版本的 mongo 中运行


Dze*_* H. 5

实现此目的的可靠方法是使用查询字符串从前端传递值。假设我们想要获取第 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);
Run Code Online (Sandbox Code Playgroud)

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