Основната концепция тук е, че имате нужда от рамката за агрегиране, за да приложите условия за „филтриране“ на елементите на масива към условията. В зависимост от наличната версия има различни техники, които могат да бъдат приложени.
Във всички случаи това е резултатът:
{
"_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 и по-нова версия
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
]
}
}
}
}
}}
])
Това е най-ефективният начин, който се възползва от $replaceRootкод>
което ни позволява да декларираме променливи, които да използваме в „заменената“ структура, използвайки $let
. Основното предимство тук е, че това изисква само "два" етапа на конвейера.
За да съответствате на съдържанието на масива, вие използвате $filterкод>
където прилагате $eq
логическа операция за тестване на стойността на "sender"
. Когато условието съвпада, тогава се връщат само съответстващите записи в масива.
Използвайки същия $filter
така че да се вземат предвид само съответстващите записи на „подател“, тогава искаме да приложим $max
над „филтрирания“ списък до стойностите в „datetime“
. $max
]5
стойността е "най-новата" дата според условията.
Искаме тази стойност, за да можем по-късно да сравним върнатите резултати от „филтрирания“ масив с този „maxDate“. Което се случва вътре в "in"
блок от $let
където двете „променливи“, декларирани по-рано за филтрираното съдържание и „maxDate“, отново се прилагат към $filter
за да върне това, което трябва да бъде единствената стойност, която отговаря на двете условия, като има и "най-нова дата".
Тъй като искате само „един“ резултат, ние използваме $arrayElemAt
за да използвате стойността вместо масива.
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
]
}
}}
])
Това в общи линии е същият процес като описания, но без $ replaceRoot
етап на тръбопровод, трябва да приложим в два $projectкод>
етапи. Причината за това е, че се нуждаем от „изчислената стойност“ от „maxDate“, за да направим това окончателно $filter
, и не е налично да се направи в съставен оператор, така че вместо това разделяме тръбопроводите. Това има малко влияние върху общата цена на операцията.
В MongoDB 2.6 до 3.0 можем да използваме повечето от техниката тук, с изключение на $arrayElemAt
и или приемете резултата от "масив" с един запис, или поставете $unwind
етап, за да се справи с това, което сега трябва да бъде единичен запис.
По-ранни версии на 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" }
}}
])
Въпреки че изглежда кратко, това е най-скъпата операция. Тук трябва да използвате $unwind
за да приложите условията към елементите на масива. Това е много скъп процес, тъй като създава копие на всеки документ за всеки запис в масив и по същество е заменен от модерните оператори, които избягват това в случай на „филтриране“.
Вторият $match
етапът тук отхвърля всички елементи (сега "документи"), които не отговарят на условието "подател". След това прилагаме $sort
за да поставите най-новата дата отгоре за всеки документ чрез _id
, оттук и двата ключа за сортиране.
Накрая прилагаме $group
за да се обърнете само към оригиналния документ, като използвате $first
като акумулатор, за да получите елемента, който е "отгоре".