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.