2015-07-05 6 views
6

Ich habe ein MongoDB-Schema, das in etwa wie folgt aussieht:MongoDB-Listen - erhalten jeden N-ten Artikel

[ 
    { 
    "name" : "name1", 
    "instances" : [ 
     { 
     "value" : 1, 
     "date" : ISODate("2015-03-04T00:00:00.000Z")    
     }, 
     { 
     "value" : 2, 
     "date" : ISODate("2015-04-01T00:00:00.000Z") 
     }, 
     { 
     "value" : 2.5, 
     "date" : ISODate("2015-03-05T00:00:00.000Z") 
     }, 
     ... 
    ] 
    }, 
    { 
    "name" : "name2", 
    "instances" : [ 
     ... 
    ] 
    } 
] 

, wo die Anzahl der Instanzen für jedes Element kann sehr groß sein.

Ich möchte manchmal nur ein Muster der Daten, das heißt, jede dritte Instanz oder jede 10. Instanz bekommen ... Sie bekommen das Bild.

kann ich dieses Ziel erreichen, indem sie alle Instanzen bekommen und Filtern sie in meinem Server-Code, aber ich frage mich, ob es einen Weg gibt ist es Abfrage mithilfe etwas Aggregation zu tun.

Irgendwelche Ideen?


Aktualisiert

die Datenstruktur Unter der Annahme war flach wie @SylvainLeroux unten vorgeschlagen, das heißt:

[ 
    {"name": "name1", "value": 1, "date": ISODate("2015-03-04T00:00:00.000Z")}, 
    {"name": "name2", "value": 5, "date": ISODate("2015-04-04T00:00:00.000Z")}, 
    {"name": "name1", "value": 2, "date": ISODate("2015-04-01T00:00:00.000Z")}, 
    {"name": "name1", "value": 2.5, "date": ISODate("2015-03-05T00:00:00.000Z")}, 
    ... 
] 

wird die Aufgabe sein, jedes N-ten Elements des Erhaltens (spezifischer name) einfacher?

+1

FWIW: _ „die Anzahl der Instanzen für jedes Element kann sehr groß sein.“ _ Vielleicht hier ein eingebettetes Dokument verwendet, ist keine gute Idee, wenn die Anzahl von Dokumenten so groß ist, dass es schließlich die 32MB Grenze erreichen konnte. Wenn Sie regelmäßig auf eine Teilmenge dieses "großen" Arrays zugreifen müssen, ist es möglicherweise ein anderer Hinweis, dass das Schema nicht geeignet ist. Ohne Ihre genauen Bedürfnisse zu kennen, würde ich denken Sie an etwas mehr wie '{ "name": "name1", Wert: 1, Datum: ...}, { "name": "name1", Wert: 2, Datum: ...}, ... 'Mit diesem Schema könnten Sie sogar das _id-Feld verwenden (ab), um ein * voreingenommenes * Datenmuster auszuwählen. –

+0

@SylvainLeroux, Instanzen teilen mehr als nur ein Name (ich das Schema für diese Frage Zweck vereinfacht). Ich möchte nicht, dass jede Instanz im Wesentlichen dieselben Daten enthält wie andere, daher habe ich ein übergeordnetes Objekt mit mehreren Instanzen. Für mich ergab das mehr Sinn. Aber ich bin offen für Vorschläge ... – yarons

Antwort

1

Leider mit dem Aggregations Rahmen ist es nicht möglich, da dies eine Option erfordern würde mit $unwind einen Arrayindex/Position zu emittieren, von denen zur Zeit der Aggregation nicht verarbeiten kann. Hier gibt es ein offenes JIRA Ticket SERVER-4588.

würde jedoch eine Abhilfe MapReduce zu verwenden, aber dies kommt zu einem enorm Leistungskosten, da die tatsächlichen Berechnungen des Array-Index erhalten werden unter Verwendung des eingebettete JavaScript-Engine ausgeführt (was langsam ist), und es ist immer noch ein einzelne globale JavaScript-Sperre, die nur die Ausführung eines einzelnen JavaScript-Threads zu einem bestimmten Zeitpunkt ermöglicht

Mit verkleinern, könnten Sie so etwas wie dies versuchen:

Mapping-Funktion:

var map = function(){ 
    for(var i=0; i < this.instances.length; i++){ 
     emit(
      { "_id": this._id, "index": i }, 
      { "index": i, "value": this.instances[i] } 
     ); 
    } 
}; 

Reduce Funktion:

var reduce = function(){} 

Anschließend können Sie die folgende mapReduce Funktion laufen auf Sammlung:

db.collection.mapReduce(map, reduce, { out : "resultCollection" }); 

Und dann können Sie das Ergebnis Sammlung Geta Liste/Array von jedem n-ten Elemente der Instanz Array Abfrage unter Verwendung der map() Cursor-Methode:

