La solución proporcionada por @Divergent funciona, pero en mi experiencia es mejor tener 2 consultas:
- Primero para filtrar y luego agrupar por ID para obtener la cantidad de elementos filtrados. No filtre aquí, es innecesario.
- Segunda consulta que filtra, ordena y pagina.
La solución al presionar $$ ROOT y usar $ slice tiene una limitación de memoria de documentos de 16 MB para colecciones grandes. Además, para colecciones grandes, dos consultas juntas parecen ejecutarse más rápido que la que tiene $$ ROOT presionando. También puede ejecutarlos en paralelo, por lo que está limitado solo por la más lenta de las dos consultas (probablemente la que ordena).
Me he conformado con esta solución usando 2 consultas y un marco de agregación (nota: uso node.js en este ejemplo, pero la idea es la misma):
var aggregation = [
{
// If you can match fields at the begining, match as many as early as possible.
$match: {...}
},
{
// Projection.
$project: {...}
},
{
// Some things you can match only after projection or grouping, so do it now.
$match: {...}
}
];
// Copy filtering elements from the pipeline - this is the same for both counting number of fileter elements and for pagination queries.
var aggregationPaginated = aggregation.slice(0);
// Count filtered elements.
aggregation.push(
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
);
// Sort in pagination query.
aggregationPaginated.push(
{
$sort: sorting
}
);
// Paginate.
aggregationPaginated.push(
{
$limit: skip + length
},
{
$skip: skip
}
);
// I use mongoose.
// Get total count.
model.count(function(errCount, totalCount) {
// Count filtered.
model.aggregate(aggregation)
.allowDiskUse(true)
.exec(
function(errFind, documents) {
if (errFind) {
// Errors.
res.status(503);
return res.json({
'success': false,
'response': 'err_counting'
});
}
else {
// Number of filtered elements.
var numFiltered = documents[0].count;
// Filter, sort and pagiante.
model.request.aggregate(aggregationPaginated)
.allowDiskUse(true)
.exec(
function(errFindP, documentsP) {
if (errFindP) {
// Errors.
res.status(503);
return res.json({
'success': false,
'response': 'err_pagination'
});
}
else {
return res.json({
'success': true,
'recordsTotal': totalCount,
'recordsFiltered': numFiltered,
'response': documentsP
});
}
});
}
});
});