2013-07-20 13 views
12

Podczas uruchamiania normalnego zapytania "find" na MongoDB mogę uzyskać całkowitą liczbę wyników (niezależnie od limitu) przez uruchomienie "count" na zwróconym kursie. Tak więc, nawet jeśli ograniczę się do wyniku ustawionego na 10 (na przykład), nadal mogę wiedzieć, że całkowita liczba wyników wyniosła 53 (ponownie, na przykład).MongoDB - Aggregation Framework (Total Count)

Jeśli dobrze rozumiem, struktura agregacji nie zwraca jednak kursora, ale po prostu wyniki. A więc, jeśli użyłem operatora rurociągu $limit, w jaki sposób mogę poznać całkowitą liczbę wyników niezależnie od wspomnianego limitu?

Zgaduję, że mogłem uruchomić agregację dwa razy (raz, aby policzyć wyniki przez $group, i raz z $limit dla rzeczywistych ograniczonych wyników), ale wydaje się to nieefektywne.

Alternatywnym podejściem może być dołączenie łącznej liczby wyników do dokumentów (przez $group) przed operacją $limit, ale wydaje się to również nieskuteczne, ponieważ ta liczba zostanie dołączona do każdego dokumentu (zamiast tylko raz zwrócona dla zbiór).

Czy tu czegoś brakuje? Jakieś pomysły? Dzięki!

Na przykład, jeśli jest to zapytanie:

db.article.aggregate(
    { $group : { 
     _id : "$author", 
     posts : { $sum : 1 } 
    }}, 
    { $sort : { posts: -1 } }, 
    { $limit : 5 } 
); 

Skąd mam wiedzieć, ile wyniki są dostępne (przed $limit)? Wynik nie jest kursorem, więc nie mogę po prostu na nim liczyć.

+0

byłoby pomocne, jeśli dodasz swoje zapytania jako przykład do pytania –

+0

Dodano próbkę kodu. Pytanie jest jednak ogólne (myślę). Dzięki! –

Odpowiedz

3

Assaf, w najbliższej przyszłości będzie kilka ulepszeń w strukturze agregacji, które pozwolą Ci łatwo wykonać obliczenia w jednym przebiegu, ale teraz najlepiej jest wykonywać obliczenia, równolegle uruchamiając dwa zapytania : jeden do agregowania #posts dla najlepszych autorów i innej agregacji do obliczania łącznej liczby postów dla wszystkich autorów. Należy również pamiętać, że jeśli wszystko, co musisz zrobić, to liczenie dokumentów, użycie funkcji liczenia jest bardzo skutecznym sposobem wykonywania obliczeń. MongoDB buforuje liczby w indeksach btree, co pozwala na bardzo szybkie liczenie zapytań.

Jeśli te agregacje okażą się powolne, istnieje kilka strategii. Po pierwsze, pamiętaj, że chcesz uruchomić kwerendę z dopasowaniem $, jeśli to konieczne, aby zmniejszyć zestaw wyników. Mecze $ można także przyspieszyć o indeksy. Po drugie, możesz wykonać te obliczenia jako pre-agregacje. Zamiast uruchamiać te agregacje za każdym razem, gdy użytkownik uzyskuje dostęp do jakiejś części aplikacji, agregacje należy uruchamiać okresowo w tle i przechowywać agregacje w kolekcji zawierającej wstępnie zagregowane wartości. Dzięki temu Twoje strony mogą po prostu sprawdzać wstępnie obliczone wartości z tej kolekcji.

+0

Dzięki za odpowiedź. Przydatne wiedzieć. Poszedłem na połączenie rozwiązań w mojej prawdziwej aplikacji, na przykład za pomocą dopasowania $ tam, gdzie to możliwe, wstępnego obliczenia, jeśli to możliwe, i po prostu bez liczenia w innych przypadkach. Powyższe zapytanie było tylko przykładem (ponieważ poproszono mnie o podanie kodu). –

+3

@Dylan czy wiesz, czy te ulepszenia zostały już zakończone? – Dan

0

Jeśli nie chcesz równolegle wykonywać dwóch zapytań (jeden do agregowania #posts dla najlepszych autorów i innej agregacji do obliczania wszystkich postów dla wszystkich autorów), możesz po prostu usunąć $ limit na potoku i na wyniki można użyć

totalCount = results.length; 
results.slice(number of skip,number of skip + number of limit); 

ex:

db.article.aggregate([ 
    { $group : { 
     _id : "$author", 
     posts : { $sum : 1 } 
    }}, 
    { $sort : { posts: -1 } } 
    //{$skip : yourSkip}, //--remove this 
    //{ $limit : yourLimit }, // remove this too 
]).exec(function(err, results){ 
    var totalCount = results.length;//--GEt total count here 
    results.slice(yourSkip,yourSkip+yourLimit); 
}); 
0

w moim przypadku, używamy $ na scenę zrzucić zestaw wyników z przesadą w tabeli temp/cache, a następnie policzyć go. i, ponieważ musimy sortować i paginować wyniki, dodajemy indeks do tabeli tymczasowej i zapisujemy nazwę tabeli w sesji, usuwamy tabelę przy zamykaniu sesji/przekroczeniu czasu pamięci podręcznej.

0

Mam ten sam problem i rozwiązał z $ projektu, $ slice i $$ ROOT.

db.article.aggregate(
{ $group : { 
    _id : '$author', 
    posts : { $sum : 1 }, 
    articles: {$push: '$$ROOT'}, 
}}, 
{ $sort : { posts: -1 } }, 
{ $project: {total: '$posts', articles: {$slice: ['$articles', from, to]}}, 
).toArray(function(err, result){ 
    var articles = result[0].articles; 
    var total = result[0].total; 
}); 

Musisz zadeklarować from i to zmienną.

https://docs.mongodb.com/manual/reference/operator/aggregation/slice/

2

Istnieje rozwiązanie używając pchają i kawałek: https://stackoverflow.com/a/39784851/4752635 (@emaniacs wspomina również tutaj).

Ale wolę używać 2 zapytań. Rozwiązanie z naciśnięciem $$ ROOT i użyciem $ slice uruchamia ograniczenie pamięci dokumentów o wielkości 16 MB dla dużych kolekcji. Również w przypadku dużych kolekcji dwa zapytania razem wydają się działać szybciej niż w przypadku przepychania $$ ROOT. Możesz je uruchamiać równolegle, więc ogranicza Cię tylko wolniejszy z dwóch zapytań (prawdopodobnie ten, który sortuje).

  1. Najpierw do filtrowania, a następnie grupowania według identyfikatora, aby uzyskać liczbę filtrowanych elementów. Nie filtruj tutaj, nie jest to konieczne.
  2. Druga kwerenda, która filtruje, sortuje i stronicuje.

mam rozliczane z tego rozwiązania przy użyciu 2 zapytań i ramy Agregacja (Uwaga - używam node.js w tym przykładzie):

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 
      }); 
      } 
     }); 
    } 
    }); 
}); 
1

uzyskać całkowitą liczbę z aggregate().toArray().length

Powiązane problemy