Licznik wyboru MongoDB (odrębny x) w kolumnie indeksowanej - zliczaj unikalne wyniki dla dużych zestawów danych

82

Przejrzałem kilka artykułów i przykładów, ale nie znalazłem jeszcze efektywnego sposobu wykonania tego zapytania SQL w MongoDB (gdzie są miliony wydziwianie dokumenty)

Pierwsze podejscie

(np. z tego prawie zduplikowanego pytania - odpowiednik języka SQL SELECT DISTINCT w Mongo? )

db.myCollection.distinct("myIndexedNonUniqueField").length

Oczywiście otrzymałem ten błąd, ponieważ mój zbiór danych jest ogromny

Thu Aug 02 12:55:24 uncaught exception: distinct failed: {
        "errmsg" : "exception: distinct too big, 16mb cap",
        "code" : 10044,
        "ok" : 0
}

Drugie podejście

Postanowiłem spróbować założyć grupę

db.myCollection.group({key: {myIndexedNonUniqueField: 1},
                initial: {count: 0}, 
                 reduce: function (obj, prev) { prev.count++;} } );

Ale zamiast tego otrzymałem ten komunikat o błędzie:

exception: group() can't handle more than 20000 unique keys

Trzecia próba

Jeszcze nie próbowałem, ale jest kilka sugestii, które obejmują mapReduce

na przykład

Również

Wygląda na to, że na GitHubie istnieje żądanie ściągnięcia naprawiające .distinctmetodę, aby wspomnieć, że powinna ona zwracać tylko liczbę, ale nadal jest otwarta: https://github.com/mongodb/mongo/pull/34

Ale w tym miejscu pomyślałem, że warto tutaj zapytać, co jest na ten temat najnowsze? Czy powinienem przejść do SQL lub innej bazy danych NoSQL, aby uzyskać różne liczby? czy jest skuteczny sposób?

Aktualizacja:

Ten komentarz do oficjalnej dokumentacji MongoDB nie jest zachęcający, czy to prawda?

http://www.mongodb.org/display/DOCS/Aggregation#comment-430445808

Aktualizacja2:

Wygląda na to, że nowa platforma agregacji odpowiada na powyższy komentarz ... (MongoDB 2.1 / 2.2 i nowsze, dostępna wersja deweloperska, a nie produkcyjna)

http://docs.mongodb.org/manual/applications/aggregation/

Eran Medan
źródło
Zakładam, że musisz to robić często, inaczej wydajność nie miałaby większego znaczenia. W takim przypadku zapisałbym odrębne wartości w oddzielnej kolekcji, która jest aktualizowana po wstawieniu nowego dokumentu, zamiast próbować zrobić coś odrębnego w tak dużej kolekcji. Albo to, albo ponownie oceniłbym moje użycie MongoDb i prawdopodobnie przeniósłbym się do czegoś innego. Jak odkryłeś, MongoDb obecnie nie jest dobry w tym, co próbujesz zrobić.
Tim Gautier
@TimGautier dzięki, obawiałem się, że wstawienie wszystkich tych wartości zajęło godziny i powinienem był o tym pomyśleć wcześniej :) Myślę, że spędzę teraz czas, aby wstawić to do MySQL dla tych statystyk ...
Eran Medan
Możesz również wykonać przyrostowy MR, w zasadzie emulując indeksowanie delta zagregowanych danych. Mam na myśli to, że zależy to od tego, kiedy potrzebujesz wyników, co do tego, czego używasz. Mogę sobie wyobrazić, że MySQL prawdopodobnie dostałby dużo IO, a czego nie robiąc tego (mogę zabić mały serwer, wyróżniając tylko 100k dokumentów w indeksie), ale przypuszczam, że jest bardziej elastyczny w zapytaniach o tego rodzaju rzeczy nadal .
Sammaye
Nie zgadzam się, że mongo nie jest dobre w tego typu rzeczach. W takim przypadku Mongo wyróżnia się.
superluminarium
1
Niestety moderator usunął moją odpowiedź, którą zamieściłem również na zduplikowanym pytaniu. Nie mogę go tam usunąć i ponownie opublikować tutaj, więc link: stackoverflow.com/a/33418582/226895
ekspert

Odpowiedzi:

75

