我正在用Node.js和mongoose写一个web应用程序。如何对我从.find()调用得到的结果进行分页?我想要一个功能可比的“限制50,100”在SQL。
当前回答
使用猫鼬,快递和翡翠的分页-这里有一个链接到我的博客与更多的细节
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
})
})
})
其他回答
实现这一点的可靠方法是使用查询字符串从前端传递值。假设我们想要获得第2页,并将输出限制为25个结果。 page=2&limit=25 //这将被添加到您的URL: http:localhost:5000?= 2限制= 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);
我建议将这个逻辑实现到中间件中,这样你就可以将它用于各种路由/控制器。
使用这个简单的插件。
https://github.com/WebGangster/mongoose-paginate-v2
安装
NPM安装mongoose- pagate -v2 使用 添加插件到一个模式,然后使用模型paginate方法:
Const mongoose = require('mongoose'); const mongoosePaginate = require(' mongoosePaginate -v2'); const mySchema =新的猫鼬。模式({ /*你的模式定义*/ }); mySchema.plugin (mongoosePaginate); const myModel =猫鼬。模型(SampleModel, mySchema); myModel.paginate().then({}) //使用方法
MongoDB官方博客有一个关于分页的条目,在那里他们解释了为什么“跳过”可能会很慢,并提供了替代方案:https://www.mongodb.com/blog/post/paging-with-the-bucket-pattern--part-1
我发现了一种非常有效的方法并亲自实施,我认为这种方法是最好的,原因如下:
它不使用跳过,这使得时间复杂度不能很好地扩展; 它使用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)的空数组,这太棒了!
您也可以使用下面的代码行
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中工作
推荐文章
- elasticsearch vs . MongoDB用于过滤应用程序
- ReferenceError: description没有定义NodeJs
- MongoDB记录所有查询
- 将一个二进制的NodeJS Buffer转换为JavaScript的ArrayBuffer
- 使用LIMIT/OFFSET运行查询,还可以获得总行数
- AngularJS只适用于单页应用程序吗?
- 如何在vue-cli项目中更改端口号
- MongoDB:如何找到安装的MongoDB的确切版本
- 同步和异步编程(在node.js中)的区别是什么?
- 如何编辑通过npm安装的节点模块?
- 如何使用mongoimport导入CSV文件?
- “node_modules”文件夹应该包含在git存储库中吗
- 使用package.json在全局和本地安装依赖项
- 在mongodb中存储日期/时间的最佳方法
- this.libOptions.parse不是一个函数