La solution fournie par @Divergent fonctionne, mais d'après mon expérience, il est préférable d'avoir 2 requêtes:
- D'abord pour le filtrage, puis le regroupement par ID pour obtenir le nombre d'éléments filtrés. Ne filtrez pas ici, c'est inutile.
- Deuxième requête qui filtre, trie et pagine.
La solution consistant à pousser $$ ROOT et à utiliser $ slice s'exécute dans une limitation de la mémoire de document de 16 Mo pour les grandes collections. En outre, pour les grandes collections, deux requêtes semblent s'exécuter plus rapidement que celle avec $$ ROOT poussant. Vous pouvez également les exécuter en parallèle, vous n'êtes donc limité que par la plus lente des deux requêtes (probablement celle qui trie).
J'ai réglé cette solution en utilisant 2 requêtes et un cadre d'agrégation (note - j'utilise node.js dans cet exemple, mais l'idée est la même):
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
});
}
});
}
});
});