MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

MongoDB:Estrutura de agregação:Obtenha o último documento datado por ID de agrupamento


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 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.