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

MongoDB:Framework di aggregazione:ottieni l'ultimo documento datato per ID di raggruppamento

Per rispondere direttamente alla tua domanda, sì, è il modo più efficiente. Ma penso che dobbiamo chiarire perché è così.

Come suggerito in alternative, l'unica cosa che le persone stanno guardando è "ordinare" i risultati prima di passare a un $group stage e quello che stanno guardando è il valore "timestamp", quindi dovresti assicurarti che tutto sia nell'ordine "timestamp", quindi da qui il modulo:

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

E, come affermato, ovviamente vorrai che un indice lo rifletta per rendere efficiente l'ordinamento:

Tuttavia, e questo è il vero punto. Quello che sembra essere stato trascurato dagli altri (se non è così per te stesso) è che tutti questi dati sono probabilmente inseriti già in ordine di tempo, in quanto ogni lettura viene registrata come aggiunta.

Quindi il bello di questo è il _id campo ( con un ObjectId predefinito ) è già nell'ordine "timestamp", poiché contiene effettivamente un valore temporale e ciò rende possibile l'affermazione:

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

E lo è Più veloce. Come mai? Ebbene non è necessario selezionare un indice (codice aggiuntivo da invocare) inoltre non è necessario "caricare" l'indice oltre al documento.

Sappiamo già che i documenti sono in ordine ( per _id ) quindi $last i confini sono perfettamente validi. Stai scansionando tutto comunque e potresti anche "range" query su _id valori ugualmente validi tra due date.

L'unica cosa reale da dire qui è che nell'utilizzo del "mondo reale", potrebbe essere più pratico per te $match tra intervalli di date quando si esegue questo tipo di accumulazione invece di ottenere il "primo" e l'"ultimo" _id valori per definire un "intervallo" o qualcosa di simile nell'utilizzo effettivo.

Allora dov'è la prova di questo? Bene, è abbastanza facile da riprodurre, quindi l'ho fatto semplicemente generando alcuni dati di esempio:

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

}

Sul mio hardware (computer portatile da 8 GB con disco spinny, che non è eccezionale, ma sicuramente adeguato) l'esecuzione di ogni forma dell'istruzione mostra chiaramente una notevole pausa con la versione che utilizza un indice e un ordinamento (stesse chiavi sull'indice dell'istruzione di ordinamento). È solo una piccola pausa, ma la differenza è abbastanza significativa da poter essere notata.

Anche guardando l'output di spiegazione (versione 2.6 e successive, o in realtà è presente nella 2.4.9 sebbene non documentato) puoi vedere la differenza in questo, sebbene il $sort è ottimizzato per la presenza di un indice, il tempo impiegato risulta essere quello della selezione dell'indice e quindi del caricamento delle voci indicizzate. Compresi tutti i campi per un "coperto" la query sull'indice non fa differenza.

Anche per la cronaca, la semplice indicizzazione della data e l'ordinamento solo sui valori della data danno lo stesso risultato. Forse leggermente più veloce, ma comunque più lento del modulo indice naturale senza l'ordinamento.

A patto che tu possa felicemente "portare a distanza" il primo e ultimo _id valori, allora è vero che l'uso dell'indice naturale nell'ordine di inserimento è in realtà il modo più efficiente per farlo. Il tuo chilometraggio nel mondo reale può variare a seconda che questo sia pratico per te o meno e potrebbe semplicemente risultare più conveniente implementare l'indice e l'ordinamento in base alla data.

Ma se fossi soddisfatto dell'utilizzo di _id intervalli o maggiore dell'"ultimo" _id nella tua query, quindi forse una modifica per ottenere i valori insieme ai risultati in modo da poter effettivamente archiviare e utilizzare tali informazioni nelle query successive:

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

E se in realtà stavi "seguendo" i risultati del genere, puoi determinare il valore massimo di ObjectId dai risultati e utilizzalo nella query successiva.

Comunque, divertiti a giocarci, ma ancora una volta Sì, in questo caso quella query è il modo più veloce.