Het basisconcept hier is dat u het aggregatieraamwerk nodig hebt om voorwaarden toe te passen om de array-elementen op de voorwaarden te "filteren". Afhankelijk van de beschikbare versie zijn er verschillende technieken die kunnen worden toegepast.
In alle gevallen is dit het resultaat:
{
"_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 en hoger
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
]
}
}
}
}
}}
])
Dit is de meest efficiënte manier om gebruik te maken van $replaceRoot
waarmee we variabelen kunnen declareren voor gebruik in de "vervangen" structuur met behulp van $let
. Het belangrijkste voordeel hier is dat dit slechts "twee" pijplijntrappen vereist.
Om de array-inhoud te matchen, gebruikt u $filter
waar u de $eq
toepast
logische bewerking om de waarde van "sender"
. te testen . Waar de voorwaarde overeenkomt, worden alleen de overeenkomende array-items geretourneerd.
Dezelfde $filter
gebruiken
zodat alleen de overeenkomende "afzender"-vermeldingen worden beschouwd, willen we vervolgens $max
over de "gefilterde" lijst naar de waarden in "datetime"
. De $max
]5
waarde is de "laatste" datum volgens de voorwaarden.
We willen deze waarde zodat we later de geretourneerde resultaten van de "gefilterde" array kunnen vergelijken met deze "maxDate". Dat is wat er gebeurt in de "in"
blok van $let
waarbij de twee eerder gedeclareerde "variabelen" voor de gefilterde inhoud en de "maxDate" opnieuw worden toegepast op $filter
om terug te geven wat de enige waarde zou moeten zijn die aan beide voorwaarden voldeed met ook de "laatste datum".
Omdat je maar "één" resultaat wilt, gebruiken we $arrayElemAt
om de waarde te gebruiken in plaats van de 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
]
}
}}
])
Dit is in principe hetzelfde proces als beschreven, maar zonder de $replaceRoot
pijplijnfase, moeten we toepassen in twee $project
stadia. De reden hiervoor is dat we de "berekende waarde" van "maxDate" nodig hebben om dat laatste te doen $filter
, en het is niet beschikbaar om te doen in een samengestelde instructie, dus in plaats daarvan splitsen we de pijplijnen. Dit heeft een kleine impact op de totale kosten van de operatie.
In MongoDB 2.6 tot 3.0 kunnen we de meeste technieken hier gebruiken, behalve $arrayElemAt
en accepteer het "array"-resultaat met een enkele invoer of plaats een $unwind
om te gaan met wat nu een enkele invoer zou moeten zijn.
MongoDB eerdere versies
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" }
}}
])
Hoewel het er kort uitziet, is dit verreweg de duurste operatie. Hier moet je $unwind
gebruiken
om de voorwaarden toe te passen op de array-elementen. Dit is een zeer kostbaar proces omdat het een kopie van elk document produceert voor elke array-invoer, en in wezen wordt vervangen door de moderne operators die dit vermijden in het geval van "filteren".
De tweede $match
stage here verwijdert alle elementen (nu "documenten") die niet overeenkwamen met de voorwaarde "afzender". Dan passen we een $sort
toe
om de "laatste" datum bovenaan te plaatsen voor elk document door de _id
, vandaar de twee "sorteer"-toetsen.
Ten slotte passen we $group
toe
om gewoon naar het originele document te verwijzen, met behulp van $first
als de accumulator om het element te krijgen dat "bovenaan" is.