Il concetto di base qui è che è necessario il framework di aggregazione per applicare condizioni per "filtrare" gli elementi dell'array alle condizioni. A seconda della versione disponibile ci sono diverse tecniche che possono essere applicate.
In tutti i casi questo è il risultato:
{
"_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 e versioni successive
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
]
}
}
}
}
}}
])
Questo è il modo più efficiente che sfrutta $replaceRoot
che ci consente di dichiarare le variabili da utilizzare nella struttura "sostituita" utilizzando $let
. Il vantaggio principale qui è che ciò richiede solo "due" fasi della pipeline.
Per abbinare il contenuto dell'array, utilizzi $filter
dove applichi $eq
operazione logica per testare il valore di "sender"
. Se la condizione corrisponde, vengono restituite solo le voci dell'array corrispondenti.
Utilizzando lo stesso $filter
in modo che vengano considerate solo le voci "mittente" corrispondenti, vogliamo quindi applicare $max
sopra l'elenco "filtrato" ai valori in "datetime"
. Il $max
]5
value è la data "ultima" in base alle condizioni.
Vogliamo questo valore in modo da poter confrontare in seguito i risultati restituiti dall'array "filtrato" con questo "maxDate". Che è ciò che accade all'interno di "in"
blocco di $let
dove le due "variabili" dichiarate in precedenza per il contenuto filtrato e "maxDate" vengono nuovamente applicate a $filter
per restituire quello che dovrebbe essere l'unico valore che soddisfa entrambe le condizioni avendo anche la "data più recente".
Poiché desideri solo "un" risultato, utilizziamo $arrayElemAt
per utilizzare il valore anziché l'array.
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
]
}
}}
])
Questo è fondamentalmente lo stesso processo descritto, ma senza $replaceRoot
fase della pipeline, dobbiamo applicare in due $project
fasi. La ragione di ciò è che abbiamo bisogno del "valore calcolato" da "maxDate" per farlo $filter
, e non è possibile farlo in un'istruzione composta, quindi dividiamo invece le pipeline. Ciò ha un piccolo impatto sul costo complessivo dell'operazione.
In MongoDB da 2.6 a 3.0 possiamo usare la maggior parte della tecnica qui ad eccezione di $arrayElemAt
e accettare il risultato "array" con una singola voce o inserire un $unwind
fase per affrontare quella che ora dovrebbe essere un'unica voce.
Versioni precedenti di 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" }
}}
])
Anche se sembra breve, questa è di gran lunga l'operazione più costosa. Qui devi utilizzare $unwind
per applicare le condizioni agli elementi dell'array. Questo è un processo molto costoso in quanto produce una copia di ogni documento per ogni voce dell'array, ed è sostanzialmente sostituito dai moderni operatori che lo evitano in caso di "filtraggio".
Il secondo $match
stage qui scarta tutti gli elementi (ora "documenti") che non corrispondono alla condizione "mittente". Quindi applichiamo un $sort
per mettere la data "ultima" in cima a ogni documento con il _id
, da qui le due chiavi di "ordinamento".
Infine applichiamo $group
per fare riferimento solo al documento originale, utilizzando $first
come accumulatore per ottenere l'elemento che è "in cima".