Giải pháp do @Divergent cung cấp không hoạt động, nhưng theo kinh nghiệm của tôi, tốt hơn là nên có 2 truy vấn:
- Đầu tiên để lọc và sau đó nhóm theo ID để lấy số phần tử được lọc. Đừng lọc ở đây, nó là không cần thiết.
- Truy vấn thứ hai lọc, sắp xếp và phân trang.
Giải pháp với việc đẩy $$ ROOT và sử dụng $ slice sẽ gặp phải giới hạn bộ nhớ tài liệu là 16MB cho các bộ sưu tập lớn. Ngoài ra, đối với các bộ sưu tập lớn, hai truy vấn cùng nhau dường như chạy nhanh hơn truy vấn có $$ ROOT đẩy. Bạn cũng có thể chạy chúng song song, vì vậy bạn chỉ bị giới hạn bởi tốc độ chậm hơn của hai truy vấn (có thể là truy vấn sắp xếp).
Tôi đã giải quyết với giải pháp này bằng cách sử dụng 2 truy vấn và khung tổng hợp (lưu ý - tôi sử dụng node.js trong ví dụ này, nhưng ý tưởng giống nhau):
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
});
}
});
}
});
});