sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB :Aggregationsramverk :Få senast daterade dokument per grupperings-ID

För att direkt svara på din fråga, ja, det är det mest effektiva sättet. Men jag tror att vi måste klargöra varför det är så.

Som föreslogs i alternativen är det enda folk tittar på att "sortera" dina resultat innan de går vidare till en $group scenen och vad de tittar på är "tidsstämpel"-värdet, så du skulle vilja se till att allt är i "tidsstämpel"-ordning, så därav formuläret:

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

Och som sagt kommer du naturligtvis att vilja ha ett index som återspeglar det för att göra sorteringen effektiv:

Men, och detta är den verkliga poängen. Det som verkar ha förbisetts av andra (om inte så för dig själv) är att all denna data sannolikt har infogats redan i tidsordning, genom att varje avläsning registreras som tillagd.

Så det fina med detta är _id fält ( med en standard ObjectId ) är redan i "tidsstämpel"-ordning, eftersom den faktiskt innehåller ett tidsvärde och detta gör påståendet möjligt:

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

Och det är snabbare. Varför? Tja, du behöver inte välja ett index (tilläggskod att anropa) du behöver inte heller "ladda" indexet utöver dokumentet.

Vi vet redan att dokumenten är i ordning (av _id ) så $last gränser är helt giltiga. Du skannar allt ändå, och du kan också "range"-fråga på _id värden är lika giltiga mellan två datum.

Det enda verkliga att säga här är att i "verkliga världen"-användning kan det bara vara mer praktiskt för dig att $match mellan datumintervall när man gör den här typen av ackumulering i motsats till att få "första" och "sista" _id värden för att definiera ett "intervall" eller något liknande i din faktiska användning.

Så var är beviset för detta? Tja, det är ganska lätt att reproducera, så jag gjorde det bara genom att generera några exempeldata:

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 hårdvara (8 GB bärbar dator med spinny disk, som inte är fantastisk, men säkerligen tillräcklig) visar varje form av uttalandet tydligt en anmärkningsvärd paus med versionen som använder ett index och en sort (samma nycklar på index som sort-satsen). Det är bara en mindre paus, men skillnaden är tillräckligt stor för att märka.

Även om du tittar på förklara-utgången (version 2.6 och senare, eller faktiskt finns i 2.4.9 men inte dokumenterad) kan du se skillnaden i det, även om $sort optimeras ut på grund av närvaron av ett index, den tid det tar verkar vara med indexval och sedan laddning av de indexerade posterna. Inkluderar alla fält för en "täckt" indexfrågan gör ingen skillnad.

Också för protokollet, en ren indexering av datumet och enbart sortering på datumvärdena ger samma resultat. Möjligen något snabbare, men ändå långsammare än den naturliga indexformen utan sorteringen.

Så länge du gärna kan "rangera" på första och sista _id värden, så är det sant att användningen av det naturliga indexet på insättningsordningen faktiskt är det mest effektiva sättet att göra detta. Din verkliga körsträcka kan variera beroende på om detta är praktiskt för dig eller inte, och det kan helt enkelt bli enklare att implementera indexet och sortera på datumet.

Men om du var nöjd med att använda _id intervall eller större än det "senaste" _id i din fråga, sedan kanske en justering för att få värdena tillsammans med dina resultat så att du faktiskt kan lagra och använda den informationen i på varandra följande frågor:

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

Och om du faktiskt "följde på" resultaten så kan du bestämma maxvärdet för ObjectId från dina resultat och använd den i nästa fråga.

Hur som helst, ha kul att leka med det, men igen Ja, i det här fallet är den frågan det snabbaste sättet.



  1. MongoDB hitta()

  2. Hur får jag Spring-Data-MongoDB att validera mina objekt?

  3. MongoDB $setUnion

  4. fel:typparameter 'D' måste användas som typparameter för någon lokal typ