MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

MongoDB :Framework d'agrégation :Récupère le dernier document daté par identifiant de regroupement

Pour répondre directement à votre question, oui c'est le moyen le plus efficace. Mais je pense que nous devons clarifier pourquoi il en est ainsi.

Comme cela a été suggéré dans les alternatives, la seule chose que les gens regardent est de "trier" vos résultats avant de passer à un $group stage et ce qu'ils regardent est la valeur "timestamp", donc vous voudriez vous assurer que tout est dans l'ordre "timestamp", donc d'où la forme :

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

Et comme indiqué, vous voudrez bien sûr qu'un index reflète cela afin de rendre le tri efficace :

Cependant, et c'est le vrai point. Ce qui semble avoir été négligé par d'autres (si ce n'est pas le cas pour vous-même), c'est que toutes ces données sont probablement insérées déjà dans l'ordre du temps, en ce sens que chaque lecture est enregistrée comme ajoutée.

Donc la beauté de ceci est le _id champ (avec un ObjectId par défaut ) est déjà dans l'ordre "horodatage", car il contient lui-même une valeur temporelle, ce qui rend l'instruction possible :

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

Et c'est l'est plus rapide. Pourquoi? Eh bien, vous n'avez pas besoin de sélectionner un index (code supplémentaire à invoquer), vous n'avez pas non plus besoin de "charger" l'index en plus du document.

Nous savons déjà que les documents sont en ordre (par _id ) donc le $last les limites sont parfaitement valides. Vous scannez tout de toute façon, et vous pouvez également "porter" la requête sur le _id valeurs comme également valables entre deux dates.

La seule vraie chose à dire ici, c'est que dans le "monde réel", il pourrait être plus pratique pour vous de $match entre les plages de dates lors de ce type d'accumulation au lieu d'obtenir le "premier" et le "dernier" _id valeurs pour définir une "plage" ou quelque chose de similaire dans votre utilisation réelle.

Où est donc la preuve de cela ? Eh bien, c'est assez facile à reproduire, alors je l'ai fait en générant quelques exemples de données :

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

}

Sur mon matériel (ordinateur portable de 8 Go avec disque spinny, ce qui n'est pas stellaire, mais certainement adéquat), l'exécution de chaque forme de l'instruction montre clairement une pause notable avec la version utilisant un index et un tri (mêmes clés sur l'index que l'instruction de tri). Ce n'est qu'une petite pause, mais la différence est suffisamment importante pour être remarquée.

Même en regardant la sortie d'explication (version 2.6 et supérieure, ou est en fait présente dans 2.4.9 bien que non documentée), vous pouvez voir la différence, bien que le $sort est optimisé en raison de la présence d'un index, le temps nécessaire semble correspondre à la sélection de l'index, puis au chargement des entrées indexées. Y compris tous les champs pour un "couvert" la requête d'index ne fait aucune différence.

Aussi pour mémoire, indexer purement la date et trier uniquement sur les valeurs de date donne le même résultat. Peut-être légèrement plus rapide, mais toujours plus lent que la forme d'index naturel sans le tri.

Donc, tant que vous pouvez "porter" avec plaisir sur le premier et dernier _id valeurs, alors il est vrai que l'utilisation de l'index naturel sur l'ordre d'insertion est en fait le moyen le plus efficace de le faire. Votre kilométrage réel peut varier selon que cela est pratique pour vous ou non et il pourrait simplement être plus pratique de mettre en œuvre l'index et le tri sur la date.

Mais si vous étiez satisfait de l'utilisation de _id plages ou supérieures au "dernier" _id dans votre requête, puis peut-être un ajustement afin d'obtenir les valeurs avec vos résultats afin que vous puissiez en fait stocker et utiliser ces informations dans des requêtes successives :

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

Et si vous "suiviez" réellement les résultats comme ça, vous pouvez déterminer la valeur maximale de ObjectId à partir de vos résultats et utilisez-le dans la requête suivante.

Quoi qu'il en soit, amusez-vous à jouer avec cela, mais encore une fois, oui, dans ce cas, cette requête est le moyen le plus rapide.