MongoDB-Auswahlanzahl (eindeutiges x) in einer indizierten Spalte - Anzahl eindeutiger Ergebnisse für große Datenmengen

82

Ich habe mehrere Artikel und Beispiele durchgesehen und noch keinen effizienten Weg gefunden, um diese SQL-Abfrage in MongoDB durchzuführen (wo es Millionen von gibt Reihen Unterlagen)

Erster Versuch

(zB aus dieser fast doppelten Frage - Mongo-Äquivalent zu SQLs SELECT DISTINCT? )

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

Offensichtlich habe ich diesen Fehler erhalten, da mein Datensatz riesig ist

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

Zweiter Versuch

Ich beschloss, eine Gruppe zu bilden

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

Aber ich habe stattdessen diese Fehlermeldung erhalten:

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

Dritter Versuch

Ich habe es noch nicht versucht, aber es gibt einige Vorschläge, die dies betreffen mapReduce

z.B

Ebenfalls

Es scheint, dass es eine Pull-Anfrage auf GitHub gibt, die die .distinctMethode korrigiert, um zu erwähnen, dass nur eine Zählung zurückgegeben werden soll, aber sie ist noch offen: https://github.com/mongodb/mongo/pull/34

Aber an diesem Punkt dachte ich, es lohnt sich, hier zu fragen, was ist das Neueste zu diesem Thema? Sollte ich für unterschiedliche Zählungen zu SQL oder einer anderen NoSQL-Datenbank wechseln? oder gibt es einen effizienten weg?

Aktualisieren:

Dieser Kommentar zu den offiziellen MongoDB-Dokumenten ist nicht ermutigend. Ist das richtig?

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

Update2:

Das neue Aggregation Framework scheint den obigen Kommentar zu beantworten ... (MongoDB 2.1 / 2.2 und höher, Entwicklungsvorschau verfügbar, nicht für die Produktion)

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

Eran Medan
quelle
Ich gehe davon aus, dass Sie dies häufig tun müssen, sonst wäre die Leistung nicht so wichtig. In diesem Fall würde ich die unterschiedlichen Werte in einer separaten Sammlung speichern, die aktualisiert wird, wenn Sie ein neues Dokument einfügen, anstatt zu versuchen, eine eindeutige für eine so große Sammlung zu erstellen. Entweder das, oder ich würde meine Verwendung von MongoDb neu bewerten und möglicherweise zu etwas anderem übergehen. Wie Sie festgestellt haben, ist MongoDb derzeit nicht gut in dem, was Sie versuchen.
Tim Gautier
@ TimGautier danke, ich hatte Angst, es hat Stunden gedauert, um all diese Werte einzufügen, und ich hätte vorher darüber nachdenken sollen :) Ich denke, ich werde jetzt die Zeit damit verbringen, es für diese Statistiken in MySQL einzufügen ...
Eran Medan
Sie können auch eine inkrementelle MR durchführen, die im Wesentlichen die Delta-Indizierung von Aggregatdaten emuliert. Ich meine, es hängt davon ab, wann Sie die Ergebnisse benötigen, was Sie verwenden. Ich kann mir vorstellen, dass MySQL viel E / A erhalten würde und was nicht, wenn ich dies tue (ich kann einen kleinen Server mit nur 100.000 Inline-Dokumenten in einem Index töten), aber ich nehme an, dass es noch flexibler ist, nach solchen Dingen zu fragen .
Sammaye
Ich bin nicht der Meinung, dass Mongo in solchen Dingen nicht gut ist. Diese Art von Sache ist das, was Mongo auszeichnet.
Superluminary
1
Leider hat der Moderator meine Antwort gelöscht, die ich auch auf doppelte Frage gestellt habe. Ich kann es dort nicht löschen und hier erneut posten, also Link: stackoverflow.com/a/33418582/226895
Experte

Antworten:

75

1) Der einfachste Weg, dies zu tun, ist über das Aggregationsframework. Dies erfordert zwei "$ group" -Befehle: Der erste gruppiert nach unterschiedlichen Werten, der zweite zählt alle unterschiedlichen Werte

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

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

2) Wenn Sie dies mit Map / Reduce tun möchten, können Sie. Dies ist auch ein zweiphasiger Prozess: In der ersten Phase erstellen wir eine neue Sammlung mit einer Liste aller eindeutigen Werte für den Schlüssel. Im zweiten Schritt zählen wir () für die neue Sammlung.

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() );

Beachten Sie, dass Sie das Ergebnis der Zuordnung / Inline-Reduzierung nicht zurückgeben können, da dies möglicherweise die Beschränkung der Dokumentgröße von 16 MB überschreitet. Sie können die Berechnung in einer Sammlung speichern und dann die Größe der Sammlung zählen () oder die Anzahl der Ergebnisse aus dem Rückgabewert von mapReduce () abrufen.

William Z.
quelle
5
Ich habe Mongo 2.2 RC0 heruntergeladen und Ihren ersten Vorschlag verwendet, und es funktioniert! und schnell! danke (gut gemacht 10gen ...) Erstellt hier eine Zusammenfassung (verwendet den Befehl zum Verknüpfen von Aggregaten und setzt ihn in eine Zeile) gist.github.com/3241616
Eran Medan
@EranMedan Ich sollte Sie jedoch warnen, ich habe das Aggregationsframework nicht vorgeschlagen, da 2.2 rc0 noch nicht wirklich für die vollständige Bereitstellung bereit ist. Ich würde nur bis zur vollständigen Version von 2.2 warten, bevor ich die Bereitstellung der Aggregation empfehle Rahmen.
Sammaye
@Sammaye ja, danke, ich bin mir dessen bewusst, werde noch nicht in Produktion gehen, ich brauchte das für interne Statistiken und wollte vermeiden, Daten nach Möglichkeit nach SQL zu verschieben (und meine Neugier stillen)
Eran Medan
Warum akzeptiert Mongo nicht: this.plugins.X-Powered-By.string? Wie würde ich dem entkommen?
EarlyPoster
Ich frage mich, ob diese Antwort für eine Sharded-Umgebung zuverlässig ist. So wie ich es verstehe, werden Shards jeweils ihre eigene Aggregation durchführen und dann das Ergebnis zurückgeben, wo die Ergebnisse dann aggregiert werden. Hätten wir in diesem Szenario nicht die Möglichkeit, dass Duplikate existieren, da die unterschiedlichen Werte in der zweiten $groupAnweisung verloren gegangen sind, bevor sie an Mongos zurückgegeben werden?
Verran
37
db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}});

direkt zum Ergebnis:

db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}})
   .result[0].count;
Stackee007
quelle
1
Richtig, das ist besser. Aber ist das nicht die gleiche Antwort, die William bereits gegeben hat?
JohnnyHK
2
Ähnlich, aber ich mag die Tatsache, dass es in einer Zeile steht. Ich habe jedoch eine Fehlermeldung erhalten: "Eigenschaft '0' von undefined kann nicht gelesen werden" Entfernen Sie die letzte Zeile und es funktioniert wunderbar.
Nico
und wenn wir über eine wirklich große Datenbank sprechen, vergessen Sie nicht {allowDiskUse: true}, also db.myCollection.aggregate ([{$ group ..}, {$ group:}], {allowDiskUse: true}). result [ 0] .count;
Hi_artem
3

Die folgende Lösung hat bei mir funktioniert

db.test.distinct ('Benutzer'); ["Alex", "England", "Frankreich", "Australien"]

db.countries.distinct ('Land'). Länge 4

Munib mir
quelle