db.chat.find().pretty().limit(3)
{
"_id" : ObjectId("593921425ccc8150f35e7662"),
"user1" : 1,
"user2" : 2,
"messages" : [
{
"capty" : 'A',
"body" : "hiii 0"
},
{
"capty" : 'B',
"body" : "hiii 1"
},
{
"capty" : 'A',
"body" : "hiii 2"
}
]
}
{
"_id" : ObjectId("593921425ccc8150f35e7663"),
"user1" : 1,
"user2" : 3,
"messages" : [
{
"capty" : 'A',
"body" : "hiii 0"
},
{
"capty" : 'A',
"body" : "hiii 1"
},
{
"capty" : 'B',
"body" : "hiii 23"
}
]
}
{
"_id" : ObjectId("593921425ccc8150f35e7664"),
"user1" : 1,
"user2" : 4,
"messages" : [
{
"capty" : 'A',
"body" : "hiii 0"
},
{
"capty" : 'B',
"body" : "hiii 1"
},
{
"capty" : 'B',
"body" : "hiii 24"
}
]
}
I am not to figure out the query that will give me list of user2 and the body where user1=1 and last capty of messages = 'B'. i.e output should be last 2 rows.
i.e desireable output.
user2:3, "body" : "hiii 23"
user2:4, "body" : "hiii 24"
The main thing here is the aggregation $slice
to get the last element from the array,
db.chat.aggregate([
{ "$match": { "user1": 1, "messages.capty": "B" } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$slice": [ "$messages.capty", -1 ] }, ["B"] ]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$project": {
"user2": 1,
"body": {
"$arrayElemAt": [
{ "$map": {
"input": {
"$filter": {
"input": { "$slice": [ "$messages",-1 ] },
"as": "m",
"cond": { "$eq": [ "$$m.capty", "B" ] }
}
},
"as": "m",
"in": "$$m.body"
}},
0
]
}
}}
])
I'm actually being "extra safe" in the $project
stage with the $filter
but it all basically the same.
First the query selects the documents, we cannot actually say at this point to "only" match the last element of the array but we want to filter documents that do not have the condition on the array at all.
The $redact
is the actual thing that looks at the "last" array entry and tests the value of the field. We can notate just the field from the array by $messages.capty
which returns just an array of those items. Here we then $slice
or even $arrayElemAt
if you want to get the last value, being the index of -1
.
At this point we only "filtered" the "documents" which do not match the condition. The final $project
stage takes the last element of the array, checks it matches the condition ( which it should by the earlier stages ), extracts the value of "body"
and turns the single array content into just the plain value.
You can alternately forego the "carefulness" and simply just grab the last array element since $redact
should have done it's job:
db.chat.aggregate([
{ "$match": { "user1": 1, "messages.capty": "B" } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$arrayElemAt": [ "$messages.capty", -1 ] }, "B" ]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$project": {
"user2": 1,
"body": {
"$arrayElemAt": [ "$messages.body", -1 ]
}
}}
])
The whole thing really breaks down to "match the possible documents with a query" and then "compare and extract the last element with $slice
or $arrayElemAt
".
Results are:
{
"_id" : ObjectId("593921425ccc8150f35e7663"),
"user2" : 3,
"body" : "hiii 23"
}
{
"_id" : ObjectId("593921425ccc8150f35e7664"),
"user2" : 4,
"body" : "hiii 24"
}