Ответ 1
Нет, другого пути нет. Два запроса - один для подсчета - один с лимитом. Или вам нужно использовать другую базу данных. Apache Solr, например, работает так, как вы хотите. Каждый запрос имеет ограниченные и возвращает totalCount.
Я заинтересован в оптимизации решения "pagination", над которым я работаю с MongoDB. Моя проблема прямолинейна. Обычно я ограничиваю количество возвращаемых документов с помощью функции limit()
. Это заставляет меня выдавать избыточный запрос без функции limit()
, чтобы я мог также захватить общее количество документов в запросе, чтобы я мог передать это клиенту, сообщив им, что им придется выдать дополнительный запрос для извлечения остальных документов.
Есть ли способ сконденсировать это в 1 запрос? Получить общее количество документов, но в то же время получить только подмножество с помощью limit()
? Есть ли другой способ думать об этой проблеме, чем я приближаюсь к ней?
Нет, другого пути нет. Два запроса - один для подсчета - один с лимитом. Или вам нужно использовать другую базу данных. Apache Solr, например, работает так, как вы хотите. Каждый запрос имеет ограниченные и возвращает totalCount.
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 }
}}
]
}}
])
Даже вы можете использовать агрегацию $count
которая была представлена в mongodb 3.6.
Вы можете использовать агрегацию ниже в mongodb 3.6
db.collection.aggregate([
{ "$facet": {
"totalData": [
{ "$match": { }},
{ "$skip": 10 },
{ "$limit": 10 }
],
"totalCount": [
{ "$count": "count" }
]
}}
])
есть способ в монгольде 3.4: $facet
вы можете сделать
db.collection.aggregate([
{
$facet: {
data: [{ $match: {} }],
total: { $count: 'total' }
}
}
])
тогда вы сможете одновременно запускать два агрегата
Времена изменились, и я считаю, что вы можете достичь того, что спрашивает OP, используя агрегацию с $sort
, $group
и $project
. Для моей системы мне также нужно было получить некоторую информацию о пользователе из моей коллекции 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
]
}
} }
])
Все зависит от опыта разбивки на страницы, который вам нужен, независимо от того, нужно ли вам выполнять два запроса.
Вам нужно перечислить каждую страницу или даже целый ряд страниц? Кто-нибудь даже идет на страницу 1051 - концептуально, что это значит?
Theres было много UX по шаблонам разбивки на страницы - Избегайте болей разбивки на страницы, охватывает различные типы разбивки на страницы и их сценарии, и многие из них не нужен запрос count, чтобы узнать, есть ли следующая страница. Например, если вы отобразите 10 элементов на странице, и вы ограничены до 13 - вы узнаете, есть ли другая страница.
MongoDB позволяет использовать cursor.count()
даже при передаче limit()
или 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 }
}
Вы можете сделать это в одном запросе. Сначала вы запускаете счетчик и внутри этого запускаете функцию limit().
В Node.js и Express.js вам придется использовать его таким образом, чтобы иметь возможность использовать функцию count и вместе с результатом resultArray.
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
});
});
Можно получить общий размер результата без эффекта limit()
с помощью count()
, как здесь указано:
Ограничить результаты в 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)
Попробуйте как ниже:
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})
})
})
})
Вы можете использовать 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()
};