1) Najłatwiej to zrobić za pomocą struktury agregacji. To wymaga dwóch poleceń „$ group”: pierwsza grupuje według odrębnych wartości, druga zlicza wszystkie odrębne wartości

pipeline = [ 
    { $group: { _id: "$myIndexedNonUniqueField"}  },
    { $group: { _id: 1, count: { $sum: 1 } } }
];

//
// Run the aggregation command
//
R = db.runCommand( 
    {
    "aggregate": "myCollection" , 
    "pipeline": pipeline
    }
);
printjson(R);

2) Jeśli chcesz to zrobić za pomocą Map / Reduce, możesz. Jest to również proces dwufazowy: w pierwszej fazie tworzymy nową kolekcję z listą wszystkich odrębnych wartości klucza. W drugim liczymy () na nowej kolekcji.

var SOURCE = db.myCollection;
var DEST = db.distinct
DEST.drop();


map = function() {
  emit( this.myIndexedNonUniqueField , {count: 1});
}

reduce = function(key, values) {
  var count = 0;

  values.forEach(function(v) {
    count += v['count'];        // count each distinct value for lagniappe
  });

  return {count: count};
};

//
// run map/reduce
//
res = SOURCE.mapReduce( map, reduce, 
    { out: 'distinct', 
     verbose: true
    }
    );

print( "distinct count= " + res.counts.output );
print( "distinct count=", DEST.count() );

Zauważ, że nie możesz zwrócić wyniku mapowania / zmniejszania w wierszu, ponieważ potencjalnie przekroczy to limit rozmiaru dokumentu 16 MB. Państwo może zapisać obliczenia w kolekcji, a następnie policzyć () wielkość zbiorów, lub można uzyskać liczbę wyników od wartości zwracanej mapreduce ().

William Z
źródło
5
Pobrałem Mongo 2.2 RC0 i skorzystałem z Twojej pierwszej sugestii i działa! i szybko! dziękuję (dobra robota 10gen ...) Utworzono tutaj sedno (użyto polecenia agregacji skrótu i ​​umieściłem go w jednej linii) gist.github.com/3241616
Eran Medan
@EranMedan Powinienem cię jednak ostrzec, nie sugerowałem frameworka agregacji, ponieważ 2.2 rc0 nadal nie jest tak naprawdę gotowe do pełnego wdrożenia, tylko coś, o czym należy pamiętać, poczekałbym do pełnej wersji 2.2 przed zaleceniem wdrożenia agregacji struktura.
Sammaye
@Sammaye tak, dzięki jestem tego świadomy, jeszcze nie trafię do produkcji, potrzebowałem tego do wewnętrznych statystyk i chciałem uniknąć przenoszenia danych do SQL, jeśli to możliwe (i ugasić moją ciekawość)
Eran Medan
Dlaczego Mongo nie akceptuje: this.plugins.X-Powered-By.string? Jak miałbym od tego uciec?
EarlyPoster
Zastanawiam się, czy ta odpowiedź jest wiarygodna dla środowiska podzielonego na fragmenty. Jak rozumiem, każdy z shardów przeprowadzi własną agregację, a następnie zwróci wynik, w którym wyniki zostaną zagregowane. Więc w tym scenariuszu, czy nie mielibyśmy szansy na istnienie duplikatów, ponieważ różne wartości zostały utracone w drugiej $groupinstrukcji, zanim zostaną przekazane z powrotem do mongosów?
Verran
37
db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}});

prosto do wyniku:

db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}})
   .result[0].count;
Stackee007
źródło
1
Tak, tak lepiej. Ale czy nie jest to ta sama odpowiedź, której udzielił już William?
JohnnyHK,
2
Podobnie, ale podoba mi się to, że jest w jednej linii. Wystąpił jednak błąd: „Nie można odczytać właściwości '0' wartości undefined” Usuń ostatnią linię i działa pięknie.
Nico
a jeśli mówimy o naprawdę ogromnej bazie danych, nie zapomnij o {allowDiskUse: true}, więc db.myCollection.aggregate ([{$ group ..}, {$ group:}], {allowDiskUse: true}). result [ 0] .count;
hi_artem
3

Poniższe rozwiązanie zadziałało dla mnie

db.test.distinct ('użytkownik'); [„alex”, „Anglia”, „Francja”, „Australia”]

db.countries.distinct ('kraj'). długość 4

Munib mir
źródło