var thirdInstances = db.resultCollection.find({"_id.index": N}) 
             .map(function(doc){return doc.value.value}) 
+1

Wie Sie bereits erwähnt haben, führt die Verwendung von MapReduce für Ad-hoc-Abfragen zu Leistungseinbußen. Abhängig vom Anwendungsfall kann das OP jedoch, wenn veraltete Daten zulässig sind, einen MapReduce-Job so planen, dass er regelmäßig ausgeführt wird, um eine Aggregatsammlung zu verarbeiten, die er später abfragen kann. –

+0

@SylvainLeroux Guter Ruf, stimme absolut zu. – chridam

2

Es scheint, dass Ihre Frage klar gefragt "bekomme jede n-te Instanz", was wie eine ziemlich klare Frage erscheint.

Abfrageoperationen wie .find() können eigentlich nur das Dokument zurück, mit Ausnahme der allgemeinen Feld „Auswahl“ in projection „wie sie ist“ und Operatoren wie der positional $ Spiel Betreiber oder $elemMatch, die einem einzigartiges abgestimmte Feldelement ermöglichen.

Natürlich gibt es $slice, aber das erlaubt nur eine „Bereichsauswahl“ auf dem Array, also wieder keine Anwendung findet.

Die "einzigen" Dinge, die ein Ergebnis auf dem Server ändern können, sind .aggregate() und .mapReduce(). Ersteres "spielt" nicht sehr gut mit dem "Aufteilen" von Arrays in irgendeiner Weise, zumindest nicht mit "n" -Elementen. Da die "function()" - Argumente von mapReduce jedoch JavaScript-basierte Logik sind, haben Sie etwas mehr Platz zum Spielen.

Für analytische Prozesse und für analytische Zwecke „nur“ dann filtern, dass nur die Array-Inhalte über verkleinern .filter() mit:

db.collection.mapReduce(
    function() { 
     var id = this._id; 
     delete this._id; 

     // filter the content of "instances" to every 3rd item only 
     this.instances = this.instances.filter(function(el,idx) { 
      return ((idx+1) % 3) == 0; 
     }); 
     emit(id,this); 
    }, 
    function() {}, 
    { "out": { "inline": 1 } } // or output to collection as required 
) 

Es ist wirklich ein einfach „JavaScript-runner“ an diesem Punkt, aber wenn dies ist nur für die analyse/testung gibt es dann überhaupt nichts falsch mit dem konzept. Natürlich ist die Ausgabe nicht "genau", wie Ihr Dokument strukturiert ist, aber es ist so nah wie ein Faksimile, wie mapReduce erhalten kann.

Der andere Vorschlag, den ich hier sehe, erfordert das Erstellen einer neuen Sammlung mit allen Elementen "denormalized" und Einfügen des "Index" aus dem Array als Teil des eindeutigen Schlüssels _id. Dass etwas produzieren können Sie direkt abfragen können, bu für den „jeden n-ten Punkt“ würden Sie noch tun müssen:

db.resultCollection.find({ 
    "_id.index": { "$in": [2,5,8,11,14] } // and so on .... 
}) 

So erarbeiten und liefern den Indexwert von „jedem n-ten Punkt“ in Ordnung "zu bekommen jeder nte Artikel ". Das scheint das Problem, das gestellt wurde, nicht wirklich zu lösen.

Wenn schien die Ausgabeform mehr wünschenswert für Ihre „Testen“ Zwecke, dann eine bessere nachfolgende Abfrage auf diese Ergebnisse mit $redact

db.newCollection([ 
    { "$redact": { 
     "$cond": { 
      "if": { 
       "$eq": [ 
        { "$mod": [ { "$add": [ "$_id.index", 1] }, 3 ] }, 
       0 ] 
      }, 
      "then": "$$KEEP", 
      "else": "$$PRUNE" 
     } 
    }} 
]) 

Das zumindest verwendet eine „logische Bedingung mit der Aggregation Pipeline würde "viel das gleiche wie das, was mit .filter() angewandt wurde, bevor nur auswählen, um den‚n-te Index‘Artikel ohne Auflistung aller möglichen Indexwerte als Abfrage Argument.

0

Oder mit nur einem Fund Block:

db.Collection.find({}).then(function(data) { 
    var ret = []; 
    for (var i = 0, len = data.length; i < len; i++) { 
    if (i % 3 === 0) { 
     ret.push(data[i]); 
    } 
    } 
    return ret; 
}); 

Gibt ein Versprechen, dessen dann() Sie die N-te modulo'ed Daten abzurufen aufrufen kann.

+0

Wie Sie in meiner Frage sehen können, habe ich bereits gesagt, dass ich alle Elemente bekommen und sie in meinem Code filtern kann. Meine Frage war, ob es eine Möglichkeit gibt, dies mit einer Mongo-Abfrage zu tun. – yarons

Verwandte Themen