Para responder diretamente à sua pergunta, sim, é a maneira mais eficiente. Mas acho que precisamos esclarecer por que isso é assim.
Como foi sugerido nas alternativas, a única coisa que as pessoas estão olhando é "classificar" seus resultados antes de passar para um
$group
stage e o que eles estão vendo é o valor "timestamp", então você deve ter certeza de que tudo está na ordem "timestamp", portanto, o formulário:db.temperature.aggregate([
{ "$sort": { "station": 1, "dt": -1 } },
{ "$group": {
"_id": "$station",
"result": { "$first":"$dt"}, "t": {"$first":"$t"}
}}
])
E, como afirmado, é claro que você deseja que um índice reflita isso para tornar a classificação eficiente:
No entanto, e este é o ponto real. O que parece ter sido ignorado por outros (se não por você) é que todos esses dados provavelmente estão sendo inseridos já em ordem de tempo, em que cada leitura é registrada como adicionada.
Então, a beleza disso é o
_id
campo (com um padrão ObjectId
) já está na ordem "timestamp", pois na verdade contém um valor de tempo e isso torna a instrução possível:db.temperature.aggregate([
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"}, "t": {"$last":"$t"}
}}
])
E é é mais rápido. Por quê? Bem, você não precisa selecionar um índice (código adicional para invocar), você também não precisa "carregar" o índice além do documento.
Já sabemos que os documentos estão em ordem ( por
_id
) para que o $last
limites são perfeitamente válidos. Você está verificando tudo de qualquer maneira e também pode "intervar" a consulta no _id
valores como igualmente válidos para entre duas datas. A única coisa real a dizer aqui é que no uso do "mundo real", pode ser mais prático para você
$match
entre intervalos de datas ao fazer esse tipo de acumulação em vez de obter o "primeiro" e o "último" _id
valores para definir um "intervalo" ou algo semelhante em seu uso real. Então, onde está a prova disso? Bem, é bastante fácil de reproduzir, então eu fiz isso gerando alguns dados de exemplo:
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
});
}
No meu hardware (laptop de 8 GB com disco giratório, que não é estelar, mas certamente adequado), a execução de cada forma da instrução mostra claramente uma pausa notável com a versão usando um índice e uma classificação (as mesmas chaves no índice que a instrução de classificação). É apenas uma pequena pausa, mas a diferença é significativa o suficiente para ser notada.
Mesmo olhando para a saída de explicação ( versão 2.6 e superior, ou realmente existe na 2.4.9 embora não documentada ), você pode ver a diferença nisso, embora o
$sort
é otimizado devido à presença de um índice, o tempo gasto parece ser com a seleção do índice e, em seguida, o carregamento das entradas indexadas. Incluindo todos os campos para um "coberto" consulta de índice não faz diferença. Também para o registro, indexar puramente a data e classificar apenas os valores de data fornece o mesmo resultado. Possivelmente um pouco mais rápido, mas ainda mais lento do que a forma de índice natural sem a classificação.
Então, contanto que você possa "variar" no primeiro e último
_id
valores, então é verdade que usar o índice natural no pedido de inserção é realmente a maneira mais eficiente de fazer isso. Sua milhagem no mundo real pode variar se isso é prático para você ou não e pode simplesmente acabar sendo mais conveniente implementar o índice e a classificação na data. Mas se você gostou de usar
_id
intervalos ou maiores que o "último" _id
em sua consulta, talvez um ajuste para obter os valores junto com seus resultados para que você possa armazenar e usar essas informações em consultas sucessivas: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 você estava realmente "acompanhando" os resultados assim, você pode determinar o valor máximo de
ObjectId
dos seus resultados e use-o na próxima consulta. De qualquer forma, divirta-se brincando com isso, mas novamente Sim, neste caso essa consulta é o caminho mais rápido.