Podstawowa koncepcja polega na tym, że potrzebujesz struktury agregacji, aby zastosować warunki do „filtrowania” elementów tablicy do warunków. W zależności od dostępnej wersji można zastosować różne techniki.
We wszystkich przypadkach jest to wynik:
{
"_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 i nowsze
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
]
}
}
}
}
}}
])
Jest to najbardziej wydajny sposób, który wykorzystuje $replaceRoot
co pozwala nam deklarować zmienne do użycia w „zastąpionej” strukturze za pomocą $let
. Główną zaletą jest to, że wymaga to tylko "dwóch" etapów potoku.
Aby dopasować zawartość tablicy, użyj $filter
gdzie stosujesz $eq
logiczna operacja testowania wartości "nadawcy"
. Tam, gdzie warunek pasuje, zwracane są tylko pasujące wpisy tablicy.
Używając tego samego $filter
aby brane pod uwagę były tylko pasujące wpisy „nadawcy”, chcemy zastosować $maks
nad "filtrowaną" listą do wartości w "datetime"
. $max
]5
wartość to „ostatnia” data według warunków.
Potrzebujemy tej wartości, abyśmy mogli później porównać zwrócone wyniki z tablicy „filtrowanej” z tą „maxDate”. Co dzieje się w "in"
blok $let
gdzie dwie „zmienne” zadeklarowane wcześniej dla filtrowanej treści i „maxDate” są ponownie stosowane do $filtr
aby zwrócić to, co powinno być jedyną wartością, która spełnia oba warunki, również z "najnowszą datą".
Ponieważ chcesz tylko „jeden” wynik, używamy $arrayElemAt
aby użyć wartości zamiast tablicy.
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
]
}
}}
])
Jest to zasadniczo ten sam proces, jak opisano, ale bez $ zastąp katalog główny
etap potoku, musimy złożyć wniosek w dwóch $project
gradacja. Powodem tego jest to, że potrzebujemy „obliczonej wartości” z „maxDate”, aby wykonać tę ostateczną $filter
, i nie jest dostępne w instrukcji złożonej, więc zamiast tego dzielimy potoki. Ma to niewielki wpływ na całkowity koszt operacji.
W MongoDB 2.6 do 3.0 możemy tutaj użyć większości techniki z wyjątkiem $arrayElemAt
i albo zaakceptuj wynik „tablicy” z pojedynczym wpisem, albo wstaw $odpręż
etap, aby poradzić sobie z tym, co powinno być teraz pojedynczym wpisem.
Wcześniejsze wersje 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" }
}}
])
Choć wydaje się to krótkie, jest to zdecydowanie najbardziej kosztowna operacja. Tutaj musisz użyć $unwind
w celu zastosowania warunków do elementów tablicy. Jest to bardzo kosztowny proces, ponieważ tworzy kopię każdego dokumentu dla każdego wpisu tablicy i jest zasadniczo zastępowany przez nowoczesnych operatorów, którzy unikają tego w przypadku „filtrowania”.
Drugi $match
stage tutaj odrzuca wszystkie elementy (teraz "dokumenty"), które nie pasują do warunku "nadawca". Następnie stosujemy $sort
aby umieścić „najnowszą” datę na górze każdego dokumentu przy pomocy _id
, stąd dwa klawisze sortowania.
Na koniec stosujemy $group
aby po prostu odwołać się do oryginalnego dokumentu, używając $first
jako akumulator, aby uzyskać element, który jest „na górze”.