Det grundläggande konceptet här är att du behöver aggregeringsramverket för att tillämpa villkor för att "filtrera" arrayelementen till villkoren. Beroende på tillgänglig version finns det olika tekniker som kan tillämpas.
I alla fall är detta resultatet:
{
"_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 och senare
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
]
}
}
}
}
}}
])
Detta är det mest effektiva sättet att dra fördel av $replaceRoot
som tillåter oss att deklarera variabler att använda i den "ersatta" strukturen med
code>$let
. Den största fördelen här är att detta endast kräver "två" pipelinesteg.
För att matcha arrayinnehållet använder du $filter
där du använder $eq
logisk operation för att testa värdet på "sender"
. Om villkoret matchar, returneras endast de matchande arrayposterna.
Använder samma $filter
så att endast de matchande "avsändare"-posterna beaktas vill vi då tillämpa $max
över den "filtrerade" listan till värdena i "datetime"
. $max
]5
värde är det "senaste" datumet enligt villkoren.
Vi vill ha detta värde så att vi senare kan jämföra de returnerade resultaten från den "filtrerade" arrayen med denna "maxDate". Vilket är vad som händer inuti "in"
block av $let
där de två "variabler" som deklarerats tidigare för det filtrerade innehållet och "maxDate" återigen tillämpas på $filter
för att returnera det som borde vara det enda värdet som uppfyllde båda villkoren med "senaste datumet".
Eftersom du bara vill ha "ett" resultat använder vi $arrayElemAt
för att använda värdet istället för arrayen.
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
]
}
}}
])
Detta är i princip samma process som beskrivs, men utan $ replaceRoot
pipeline-stadiet måste vi ansöka i två $project
etapper. Anledningen till detta är att vi behöver det "beräknade värdet" från "maxDate" för att göra den sista $filter
, och det är inte tillgängligt att göra i ett sammansatt uttalande, så istället delar vi upp pipelines. Detta har en liten inverkan på den totala kostnaden för operationen.
I MongoDB 2.6 till 3.0 kan vi använda det mesta av tekniken här förutom $arrayElemAt
och antingen acceptera "array"-resultatet med en enda post eller lägg in en $unwind
skede för att ta itu med vad som nu borde vara en enda post.
MongoDB tidigare versioner
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" }
}}
])
Även om det ser kort ut, är detta den överlägset mest kostsamma operationen. Här måste du använda $unwind
för att tillämpa villkoren på arrayelementen. Detta är en mycket kostsam process eftersom den producerar en kopia av varje dokument för varje matrispost, och i huvudsak ersätts av moderna operatörer som undviker detta i fallet med "filtrering".
Den andra $match
steg här kasserar alla element (nu "dokument") som inte matchade "avsändarvillkoret". Sedan tillämpar vi en $sort
för att sätta det "senaste" datumet överst för varje dokument med _id
, därav de två "sorterings"-tangenterna.
Slutligen tillämpar vi $group
för att bara referera till originaldokumentet, med $first
som ackumulator för att få elementet som är "överst".