我有兴趣优化我正在使用MongoDB的“分页”解决方案。我的问题很直接。我通常使用limit()
功能限制返回的文档数量。这迫使我发出一个没有limit()
函数的冗余查询,以便我也可以捕获查询中的文档总数,这样我就可以将其传递给客户端,让他们知道他们必须发出一个其他请求以检索其余文件。
有没有办法将此缩减为1个查询?获取文档总数但同时仅使用limit()
检索子集?是否有不同的方式来思考这个问题而不是我接近它?
答案 0 :(得分:15)
否,没有别的办法。两个查询 - 一个用于计数 - 一个用于限制。或者您必须使用其他数据库。例如Apache Solr就像您想要的那样工作。每个查询都有限和返回totalCount。
答案 1 :(得分:9)
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" }
]
}}
])
答案 2 :(得分:6)
Mongodb 3.4中有一种方法:$ facet
你可以做到
db.collection.aggregate([
{
$facet: {
data: [{ $match: {} }],
total: { $count: 'total' }
}
}
])
那么你将能够同时运行两个聚合
答案 3 :(得分:5)
时代已经发生变化,我相信通过使用$sort
,$group
和$project
汇总,您可以实现OP的要求。对于我的系统,我还需要从我的users
集合中获取一些用户信息。希望这也可以回答任何问题。下面是一个聚合管道。最后三个对象(排序,组和项目)处理总计数,然后提供分页功能。
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
]
}
} }
])
答案 4 :(得分:4)
以下是使用 $facets
在 MongoDB 3.4+(使用 Mongoose)中执行此操作的方法。此示例根据文档在匹配后返回一个 $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 返回的分页信息很有用。
答案 5 :(得分:3)
这一切都取决于您是否需要进行两次查询所需的分页经验。
您是否需要列出每一页甚至一系列页面?有没有人甚至去第1051页 - 概念上这究竟是什么意思?
在分页模式上有很多用户体验 - Avoid the pains of pagination涵盖各种类型的分页及其场景,许多不需要计数查询来知道是否下一页。例如,如果您在页面上显示10个项目并且限制为13 - 您将知道是否还有另一个页面..
答案 6 :(得分:2)
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的更多详细信息,请访问this page
答案 7 :(得分:1)
即使您通过cursor.count()
或limit()
,MongoDB也可以使用skip()
。
假设你有一个db.collection
有10个项目。
你可以这样做:
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 }
}
答案 8 :(得分:0)
您可以在一个查询中执行此操作。首先运行一个计数,并在该运行中运行limit()函数。
在Node.js和Express.js中,你必须像这样使用它才能使用“count”函数和toArray的“结果”。
var curFind = db.collection('tasks').find({query});
然后你可以像这样运行两个函数(一个嵌套在另一个函数中)
curFind.count(function (e, count) {
// Use count here
curFind.skip(0).limit(10).toArray(function(err, result) {
// Use result here and count here
});
});
答案 9 :(得分:0)
可以获得总结果大小而不会limit()
使用count()
的效果,如下所示:
Limiting results in MongoDB but still getting the full count?
根据文档,您甚至可以控制在调用count()
时是否考虑限制/分页:
https://docs.mongodb.com/manual/reference/method/cursor.count/#cursor.count
编辑:与其他地方的内容形成对比 - 文档明确指出"操作不执行查询,而是计算查询返回的结果" 。根据我的理解,这意味着只执行一个查询。
示例:
> db.createCollection("test")
{ "ok" : 1 }
> db.test.insert([{name: "first"}, {name: "second"}, {name: "third"},
{name: "forth"}, {name: "fifth"}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 5,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.test.find()
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c8"), "name" : "forth" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c9"), "name" : "fifth" }
> db.test.count()
5
> var result = db.test.find().limit(3)
> result
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }
> result.count()
5 (total result size of the query without limit)
> result.count(1)
3 (result size with limit(3) taken into account)
答案 10 :(得分:0)
试试如下:
cursor.count(false,function(err,total){console.log(“total”,total)})
core.db.users.find(query, {}, {skip:0, limit:1}, function(err, cursor){
if(err)
return callback(err);
cursor.toArray(function(err, items){
if(err)
return callback(err);
cursor.count(false, function(err, total){
if(err)
return callback(err);
console.log("cursor", total)
callback(null, {items: items, total:total})
})
})
})
答案 11 :(得分:0)
您可以使用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()
};
答案 12 :(得分:0)
在将聚合用于分页时,请注意。如果API经常用于用户获取数据,则最好使用两个查询。当更多的用户在线访问系统时,这至少比在生产服务器上使用聚合获取数据快50倍。总计和$ facet更适合不太频繁调用的Dashboard,报表和cron作业。