我正在用Node.js和mongoose写一个web应用程序。如何对我从.find()调用得到的结果进行分页?我想要一个功能可比的“限制50,100”在SQL。


当前回答

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.

其他回答

您可以像这样编写查询。

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/

我发现了一种非常有效的方法并亲自实施,我认为这种方法是最好的,原因如下:

它不使用跳过,这使得时间复杂度不能很好地扩展; 它使用id来查询文档。在MongoDB中,id默认情况下是索引的,这使得查询它们非常快; 它使用精益查询,这些被认为是非常具有执行力的,因为他们从Mongoose中删除了很多“魔法”,并返回一个来自MongoDB的“原始”文档; 它不依赖于任何可能包含漏洞或具有易受攻击依赖项的第三方包。

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

我将提供一些上下文,这是一个Pokémon存储库,分页工作如下:API从req接收unsafeId。Express的body对象,我们需要将其转换为字符串以防止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 - 1错误,你可以像下面这样做,考虑到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)的空数组,这太棒了!

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

  })

在这种情况下,可以将查询页面和/或限制作为查询字符串添加到URL。

例如: ?page=0&limit=25 //这将被添加到您的URL: http:localhost:5000?= 0限制= 25页

因为它是一个字符串,我们需要将它转换为一个数字来进行计算。让我们使用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开始

您也可以使用下面的代码行

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中工作