Użyłem agregacji do pobierania rekordów z mongodb.
$result = $collection->aggregate(array(
array('$match' => $document),
array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'), 'views' => array('$sum' => 1))),
array('$sort' => $sort),
array('$skip' => $skip),
array('$limit' => $limit),
));
Jeśli wykonam to zapytanie bez ograniczeń, zostanie pobranych 10 rekordów. Ale chcę zachować limit na 2. Więc chciałbym uzyskać łączną liczbę rekordów. Jak mogę zrobić z agregacją? Proszę, poradź mi. Dzięki
Odpowiedzi:
Jest to jedno z najczęściej zadawanych pytań, aby uzyskać wynik podzielony na strony i całkowitą liczbę wyników jednocześnie w jednym zapytaniu. Nie potrafię wyjaśnić, jak się czułem, kiedy w końcu to osiągnąłem LOL.
$result = $collection->aggregate(array( array('$match' => $document), array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'), 'views' => array('$sum' => 1))), array('$sort' => $sort), // get total, AND preserve the results array('$group' => array('_id' => null, 'total' => array( '$sum' => 1 ), 'results' => array( '$push' => '$$ROOT' ) ), // apply limit and offset array('$project' => array( 'total' => 1, 'results' => array( '$slice' => array( '$results', $skip, $length ) ) ) ) ))
Wynik będzie wyglądał mniej więcej tak:
[ { "_id": null, "total": ..., "results": [ {...}, {...}, {...}, ] } ]
źródło
{ $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}
(wstaw po{$group:{}}
zliczeniu całkowitego znalezienia.Od wersji 3.4 (tak mi się wydaje) MongoDB ma teraz nowego operatora potoku agregacji o nazwie „ facet ”, który własnymi słowami:
W tym konkretnym przypadku oznacza to, że można zrobić coś takiego:
$result = $collection->aggregate([ { ...execute queries, group, sort... }, { ...execute queries, group, sort... }, { ...execute queries, group, sort... }, $facet: { paginatedResults: [{ $skip: skipPage }, { $limit: perPage }], totalCount: [ { $count: 'count' } ] } ]);
Wynik będzie (z wynikiem ex 100):
[ { "paginatedResults":[{...},{...},{...}, ...], "totalCount":[{"count":100}] } ]
źródło
$project
?Użyj tego, aby znaleźć całkowitą liczbę w wynikowym zbiorze.
db.collection.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] );
źródło
Możesz użyć funkcji toArray, a następnie uzyskać jej długość dla łącznej liczby rekordów.
źródło
TypeError: Parent.aggregate(...).toArray is not a function
to jest błąd, który podałem przy tym rozwiązaniu.Użyj etapu potoku agregacji $ count, aby uzyskać łączną liczbę dokumentów:
Zapytanie:
db.collection.aggregate( [ { $match: { ... } }, { $group: { ... } }, { $count: "totalCount" } ] )
Wynik:
{ "totalCount" : Number of records (some integer value) }
źródło
Zrobiłem to w ten sposób:
db.collection.aggregate([ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] ).map(function(record, index){ print(index); });
Agregat zwróci tablicę, więc po prostu zapętl ją i uzyskaj ostateczny indeks.
Innym sposobem na to jest:
var count = 0 ; db.collection.aggregate([ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] ).map(function(record, index){ count++ }); print(count);
źródło
var
deklaracji animap
wezwania. Wystarczą pierwsze 3 wiersze pierwszego przykładu.Rozwiązanie dostarczone przez @Divergent działa, ale z mojego doświadczenia wynika, że lepiej mieć 2 zapytania:
Rozwiązanie z wypychaniem $$ ROOT i użyciem $ slice powoduje ograniczenie pamięci dokumentów do 16 MB dla dużych zbiorów. Ponadto w przypadku dużych kolekcji dwa zapytania razem wydają się działać szybciej niż to z wypychaniem $$ ROOT. Możesz je również uruchamiać równolegle, więc ogranicza Cię tylko wolniejsze z dwóch zapytań (prawdopodobnie to, które sortuje).
Rozstrzygnąłem się z tym rozwiązaniem za pomocą 2 zapytań i frameworka agregacji (uwaga - w tym przykładzie używam node.js, ale pomysł jest ten sam):
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 }); } }); } }); });
źródło
//const total_count = await User.find(query).countDocuments(); //const users = await User.find(query).skip(+offset).limit(+limit).sort({[sort]: order}).select('-password'); const result = await User.aggregate([ {$match : query}, {$sort: {[sort]:order}}, {$project: {password: 0, avatarData: 0, tokens: 0}}, {$facet:{ users: [{ $skip: +offset }, { $limit: +limit}], totalCount: [ { $count: 'count' } ] }} ]); console.log(JSON.stringify(result)); console.log(result[0]); return res.status(200).json({users: result[0].users, total_count: result[0].totalCount[0].count});
źródło
Może to działać w przypadku wielu warunków dopasowania
const query = [ { $facet: { cancelled: [ { $match: { orderStatus: 'Cancelled' } }, { $count: 'cancelled' } ], pending: [ { $match: { orderStatus: 'Pending' } }, { $count: 'pending' } ], total: [ { $match: { isActive: true } }, { $count: 'total' } ] } }, { $project: { cancelled: { $arrayElemAt: ['$cancelled.cancelled', 0] }, pending: { $arrayElemAt: ['$pending.pending', 0] }, total: { $arrayElemAt: ['$total.total', 0] } } } ] Order.aggregate(query, (error, findRes) => {})
źródło
Po zastosowaniu agregacji potrzebowałem całkowitej liczby całkowitej. To zadziałało dla mnie:
db.mycollection.aggregate([ { $group: { _id: { field1: "$field1", field2: "$field2" }, } }, { $group: { _id: null, count: { $sum: 1 } } } ])
Wynik:
{ "_id" : null, "count" : 57.0 }
źródło
Oto kilka sposobów na uzyskanie całkowitej liczby rekordów podczas agregacji MongoDB:
Używając
$count
:db.collection.aggregate([ // Other stages here { $count: "Total" } ])
Uzyskanie 1000 rekordów zajmuje średnio 2 ms i jest najszybszym sposobem.
Używając
.toArray()
:Uzyskanie 1000 rekordów zajmuje średnio 18 ms.
Używając
.itcount()
:Uzyskanie 1000 rekordów zajmuje średnio 14 ms.
źródło
Przepraszam, ale myślę, że potrzebujesz dwóch zapytań. Jeden dla wszystkich wyświetleń, a drugi dla zgrupowanych rekordów.
Możesz znaleźć przydatną odpowiedź
źródło
Jeśli nie chcesz grupować, użyj następującej metody:
db.collection.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $count: 'count' } ] );
źródło