使用 limit 时,通过 MongoDB 获取文档总数

我对优化我正在使用 MongoDB 开发的“分页”解决方案很感兴趣。我的问题很直接。我通常使用 limit()功能限制返回的文档数量。这迫使我发出一个没有 limit()函数的冗余查询,以便我也捕获查询中的文档总数,这样我就可以传递给客户端,让他们知道他们将不得不发出一个额外的请求来检索剩余的文档。

有没有办法把它浓缩成一个查询?获取文档的总数,但同时只检索一个子集使用 limit()?有没有一种不同的方式来思考这个问题,而不是我正在处理它?

70584 次浏览

没有强,没有别的办法。两个查询-一个用于计数-一个有限制。或者你必须使用不同的数据库。例如,ApacheSolr 的工作方式如您所愿。每个查询都有有限的 还有返回 talCount。

这完全取决于您是否需要执行两个查询所需的分页体验。

您是否需要列出每一个页面甚至一系列页面?有人翻到1051页了吗? 概念上讲,这到底是什么意思?

有很多关于分页模式的用户体验-避免分页的痛苦涵盖了各种类型的分页及其场景,许多不需要计数查询就可以知道是否有下一个页面。例如,如果您在一个页面上显示10个项目,并限制为13-您将知道是否有另一个页面。.

您可以在一个查询中完成此操作。

在 Node.js 和 Express.js 中,必须像这样使用它才能使用“ count”函数和 toArray 的“ result”函数。

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


});


});

使用 count()有可能在不受 limit()影响的情况下获得总的结果大小,如下所示: 在 MongoDB 中限制结果但仍然得到完整的计数?

根据文档,您甚至可以控制在调用 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)

时代变了,我相信你可以通过使用 $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 (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})
})
})
})

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 }
}

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" }
]
}}
])

默认情况下,count ()方法忽略 Skip ()和 cursor.limit ()(MongoDB 文档)

由于 count 方法排除了限制和跳过的影响,您可以使用 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()
};

在使用分页聚合时需要注意的一点是。如果用户经常使用 API 来获取数据,那么最好使用两个查询。当更多用户在线访问系统时,这至少比在生产服务器上使用聚合获取数据的速度快50倍。聚合和 $facet 更适合调用频率较低的 Dashboard、报告和 cron 作业。

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 的详细信息,请访问 这一页

下面介绍如何使用 $facetsMongoDB 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 获取返回的分页信息非常有用。

参考资料: MongoDB $facet

我们可以使用2个查询。

    const limit = parseInt(req.query.limit || 50, 10);
let page = parseInt(req.query.page || 0, 10);
if (page > 0) { page = page - 1}


let doc = await req.db.collection('bookings').find().sort( { _id: -1 }).skip(page).limit(limit).toArray();
let count = await req.db.collection('bookings').find().count();
res.json({data: [...doc], count: count});

我采用了两种查询方法,下面的代码直接取自我正在进行的一个项目,使用 MongoDB Atlas 和一个全文搜索索引:

return new Promise( async (resolve, reject) => {
try {


const search = {
$search: {
index: 'assets',
compound: {
should: [{
text: {
query: args.phraseToSearch,
path: [
'title', 'note'
]
}
}]
}
}
}


const project = {
$project: {
_id: 0,
id: '$_id',
userId: 1,
title: 1,
note: 1,
score: {
$meta: 'searchScore'
}
}
}


const match = {
$match: {
userId: args.userId
}
}


const skip = {
$skip: args.skip
}


const limit = {
$limit: args.first
}


const group = {
$group: {
_id: null,
count: { $sum: 1 }
}
}


const searchAllAssets = await Models.Assets.schema.aggregate([
search, project, match, skip, limit
])


const [ totalNumberOfAssets ] = await Models.Assets.schema.aggregate([
search, project, match, group
])


return await resolve({
searchAllAssets: searchAllAssets,
totalNumberOfAssets: totalNumberOfAssets.count
})


} catch (exception) {
return reject(new Error(exception))
}
})

我也遇到过同样的问题,并且遇到了这个问题。这个问题的正确解决方案发布在 给你上。