Tout d'abord, montrons vos données de manière à ce que les gens puissent les utiliser et produire le résultat souhaité :
{ value: 1,
_id: ObjectId('5cb9ea0c75c61525e0176f96'),
name: 'Test',
category: 'Development',
subcategory: 'Programming Languages',
status: 'Supported',
description: 'Test',
change:
[ { version: 1,
who: 'ATL User',
when: new Date('2019-04-19T15:30:39.912Z'),
what: 'Item Creation' },
{ version: 2,
who: 'ATL Other User',
when: new Date('2019-04-19T15:31:39.912Z'),
what: 'Name Change' } ],
}
Notez que le "when"
les dates sont en fait différentes donc il y aura un $max
valeur et ils ne sont pas simplement les mêmes. Nous pouvons maintenant passer en revue les cas
Cas 1 - Récupérer le "singulier" $max
valeur
Le cas de base ici est d'utiliser le $arrayElemAt
et $indexOfArray
opérateurs pour renvoyer le $max
correspondant
valeur :
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$arrayElemAt": [
"$change",
{ "$indexOfArray": [
"$change.when",
{ "$max": "$change.when" }
]}
]
}
}}
])
Renvoie :
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : {
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
}
Fondamentalement, le "$max": "$change.when"
renvoie la valeur qui est le "maximum" à partir de ce tableau de valeurs. Vous trouvez ensuite "l'index" correspondant de ce tableau de valeurs via $indexOfArray
qui renvoie le premier index correspondant trouvé. Cette position "index" (à partir en fait d'un tableau de "when"
valeurs transposées dans le même ordre) est ensuite utilisé avec $arrayElemAt
pour extraire "l'objet entier" du "change"
tableau à la position d'index spécifiée.
Cas 2 - Renvoie le "multiple" $max
entrées
À peu près la même chose avec $max
, sauf que cette fois nous $filter
pour retourner le multiple "possible" valeurs correspondant à $max
valeur :
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$filter": {
"input": "$change",
"cond": {
"$eq": [ "$$this.when", { "$max": "$change.when" } ]
}
}
}
}}
])
Renvoie :
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
]
}
Ainsi, le $max
est bien sûr le même mais cette fois la valeur singulière renvoyée par cet opérateur est utilisée dans un $eq
comparaison dans $filter
. Cela inspecte chaque élément du tableau et regarde le courant "when"
valeur ( "$$this.when"
). Où "égal" alors l'élément est retourné.
Fondamentalement la même que la première approche mais à l'exception que $filter
autorise "multiple" éléments à restituer. Donc tout avec le même $max
valeur.
Cas 3 - Pré-trier le contenu du tableau.
Maintenant, vous remarquerez peut-être que dans l'exemple de données que j'ai inclus (adapté des vôtres mais avec une date "max" réelle), la valeur "max" est en fait la dernière valeur dans le tableau. Cela peut se produire naturellement car $push
( par défaut ) "ajoute" à la fin du contenu du tableau existant. Donc "plus récent" les entrées auront tendance à être à la fin du tableau.
C'est bien sûr la valeur par défaut comportement, mais il y a de bonnes raisons pour lesquelles vous "pouvez" veux changer ça. Bref le meilleur moyen d'obtenir le "plus récent" entrée de tableau est en fait de retourner le premier élément du tableau.
Tout ce que vous avez à faire est de vous assurer que le "plus récent" est en fait ajouté en premier plutôt que dernier . Il existe deux approches :
-
Utilisez
$position
pour "pré-mettre en attente" les éléments du tableau : Il s'agit d'un simple modificateur de$push
en utilisant le0
poste afin de toujours ajouter au devant :db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [{ "version": 3, "who": "ATL User", "when": new Date(), "what": "Another change" }], "$position": 0 } }} )
Cela changerait le document en :
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96"), "value" : 1, "name" : "Test", "category" : "Development", "subcategory" : "Programming Languages", "status" : "Supported", "description" : "Test", "change" : [ { "version" : 3, "who" : "ATL User", "when" : ISODate("2019-04-20T02:40:30.024Z"), "what" : "Another change" }, { "version" : 1, "who" : "ATL User", "when" : ISODate("2019-04-19T15:30:39.912Z"), "what" : "Item Creation" }, { "version" : 2, "who" : "ATL Other User", "when" : ISODate("2019-04-19T15:31:39.912Z"), "what" : "Name Change" } ] }
Notez que cela vous obligerait à aller "inverser" tous les éléments de votre tableau au préalable afin que le "plus récent" soit déjà à l'avant afin que l'ordre soit maintenu. Heureusement, cela est quelque peu couvert dans la deuxième approche...
-
Utilisez
$sort
pour modifier les documents dans l'ordre sur chaque$push
: Et c'est l'autre modificateur qui "re-trie" de manière atomique à chaque nouvel ajout d'élément. L'utilisation normale est fondamentalement la même avec tous les nouveaux éléments à$each
comme ci-dessus, ou même juste un tableau "vide" afin d'appliquer le$sort
aux données existantes uniquement :db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [], "$sort": { "when": -1 } } }} )
Résultats en :
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96"), "value" : 1, "name" : "Test", "category" : "Development", "subcategory" : "Programming Languages", "status" : "Supported", "description" : "Test", "change" : [ { "version" : 3, "who" : "ATL User", "when" : ISODate("2019-04-20T02:40:30.024Z"), "what" : "Another change" }, { "version" : 2, "who" : "ATL Other User", "when" : ISODate("2019-04-19T15:31:39.912Z"), "what" : "Name Change" }, { "version" : 1, "who" : "ATL User", "when" : ISODate("2019-04-19T15:30:39.912Z"), "what" : "Item Creation" } ] }
Cela peut prendre une minute pour comprendre pourquoi vous voulez
$push
afin de$sort
un tableau comme celui-ci, mais l'intention générale est lorsque des modifications peuvent être apportées à un tableau qui "modifie" une propriété comme uneDate
valeur triée et vous utiliseriez une telle instruction pour refléter ces modifications. Ou bien ajoutez simplement de nouveaux éléments avec le$sort
et laissez faire.
Alors pourquoi "stocker" le tableau commandé comme ça? Comme mentionné précédemment, vous voulez le premier élément comme "le plus récent" , puis la requête à renvoyer qui devient simplement :
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true }
},
{ "change": { "$slice": 1 } }
)
Renvoie :
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 3,
"who" : "ATL User",
"when" : ISODate("2019-04-20T02:40:30.024Z"),
"what" : "Another change"
}
]
}
Donc le $slice
peut ensuite être utilisé uniquement pour extraire des éléments de tableau par des index connus. Techniquement, vous pouvez simplement utiliser -1
là pour retourner le dernier élément du tableau de toute façon, mais la réorganisation où le plus récent est le premier permet d'autres choses comme la confirmation que la dernière modification a été faite par un certain utilisateur, et/ou d'autres conditions comme une contrainte de plage de dates. c'est-à-dire :
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true },
"change.0.who": "ATL User",
"change.0.when": { "$gt": new Date("2018-04-01") }
},
{ "change": { "$slice": 1 } }
)
Notant ici que quelque chose comme "change.-1.when"
est une déclaration illégale, c'est pourquoi nous réorganisons le tableau afin que vous puissiez utiliser le legal 0
pour premier au lieu de -1
pour dernier .
Conclusion
Il y a donc plusieurs choses que vous pouvez faire, soit en utilisant l'approche d'agrégation pour filtrer le contenu du tableau, soit via des formulaires de requête standard après avoir apporté quelques modifications à la manière dont les données sont réellement stockées. Lequel utiliser dépend de votre propre situation, mais il convient de noter que l'un des formulaires de requête standard s'exécutera nettement plus rapidement que toute manipulation via le cadre d'agrégation ou tout opérateur calculé.