sql >> Base de Datos >  >> NoSQL >> MongoDB

MongoDB:marco de agregación:obtenga el último documento fechado por ID de agrupación

Para responder directamente a su pregunta, sí, es la forma más eficiente. Pero sí creo que debemos aclarar por qué esto es así.

Como se sugirió en las alternativas, lo que la gente busca es "ordenar" los resultados antes de pasarlos a un $group etapa y lo que están viendo es el valor de "marca de tiempo", por lo que querrá asegurarse de que todo esté en orden de "marca de tiempo", por lo tanto, la forma:

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

Y como se indicó, por supuesto, querrá un índice que refleje eso para que la clasificación sea eficiente:

Sin embargo, y este es el punto real. Lo que parece haber sido pasado por alto por otros (si no es así por usted mismo) es que todos estos datos probablemente ya se hayan insertado ya. en orden de tiempo, en el que cada lectura se registra como agregada.

Así que la belleza de esto es el _id campo (con un ObjectId predeterminado ) ya está en orden de "marca de tiempo", ya que en realidad contiene un valor de tiempo y esto hace posible la declaración:

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

Y es es más rápido. ¿Por qué? Bueno, no necesita seleccionar un índice (código adicional para invocar), tampoco necesita "cargar" el índice además del documento.

Ya sabemos que los documentos están en orden ( por _id ) por lo que $last Los límites son perfectamente válidos. De todos modos, está escaneando todo, y también podría realizar una consulta de "rango" en el _id valores igualmente válidos entre dos fechas.

Lo único real que se puede decir aquí es que, en el uso del "mundo real", podría ser más práctico para usted $match entre rangos de fechas al hacer este tipo de acumulación en lugar de obtener el "primero" y el "último" _id valores para definir un "rango" o algo similar en su uso real.

Entonces, ¿dónde está la prueba de esto? Bueno, es bastante fácil de reproducir, así que lo hice generando algunos datos de muestra:

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

}

En mi hardware (portátil de 8 GB con disco giratorio, que no es estelar, pero sin duda adecuado), la ejecución de cada forma de la declaración muestra claramente una pausa notable con la versión que usa un índice y una ordenación (las mismas claves en el índice que la declaración de ordenación). Es solo una pequeña pausa, pero la diferencia es lo suficientemente significativa como para notarlo.

Incluso mirando el resultado de la explicación (versión 2.6 y superior, o en realidad está allí en 2.4.9 aunque no está documentado) puede ver la diferencia en eso, aunque el $sort está optimizado debido a la presencia de un índice, el tiempo necesario parece ser con la selección del índice y luego cargando las entradas indexadas. Incluyendo todos los campos para un "cubierto" consulta de índice no hace ninguna diferencia.

También para el registro, indexar puramente la fecha y solo ordenar los valores de fecha da el mismo resultado. Posiblemente un poco más rápido, pero aún más lento que la forma de índice natural sin ordenar.

Entonces, siempre que pueda "alcanzar" felizmente en el primero y último _id valores, entonces es cierto que usar el índice natural en el pedido de inserción es en realidad la forma más eficiente de hacerlo. Su millaje en el mundo real puede variar en función de si esto es práctico para usted o no, y simplemente podría resultar más conveniente implementar el índice y la clasificación por fecha.

Pero si estaba satisfecho con el uso de _id rangos o mayores que el "último" _id en su consulta, entonces tal vez un ajuste para obtener los valores junto con sus resultados para que pueda almacenar y usar esa información en consultas sucesivas:

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

Y si en realidad estaba "siguiendo" los resultados de esa manera, entonces puede determinar el valor máximo de ObjectId de sus resultados y úselo en la próxima consulta.

De todos modos, diviértete jugando con eso, pero de nuevo Sí, en este caso esa consulta es la forma más rápida.