Le concept de base ici est que vous avez besoin du cadre d'agrégation afin d'appliquer des conditions pour "filtrer" les éléments du tableau aux conditions. Selon la version disponible, différentes techniques peuvent être appliquées.
Dans tous les cas voici le résultat :
{
"_id" : ObjectId("593921425ccc8150f35e7664"),
"user1" : 1,
"user2" : 4,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-09T10:04:50Z"),
"body" : "hiii 1"
}
}
{
"_id" : ObjectId("593921425ccc8150f35e7663"),
"user1" : 1,
"user2" : 3,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-10T10:04:50Z"),
"body" : "hiii 2"
}
}
{
"_id" : ObjectId("593921425ccc8150f35e7662"),
"user1" : 1,
"user2" : 2,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-08T10:04:50Z"),
"body" : "hiii 0"
}
}
MongoDB 3.4 et supérieur
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$replaceRoot": {
"newRoot": {
"$let": {
"vars": {
"messages": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"maxDate": {
"$max": {
"$map": {
"input": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"as": "m",
"in": "$$m.datetime"
}
}
}
},
"in": {
"_id": "$_id",
"user1": "$user1",
"user2": "$user2",
"messages": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$messages",
"as": "m",
"cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
}},
0
]
}
}
}
}
}}
])
C'est le moyen le plus efficace qui tire parti de $replaceRoot
qui nous permet de déclarer des variables à utiliser dans la structure "remplacée" en utilisant $let
. Le principal avantage ici est que cela ne nécessite que "deux" étapes de pipeline.
Afin de faire correspondre le contenu du tableau, vous utilisez $filter
où vous appliquez le $eq
opération logique pour tester la valeur de "sender"
. Lorsque la condition correspond, seules les entrées de tableau correspondantes sont renvoyées.
Utiliser le même $filter
afin que seules les entrées "expéditeur" correspondantes soient prises en compte, nous voulons ensuite appliquer $max
sur la liste "filtrée" aux valeurs dans "datetime"
. Le $max
]5
la valeur est la "dernière" date selon les conditions.
Nous voulons cette valeur afin de pouvoir comparer ultérieurement les résultats renvoyés par le tableau "filtré" à ce "maxDate". C'est ce qui se passe dans le "in"
bloc de $let
où les deux "variables" déclarées précédemment pour le contenu filtré et le "maxDate" sont à nouveau appliqués à $filter
afin de renvoyer ce qui devrait être la seule valeur remplissant les deux conditions ayant également la "dernière date".
Puisque vous ne voulez qu'"un" résultat, nous utilisons $arrayElemAt
pour utiliser la valeur plutôt que le tableau.
MongoDB 3.2
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$project": {
"user1": 1,
"user2": 1,
"messages": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"maxDate": {
"$max": {
"$map": {
"input": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"as": "m",
"in": "$$m.datetime"
}
}
}
}},
{ "$project": {
"user1": 1,
"user2": 1,
"messages": {
"$arrayElemAt":[
{ "$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
}},
0
]
}
}}
])
Il s'agit essentiellement du même processus que celui décrit, mais sans le $replaceRoot
étape du pipeline, nous devons postuler en deux $project
étapes. La raison en est que nous avons besoin de la "valeur calculée" de "maxDate" afin de faire ce dernier $filter
, et il n'est pas possible de le faire dans une instruction composée, donc à la place, nous divisons les pipelines. Cela a un faible impact sur le coût global de l'opération.
Dans MongoDB 2.6 à 3.0, nous pouvons utiliser la plupart des techniques ici, à l'exception de $arrayElemAt
et soit accepter le résultat "tableau" avec une seule entrée ou mettre dans un $unwind
étape pour traiter ce qui devrait maintenant être une entrée unique.
Versions antérieures de MongoDB
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$unwind": "$messages" },
{ "$match": { "messages.sender": 1 } },
{ "$sort": { "_id": 1, "messages.datetime": -1 } },
{ "$group": {
"_id": "$_id",
"user1": { "$first": "$user1" },
"user2": { "$first": "$user2" },
"messages": { "$first": "$messages" }
}}
])
Bien qu'elle paraisse brève, c'est de loin l'opération la plus coûteuse. Ici, vous devez utiliser $unwind
afin d'appliquer les conditions aux éléments du tableau. C'est un processus très coûteux car il produit une copie de chaque document pour chaque entrée de tableau, et est essentiellement remplacé par les opérateurs modernes qui évitent cela dans le cas du "filtrage".
Le deuxième $match
stage ici supprime tous les éléments (maintenant "documents" ) qui ne correspondaient pas à la condition "sender". Ensuite, nous appliquons un $sort
afin de mettre la "dernière" date en haut pour chaque document par le _id
, d'où les deux clés de "tri".
Enfin nous appliquons $group
afin de simplement se référer au document original, en utilisant $first
comme accumulateur pour obtenir l'élément qui est "sur le dessus".