sql >> Database teknologi >  >> NoSQL >> MongoDB

MongoDB :Aggregationsramme :Hent sidst daterede dokument pr. grupperings-id

For direkte at besvare dit spørgsmål, ja, det er den mest effektive måde. Men jeg tror, ​​vi skal afklare, hvorfor det er sådan.

Som det blev foreslået i alternativer, er den eneste ting, folk kigger på, at "sortere" dine resultater, før de går videre til en $group fase, og det, de ser på, er "tidsstempel"-værdien, så du vil gerne sikre dig, at alt er i "tidsstempel"-rækkefølge, så derfor formen:

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

Og som sagt vil du selvfølgelig gerne have et indeks, der afspejler det for at gøre sorteringen effektiv:

Men, og dette er den virkelige pointe. Det, der tilsyneladende er blevet overset af andre (hvis ikke for dig selv), er, at alle disse data sandsynligvis er blevet indsat allerede i tidsrækkefølge, idet hver læsning registreres som tilføjet.

Så det smukke ved dette er _id felt ( med en standard ObjectId ) er allerede i "tidsstempel" rækkefølge, da den faktisk selv indeholder en tidsværdi, og dette gør sætningen mulig:

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

Og det er hurtigere. Hvorfor? Nå, du behøver ikke at vælge et indeks (ekstra kode at påkalde), du behøver heller ikke at "indlæse" indekset ud over dokumentet.

Vi ved allerede, at dokumenterne er i orden (af _id ) så $last grænser er helt gyldige. Du scanner alt alligevel, og du kan også "rangere" forespørgsel på _id værdier er lige gyldige mellem to datoer.

Den eneste rigtige ting at sige her er, at i "den virkelige verden" brug, kan det bare være mere praktisk for dig at $match mellem datointervaller, når man laver denne form for akkumulering i modsætning til at få den "første" og "sidste" _id værdier for at definere et "område" eller noget lignende i dit faktiske forbrug.

Så hvor er beviset for dette? Nå, det er ret nemt at reproducere, så jeg gjorde det bare ved at generere nogle eksempeldata:

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

}

På min hardware (8 GB bærbar computer med spinny disk, som ikke er fantastisk, men bestemt tilstrækkelig) viser hver form af erklæringen tydeligt en bemærkelsesværdig pause med versionen ved hjælp af et indeks og en sortering (samme taster på indeks som sorteringserklæringen). Det er kun en mindre pause, men forskellen er betydelig nok til at bemærke.

Selv ser du på forklaringsoutputtet (version 2.6 og nyere, eller faktisk er der i 2.4.9, selvom det ikke er dokumenteret), kan du se forskellen i det, selvom $sort er optimeret ud på grund af tilstedeværelsen af ​​et indeks, den tid, det tager, ser ud til at være med indeksvalg og derefter indlæsning af de indekserede poster. Inkluderer alle felter for en "dækket" indeksforespørgsel gør ingen forskel.

Også for ordens skyld giver ren indeksering af datoen og kun sortering på datoværdierne det samme resultat. Muligvis lidt hurtigere, men stadig langsommere end den naturlige indeksform uden sorteringen.

Så længe du med glæde kan "række" på den første og sidste _id værdier, så er det rigtigt, at brug af det naturlige indeks på indsættelsesrækkefølgen faktisk er den mest effektive måde at gøre dette på. Dit kilometertal i den virkelige verden kan variere alt efter, om dette er praktisk for dig eller ej, og det kan simpelthen ende med at være mere bekvemt at implementere indekset og sortere på datoen.

Men hvis du var tilfreds med at bruge _id områder eller større end den "sidste" _id i din forespørgsel, så måske en tweak for at få værdierne sammen med dine resultater, så du faktisk kan gemme og bruge disse oplysninger i på hinanden følgende forespørgsler:

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

Og hvis du faktisk "følger efter" resultaterne på den måde, kan du bestemme den maksimale værdi af ObjectId fra dine resultater og brug det i den næste forespørgsel.

Under alle omstændigheder, hav det sjovt med at lege med det, men igen Ja, i dette tilfælde er den forespørgsel den hurtigste måde.



  1. Hvorfor returnerer mongoose-modellens hasOwnProperty falsk, når egenskaben eksisterer?

  2. MongoDB $arrayElemAt

  3. MongoDB deleteMany()

  4. Hvorfor frarådes KEYS at blive brugt i Redis?