Det grundlæggende koncept her er, at du har brug for aggregeringsrammen for at anvende betingelser for at "filtrere" array-elementerne til betingelserne. Afhængigt af den tilgængelige version er der forskellige teknikker, der kan anvendes.
I alle tilfælde er dette 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 og nyere
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
]
}
}
}
}
}}
])
Dette er den mest effektive måde, som udnytter $replaceRoot
som giver os mulighed for at erklære variabler til brug i den "erstattede" struktur ved hjælp af
kode>$let
. Den største fordel her er, at dette kun kræver "to" pipeline-trin.
For at matche array-indholdet bruger du $filter
hvor du anvender $eq
logisk operation for at teste værdien af "sender"
. Hvor betingelsen matcher, returneres kun de matchende array-indgange.
Bruger den samme $filter
så kun de matchende "afsender"-indgange tages i betragtning, vil vi så anvende $max
over den "filtrerede" liste til værdierne i "datetime"
. $max
]5
værdi er den "seneste" dato ifølge betingelserne.
Vi ønsker denne værdi, så vi senere kan sammenligne de returnerede resultater fra det "filtrerede" array med denne "maxDate". Hvilket er, hvad der sker inde i "in"
blok af $let
hvor de to tidligere erklærede "variabler" for det filtrerede indhold og "maxDate" igen anvendes på $filter
for at returnere, hvad der skulle være den eneste værdi, der opfyldte begge betingelser med "seneste dato".
Da du kun ønsker "et" resultat, bruger vi $arrayElemAt
at bruge værdien i stedet for matrixen.
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
]
}
}}
])
Dette er grundlæggende den samme proces som beskrevet, men uden $ replaceRoot
pipeline fase, skal vi ansøge i to $project
niveauer. Grunden til dette er, at vi har brug for den "beregnede værdi" fra "maxDate" for at gøre den sidste $filter
, og det er ikke tilgængeligt at gøre i en sammensat erklæring, så i stedet opdeler vi pipelines. Dette har en lille indvirkning på de samlede omkostninger ved operationen.
I MongoDB 2.6 til 3.0 kan vi bruge det meste af teknikken her undtagen >$arrayElemAt
og enten acceptere "array"-resultatet med en enkelt indtastning eller indsætte en $unwind
etape for at beskæftige sig med det, der nu skulle være en enkelt post.
MongoDB tidligere 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" }
}}
])
Selvom det ser kort ud, er dette langt den dyreste operation. Her skal du bruge $unwind
for at anvende betingelserne på array-elementerne. Dette er en meget bekostelig proces, da den producerer en kopi af hvert dokument for hver array-indgang og i det væsentlige erstattes af de moderne operatører, der undgår dette i tilfælde af "filtrering".
Den anden $match
fase kasserer her alle elementer (nu "dokumenter"), som ikke matchede "afsender"-betingelsen. Så anvender vi en $sort
for at sætte den "seneste" dato øverst for hvert dokument ved _id
, deraf de to "sorteringstaster".
Til sidst anvender vi $group
for blot at henvise til det originale dokument ved at bruge $first
som akkumulator for at få det element, der er "på toppen".