sql >> Database >  >> NoSQL >> MongoDB

MongoDB:Aggregatieraamwerk:Laatst gedateerd document per groeperings-ID ophalen

Om uw vraag direct te beantwoorden, ja, dit is de meest efficiënte manier. Maar ik denk dat we moeten verduidelijken waarom dit zo is.

Zoals werd gesuggereerd in alternatieven, is het enige waar mensen naar kijken, uw resultaten "sorteren" voordat ze worden doorgegeven aan een $group stadium en waar ze naar kijken is de "timestamp" -waarde, dus je zou ervoor willen zorgen dat alles in de "timestamp" -volgorde staat, dus vandaar de vorm:

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

En zoals gezegd wil je natuurlijk een index die dat weerspiegelt om de sortering efficiënt te maken:

Echter, en dit is het echte punt. Wat anderen over het hoofd hebben gezien (als dat niet het geval is voor uzelf), is dat al deze gegevens waarschijnlijk reeds zijn ingevoegd in tijdsvolgorde, in die zin dat elke meting wordt geregistreerd als toegevoegd.

Dus het mooie hiervan is de _id veld ( met een standaard ObjectId ) staat al in "timestamp"-volgorde, omdat het zelf eigenlijk een tijdwaarde bevat en dit maakt de verklaring mogelijk:

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

En het is is sneller. Waarom? Welnu, u hoeft geen index te selecteren (extra code om aan te roepen), u hoeft ook de index niet naast het document te "laden".

We weten al dat de documenten in orde zijn ( door _id ) dus de $last grenzen zijn volkomen geldig. Je bent toch alles aan het scannen, en je zou ook een zoekopdracht kunnen "bereiken" op de _id waarden even geldig voor tussen twee datums.

Het enige echte wat je hier kunt zeggen, is dat het in 'echte wereld'-gebruik misschien praktischer voor je is om $match tussen datumbereiken bij het doen van dit soort accumulatie in tegenstelling tot het verkrijgen van de "eerste" en "laatste" _id waarden om een ​​"bereik" of iets dergelijks in uw werkelijke gebruik te definiëren.

Dus waar is het bewijs hiervan? Nou, het is vrij eenvoudig te reproduceren, dus ik deed het gewoon door wat voorbeeldgegevens te genereren:

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

}

Op mijn hardware (8 GB laptop met spinny disk, die niet geweldig is, maar zeker voldoende) laat het uitvoeren van elke vorm van het statement duidelijk een opmerkelijke pauze zien met de versie die een index en een sort gebruikt (dezelfde sleutels op index als het sort statement). Het is maar een kleine pauze, maar het verschil is significant genoeg om op te merken.

Zelfs als je kijkt naar de uitvoer van de uitleg (versie 2.6 en hoger, of is er eigenlijk in 2.4.9, hoewel niet gedocumenteerd), kun je het verschil daarin zien, hoewel de $sort is geoptimaliseerd vanwege de aanwezigheid van een index, de tijd die nodig is om de index te selecteren en vervolgens de geïndexeerde items te laden, lijkt te zijn. Inclusief alle velden voor een "overdekt" indexquery maakt geen verschil.

Ook voor de goede orde, puur indexeren van de datum en alleen sorteren op de datumwaarden geeft hetzelfde resultaat. Mogelijk iets sneller, maar nog steeds langzamer dan de natuurlijke indexvorm zonder de soort.

Dus zolang je maar gelukkig kunt "bereiken" op de eerste en laatste _id waarden, dan is het waar dat het gebruik van de natuurlijke index op de invoegvolgorde eigenlijk de meest efficiënte manier is om dit te doen. Uw werkelijke kilometers kunnen variëren, afhankelijk van of dit praktisch voor u is of niet, en het kan eenvoudigweg handiger zijn om de index te implementeren en op de datum te sorteren.

Maar als u tevreden was met het gebruik van _id bereiken of groter dan de "laatste" _id in uw zoekopdracht, dan misschien één aanpassing om de waarden samen met uw resultaten te krijgen, zodat u die informatie in feite kunt opslaan en gebruiken in opeenvolgende zoekopdrachten:

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" } 
    }}
])

En als u de resultaten daadwerkelijk op die manier "volgde", dan kunt u de maximale waarde van ObjectId bepalen uit uw resultaten en gebruik deze in de volgende zoekopdracht.

Hoe dan ook, veel plezier ermee, maar nogmaals Ja, in dit geval is die vraag de snelste manier.



  1. Mongodb opvragen van golang met behulp van de _id die is opgeslagen in een array

  2. Hoe gebruik je redis PUBLISH/SUBSCRIBE met nodejs om klanten op de hoogte te stellen wanneer gegevenswaarden veranderen?

  3. Wat is het standaard databasepad voor MongoDB?

  4. Hoe externe sleutels af te dwingen in NoSql-databases (MongoDB)?