MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

MongoDB:Aggregationsframework:Holen Sie sich das letzte datierte Dokument pro Gruppierungs-ID

Um Ihre Frage direkt zu beantworten, ja, es ist der effizienteste Weg. Aber ich denke, wir müssen klären, warum das so ist.

Wie in Alternativen vorgeschlagen wurde, ist das einzige, worauf die Leute achten, Ihre Ergebnisse zu "sortieren", bevor sie an eine $group übergeben werden Phase und was sie sich ansehen, ist der "timestamp"-Wert, also sollten Sie sicherstellen, dass alles in der "timestamp"-Reihenfolge ist, daher die Form:

db.temperature.aggregate([
    { "$sort": { "station": 1, "dt": -1 } },
    { "$group": {
        "_id": "$station", 
        "result": { "$first":"$dt"}, "t": {"$first":"$t"} 
    }}
])

Und wie bereits erwähnt, möchten Sie natürlich einen Index, der dies widerspiegelt, um die Sortierung effizient zu gestalten:

Allerdings, und das ist der eigentliche Punkt. Was anscheinend von anderen (wenn nicht von Ihnen selbst) übersehen wurde, ist, dass all diese Daten wahrscheinlich bereits eingefügt werden in zeitlicher Reihenfolge, indem jeder Messwert als hinzugefügt aufgezeichnet wird.

Das Schöne daran ist also die _id -Feld (mit einer standardmäßigen ObjectId ) ist bereits in der Reihenfolge "timestamp", da es selbst tatsächlich einen Zeitwert enthält und dies die Aussage ermöglicht:

db.temperature.aggregate([
    { "$group": {
        "_id": "$station", 
        "result": { "$last":"$dt"}, "t": {"$last":"$t"} 
    }}
])

Und es ist Schneller. Wieso den? Nun, Sie müssen keinen Index auswählen (zusätzlicher Code zum Aufrufen), Sie müssen den Index auch nicht zusätzlich zum Dokument "laden".

Wir wissen bereits, dass die Dokumente in Ordnung sind (durch _id ), also das $last Grenzen sind vollkommen gültig. Sie scannen sowieso alles, und Sie könnten auch die _id "bereichsabfragen". Werte als zwischen zwei Daten gleichermaßen gültig.

Das einzig Richtige, was hier zu sagen ist, ist, dass es in der "echten Welt" für Sie praktischer sein könnte, $match zu verwenden zwischen Datumsbereichen, wenn diese Art der Akkumulation durchgeführt wird, im Gegensatz zum Abrufen der "ersten" und "letzten" _id Werte, um einen "Bereich" oder etwas Ähnliches in Ihrer tatsächlichen Nutzung zu definieren.

Wo ist also der Beweis dafür? Nun, es ist ziemlich einfach zu reproduzieren, also habe ich es einfach getan, indem ich einige Beispieldaten generiert habe:

var stations = [ 
    "AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL",
    "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA",
    "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE",
    "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK",
    "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT",
    "VA", "WA", "WV", "WI", "WY"
];


for ( i=0; i<200000; i++ ) {

    var station = stations[Math.floor(Math.random()*stations.length)];
    var t = Math.floor(Math.random() * ( 96 - 50 + 1 )) +50;
    dt = new Date();

    db.temperatures.insert({
        station: station,
        t: t,
        dt: dt
    });

}

Auf meiner Hardware (8-GB-Laptop mit Spinny Disk, was nicht herausragend, aber sicherlich ausreichend ist), zeigt das Ausführen jeder Form der Anweisung deutlich eine bemerkenswerte Pause mit der Version, die einen Index und eine Sortierung verwendet (dieselben Schlüssel im Index wie die Sortieranweisung). Es ist nur eine kleine Pause, aber der Unterschied ist signifikant genug, um es zu bemerken.

Selbst wenn Sie sich die EXPLAIN-Ausgabe ansehen (Version 2.6 und höher, oder tatsächlich in 2.4.9 vorhanden, obwohl nicht dokumentiert), können Sie den Unterschied darin erkennen, obwohl $sort aufgrund des Vorhandenseins eines Index herausoptimiert wird, scheint die benötigte Zeit mit der Indexauswahl und dem anschließenden Laden der indizierten Einträge zu liegen. Einschließlich aller Felder für ein "gedeckt" Indexabfrage macht keinen Unterschied.

Auch für den Datensatz führt das reine Indizieren des Datums und das Sortieren nur nach den Datumswerten zum gleichen Ergebnis. Möglicherweise etwas schneller, aber immer noch langsamer als die natürliche Indexform ohne die Sortierung.

Also, solange Sie zuerst zufrieden "range" können und zuletzt _id Werten, dann ist es wahr, dass die Verwendung des natürlichen Index für den Anzeigenauftrag tatsächlich der effizienteste Weg ist, dies zu tun. Ihr realer Kilometerstand kann variieren, je nachdem, ob dies für Sie praktikabel ist oder nicht, und es könnte am Ende einfach bequemer sein, den Index und die Sortierung nach Datum zu implementieren.

Aber wenn Sie mit der Verwendung von _id zufrieden waren Bereiche oder größer als die "letzte" _id in Ihrer Abfrage, dann vielleicht eine Optimierung, um die Werte zusammen mit Ihren Ergebnissen zu erhalten, sodass Sie diese Informationen tatsächlich speichern und in aufeinanderfolgenden Abfragen verwenden können:

db.temperature.aggregate([
    // Get documents "greater than" the "highest" _id value found last time
    { "$match": {
        "_id": { "$gt":  ObjectId("536076603e70a99790b7845d") }
    }},

    // Do the grouping with addition of the returned field
    { "$group": {
        "_id": "$station", 
        "result": { "$last":"$dt"},
        "t": {"$last":"$t"},
        "lastDoc": { "$last": "$_id" } 
    }}
])

Und wenn Sie die Ergebnisse tatsächlich so "verfolgen", können Sie den maximalen Wert von ObjectId bestimmen aus Ihren Ergebnissen und verwenden Sie es in der nächsten Abfrage.

Trotzdem viel Spaß beim Spielen, aber ja, in diesem Fall ist diese Abfrage der schnellste Weg.