如何在使用官方mongodb客户端的node.js中实现mongodb分页?

3
4个回答

10
“基于偏移量的”方法有一个很大的缺陷:如果在调用API之间结果列表发生了变化,索引会发生偏移,导致某个项目要么返回两次,要么被跳过而永远不会返回。
这个问题在以下链接中得到了说明: https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/
“基于时间”的分页方法会更好一些,因为结果不再被跳过。如果你查询第一页,然后删除了一个新项目,它不会使你的第二页结果发生偏移,一切都很好。然而,这种方法有一个主要缺陷:如果有多个同时创建的项目怎么办?
最好使用“基于游标的分页”。 可以使用集合中任何具有“唯一、可排序和不可变”属性的字段来实现。
_id 满足所有“唯一、可排序和不可变”的条件。基于这个字段,我们可以对结果进行排序并返回页面结果,将上一个文档的 _id 作为后续请求的游标。
curl https://api.mixmax.com/items?limit=2
const items = db.items.find({}).sort({
   _id: -1
}).limit(2);

const next = items[items.length - 1]._id
res.json({ items, next })

当用户想要获取第二页时,他们通过 URL 上的游标(作为 next 参数)进行传递: curl https://api.mixmax.com/items?limit=2&next=590e9abd4abbf1165862d342
const items = db.items.find({
  _id: { $lt: req.query.next }
}).sort({
   _id: -1
}).limit(2);

const next = items[items.length - 1]._id
res.json({ items, next })

如果我们想按不同的顺序返回结果,比如按照项目的日期,则需要在查询字符串中添加sort=launchDatecurl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
   launchDate: -1
}).limit(2);

const next = items[items.length - 1].launchDate;
res.json({ items, next })

对于后续页面请求
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z

(注:该文本为一段代码,无法进行直接翻译。)
const items = db.items.find({
  launchDate: { $lt: req.query.next }
}).sort({
   _id: -1
}).limit(2);

const next = items[items.length - 1].launchDate;
res.json({ items, next });

如果我们在同一天和时间上发布了一堆物品?现在我们的launchDate字段不再是唯一的,也不满足唯一、可排序和不变的条件。我们不能将其用作游标字段。但是,我们可以使用两个字段生成游标。由于我们知道MongoDB中的_id字段始终满足上述三个条件,因此我们知道如果我们将其与我们的launchDate字段一起使用,这两个字段的组合将满足要求,并且可以一起用作游标字段。 curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
   launchDate: -1,
  _id: -1 // secondary sort in case there are duplicate launchDate values
}).limit(2);

const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });

对于后续页面请求
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z_590e9abd4abbf1165862d342
const [nextLaunchDate, nextId] = req.query.next.split(‘_’);
const items = db.items.find({
  $or: [{
    launchDate: { $lt: nextLaunchDate }
  }, {
    // If the launchDate is an exact match, we need a tiebreaker, so we use the _id field from the cursor.
    launchDate: nextLaunchDate,
  _id: { $lt: nextId }
  }]
}).sort({
   _id: -1
}).limit(2);

const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });

参考文献: https://engineering.mixmax.com/blog/api-paging-built-the-right-way/

这也被称为键集分页,可以在此处阅读更多信息:https://use-the-index-luke.com/no-offset 它通常更高效和一致,尽管应该说,它有一些偏移分页方法没有的限制,其中一个重要的限制是,您无法导航到任意页面。 - Miguel Santos

5
使用推荐的分页方法,结合limit()和skip()函数(见此处):
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect('http:localhost:27017').then((client) => {
    const db = client.db(mongo.db);
    db.collection('my-collection').find({}, {limit:10, skip:0}).then((documents) => {
        //First 10 documents
        console.log(documents);
    });


    db.collection('my-collection').find({}, {limit:10, skip:10}).then((documents) => {
        //Documents 11 to 20
        console.log(documents);
    });
});

这是一个分页函数:

function studentsPerPage (pageNumber, nPerPage) {
    return db.collection('students').find({}, 
        {
            limit: nPerPage, 
            skip: pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0
        });
}

3
在Mongo的文档中,这似乎是推荐的方法,有点疯狂!在几乎任何数据库中,如果偏移量很大,跳过可能会有些危险,因为它需要扫描所有文档,直到找到您要查找的文档(值得赞扬的是,文档确实提到了这一点)。我认为更好的模式可能是按_id字段排序,并传递要获取之前的文档的_id-- {_id:{$lte:previousId}} - klhr
“跳过”方法虽然简单明了,但如果大多数人不会浏览大量文档,那么可能不值得担心。 - klhr

0
我正在发送一个在MongoDb和Nodejs上的API。
module.exports.fetchLoans = function(req, res, next) {
    var perPage = 5;
    var page = req.body.page || 1;
    loans
      .find({ userId: req.user._id})
      .select("-emi")
      .skip(perPage * page - perPage)
      .limit(perPage)
      .sort({ timestamp: -1 })
      .exec(function(err, loan) {
        if (loan != null) {
          loans
            .find({ userId: req.user._id})
            .count()
            .exec(function(err, count) {
              if (count != null) {
                res.json({
                  success: true,
                  loans: loan,
                  currentpage: page,
                  totalpages: Math.ceil(count / perPage)
                });
              } else {
                console.log("Milestone Error: ", err);
                res.json({ success: false, error: "Internal Server Error. Please try again." });
              }
            });
        } else {
          console.log("Milestone Error: ", err);
          res.json({ success: false, error: "Internal Server Error. Please try again." });
        }
      });
  };

在这段代码中,您需要在每次访问时提供页面编号。

0

您可以使用skiplimit选项来实现分页。

module.exports = (data)=>{

  let page = parseInt(data.page);
  let limit = parseInt(data.limit);
  let skip = 0

  if(page>1){
   skip = (page * limit);
   }


let mongoClient = require('mongodb').MongoClient;
    mongoClient.connect('mongodb://localhost:27017').then((client) => {
        let db = client.db('your-db');
        db.collection('your-collection').find({}, {limit:limit, skip:skip}).then((documents) => {
            console.log(documents);
        });

    });
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接