我对使用MongoDB优化“分页”解决方案很感兴趣。我的问题很简单。通常,我使用limit()
功能限制返回的文档数量。这迫使我发出一个没有limit()
函数的多余查询,以便我也可以捕获查询中文档的总数,以便我可以将其传递给客户端,让他们知道他们需要发出额外的请求来检索其余文档。
是否有一种方法可以将此压缩为1个查询?同时获取文档的总数,但只检索使用limit()
的子集?是否有一种不同于我处理该问题的方式?
我对使用MongoDB优化“分页”解决方案很感兴趣。我的问题很简单。通常,我使用limit()
功能限制返回的文档数量。这迫使我发出一个没有limit()
函数的多余查询,以便我也可以捕获查询中文档的总数,以便我可以将其传递给客户端,让他们知道他们需要发出额外的请求来检索其余文档。
是否有一种方法可以将此压缩为1个查询?同时获取文档的总数,但只检索使用limit()
的子集?是否有一种不同于我处理该问题的方式?
Mongodb 3.4 推出了聚合操作符 $facet
它可以在同一组输入文档上,在单个阶段中处理多个聚合管道。
使用 $facet
和 $group
操作符,您可以通过$limit
找到文档并获取总数。
您可以在 mongodb 3.4 中使用以下聚合操作:
db.collection.aggregate([
{ "$facet": {
"totalData": [
{ "$match": { }},
{ "$skip": 10 },
{ "$limit": 10 }
],
"totalCount": [
{ "$group": {
"_id": null,
"count": { "$sum": 1 }
}}
]
}}
])
即使您可以在MongoDB 3.6中使用已经介绍的$count
聚合函数。
您可以在MongoDB 3.6 中使用以下聚合函数。
db.collection.aggregate([
{ "$facet": {
"totalData": [
{ "$match": { }},
{ "$skip": 10 },
{ "$limit": 10 }
],
"totalCount": [
{ "$count": "count" }
]
}}
])
不,没有其他方法。需要两次查询 - 一次查询总数,一次带有限制。否则您需要使用不同的数据库,例如Apache Solr,它可以按照您的需求正常工作。在Solr中,每个查询都有限制且返回totalCount。
MongoDB允许您在使用limit()
或skip()
时仍然使用cursor.count()
。
假设您有一个包含10个项目的db.collection
:
您可以执行以下操作:
async function getQuery() {
let query = await db.collection.find({}).skip(5).limit(5); // returns last 5 items in db
let countTotal = await query.count() // returns 10-- will not take `skip` or `limit` into consideration
let countWithConstraints = await query.count(true) // returns 5 -- will take into consideration `skip` and `limit`
return { query, countTotal }
}
.skip(5).limit(5)
不会返回数据库中的最后5个项目,而是返回第二组5个项目。count()
将始终返回10,无论有多少项目,只要至少有10个项目。 - Walter Tross以下是使用 MongoDB 3.4+(配合 Mongoose)和 $facets
完成此操作的步骤。该示例基于匹配后的文档返回 $count
。
const facetedPipeline = [{
"$match": { "dateCreated": { $gte: new Date('2021-01-01') } },
"$project": { 'exclude.some.field': 0 },
},
{
"$facet": {
"data": [
{ "$skip": 10 },
{ "$limit": 10 }
],
"pagination": [
{ "$count": "total" }
]
}
}
];
const results = await Model.aggregate(facetedPipeline);
这种模式对于从 REST API 返回分页信息很有用。
时代变了,我相信您可以通过使用聚合、$sort
、$group
和$project
来实现OP所要求的内容。对于我的系统,我还需要从我的users
集合中获取一些用户信息。希望这也能回答任何关于那方面的问题。下面是一个聚合管道。最后三个对象(sort、group和project)负责获取总计数,然后提供分页功能。
db.posts.aggregate([
{ $match: { public: true },
{ $lookup: {
from: 'users',
localField: 'userId',
foreignField: 'userId',
as: 'userInfo'
} },
{ $project: {
postId: 1,
title: 1,
description: 1
updated: 1,
userInfo: {
$let: {
vars: {
firstUser: {
$arrayElemAt: ['$userInfo', 0]
}
},
in: {
username: '$$firstUser.username'
}
}
}
} },
{ $sort: { updated: -1 } },
{ $group: {
_id: null,
postCount: { $sum: 1 },
posts: {
$push: '$$ROOT'
}
} },
{ $project: {
_id: 0,
postCount: 1,
posts: {
$slice: [
'$posts',
currentPage ? (currentPage - 1) * RESULTS_PER_PAGE : 0,
RESULTS_PER_PAGE
]
}
} }
])
Mongodb 3.4中有一种方法:$facet
您可以使用它来实现以下操作:
db.collection.aggregate([
{
$facet: {
data: [{ $match: {} }],
total: { $count: 'total' }
}
}
])
那么您将能够同时运行两个聚合操作。
默认情况下,count()方法会忽略游标的skip()和limit()效果。(参考MongoDB文档)
由于count方法排除了limit和skip的影响,您可以使用cursor.count()
来获取总数。
const cursor = await database.collection(collectionName).find(query).skip(offset).limit(limit)
return {
data: await cursor.toArray(),
count: await cursor.count() // this will give count of all the documents before .skip() and limit()
};
MongoDB引入了一种新的方法,仅获取与给定查询匹配的文档计数,具体如下:
const result = await db.collection('foo').count({name: 'bar'});
console.log('result:', result) // prints the matching doc count
分页使用的配方:
const query = {name: 'bar'};
const skip = (pageNo - 1) * pageSize; // assuming pageNo starts from 1
const limit = pageSize;
const [listResult, countResult] = await Promise.all([
db.collection('foo')
.find(query)
.skip(skip)
.limit(limit),
db.collection('foo').count(query)
])
return {
totalCount: countResult,
list: listResult
}
了解更多关于db.collection.count的详细信息,请访问此页面
在使用聚合进行分页时,建议提供一个警告。如果API经常被用户用于获取数据,则最好使用两个查询。当更多的用户在线访问系统时,这比在生产服务器上使用聚合获取数据至少快50倍。聚合和$facet更适合仪表板、报告和调度作业等不经常调用的场景。