Решение, предоставленное @Divergent, действительно работает, но, по моему опыту, лучше иметь 2 запроса:
- Сначала для фильтрации, а затем для группировки по идентификатору, чтобы получить количество отфильтрованных элементов. Не фильтруйте здесь, это не нужно.
- Второй запрос, который фильтрует, сортирует и разбивает на страницы.
Решение с нажатием $$ ROOT и использованием $ slice приводит к ограничению памяти документа в 16 МБ для больших коллекций. Кроме того, для больших коллекций два запроса вместе, кажется, выполняются быстрее, чем запрос с нажатием $$ ROOT. Вы также можете запускать их параллельно, поэтому вы ограничены только более медленным из двух запросов (возможно, тем, который сортирует).
Я выбрал это решение, используя 2 запроса и структуру агрегации (примечание - в этом примере я использую node.js, но идея та же):
var aggregation = [
{
$match: {...}
},
{
$project: {...}
},
{
$match: {...}
}
];
var aggregationPaginated = aggregation.slice(0);
aggregation.push(
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
);
aggregationPaginated.push(
{
$sort: sorting
}
);
aggregationPaginated.push(
{
$limit: skip + length
},
{
$skip: skip
}
);
model.count(function(errCount, totalCount) {
model.aggregate(aggregation)
.allowDiskUse(true)
.exec(
function(errFind, documents) {
if (errFind) {
res.status(503);
return res.json({
'success': false,
'response': 'err_counting'
});
}
else {
var numFiltered = documents[0].count;
model.request.aggregate(aggregationPaginated)
.allowDiskUse(true)
.exec(
function(errFindP, documentsP) {
if (errFindP) {
res.status(503);
return res.json({
'success': false,
'response': 'err_pagination'
});
}
else {
return res.json({
'success': true,
'recordsTotal': totalCount,
'recordsFiltered': numFiltered,
'response': documentsP
});
}
});
}
});
});