Count if a value exists inside the array of objects [ MongoDB ] - mongodb

I am developing a simple chat application with MongoDB and got stuck into a situation.
My document in database is as
{
"_id" : ObjectId("605a217ed8168f4c262f4782"),
"message" : "Hi, This is a test message",
"created" : ISODate("2021-03-23T17:12:30.000Z"),
"user" : {
"_id" : ObjectId("5977af7df1d8cc4623283b14"),
"name" : "Sender Of Message"
},
"recipients" : [
{
"_id" : ObjectId("5977af7df1d8cc4623283b14"),
"time" : ISODate("2021-03-23T17:12:30.000Z")
},
{
"_id" : ObjectId("5df50a5eaa0e3c3104006101"),
"time" : ISODate("2021-03-23T17:12:35.000Z")
}
],
"target" : {
"_id" : ObjectId("5df50a5eaa0e3c3104006101"),
"name" : "Target Person"
},
"status" : 1
}
When I try to get the last message with the unread count of the user I am always getting 1
Here is the query that I tried on.
db.collection.aggregate([
{ $match: { 'target._id': ObjectId('5df50a5eaa0e3c3104006101'), status: 1 } },
{ $sort: { _id: -1 } },
{
$group: {
_id: '$user._id',
doc: { $first: '$$ROOT' },
unread: {
$sum: {
$cond: {
if: { $ne: [ ObjectId('5df50a5eaa0e3c3104006101'), '$recipients._id' ] },
then: 1,
else: 0
}
}
}
}
}
])
If the collection contains even just the one document above, it is supposed to give 0 as the object inside the recipients array already contains the _id as ObjectId('5df50a5eaa0e3c3104006101'), but I'm getting 1 for the unread count. Any help?
Here is the output that I get from the query
{
"_id" : ObjectId("5977af7df1d8cc4623283b14"),
"doc" : {
"_id" : ObjectId("605a217ed8168f4c262f4782"),
"message" : "Hi, This is a test message",
"created" : ISODate("2021-03-23T17:12:30.000Z"),
"user" : {
"_id" : ObjectId("5977af7df1d8cc4623283b14"),
"name" : "Sender Of Message"
},
"recipients" : [
{
"_id" : ObjectId("5977af7df1d8cc4623283b14"),
"time" : ISODate("2021-03-23T17:12:30.000Z")
},
{
"_id" : ObjectId("5df50a5eaa0e3c3104006101"),
"time" : ISODate("2021-03-23T17:12:35.000Z")
}
],
"target" : {
"_id" : ObjectId("5df50a5eaa0e3c3104006101"),
"name" : "Target Person"
},
"status" : 1
},
"unread" : 1.0
}
I know why its showing with the count as 1
The array recipients contains an object with _id as ObjectId("5977af7df1d8cc4623283b14") inside it, so its a non matching condition. Which is causing the if condition to be satisfied and produce a value 1.
But I need to figure out how to query it to get the actual value.
Please note that I cant use $push operator on recipients array as it might have greater amount of object ( maybe in future )

Thanks for the support, but I have found the answer by myself.
Here is my approch to get the data as per the requirement.
Instead of searching for the records within the array what I did is
Filtered the data array to the _id that I don't need, so the array will have exactly one document or else it will be empty.
When taking the negation of the condition. ie, when there is one value in the array I need the counter to be 0 or else it should be 1
So I used the $size to check the array's size and $filter to filter out the other _ids and then used $sum to increment the counter as required.
db.collection.aggregate([
{ $match: { 'target._id': ObjectId('5df50a5eaa0e3c3104006101'), status: 1 } },
{ $sort: { _id: -1 } },
{
$group: {
_id: '$user._id',
doc: { $first: '$$ROOT' },
unread: {
$sum: {
$cond:{
if: {
$size: {
$filter: {
input: '$recipients',
as: 'item',
cond: { $eq: [ ObjectId('5df50a5eaa0e3c3104006101'), '$$item._id' ] }
}
}
},
then: 0,
else: 1
}
}
}
}
}
])

Try to Use like this:
db.getCollection('test').aggregate([
{ $match: { 'target._id': ObjectId('5df50a5eaa0e3c3104006101'), status: 1
} },
{ $sort: { _id: -1 } },
{ $unwind: { path: "$recipients", preserveNullAndEmptyArrays: true } },
{
$group: {
_id: '$user._id',
doc: { $first: '$$ROOT' },
unread: {
$sum: {
$cond: {
if: { $ne: ['$recipients._id',
ObjectId('5df50a5eaa0e3c3104006101') ] },
then: 1,
else: 0
}
}
}
}
}
])

Related

MongoDB: Count total number of true and false values for documents matching a query

My question is exactly same to this
MongoDB Count total number of true and false values for documents matching a query
I followed the same solution but I am not getting the desired output.
I have some products records in database. Some of them are free and some are not. I want to retrieve the count of free and non-free products. This is my records
{ "_id" : ObjectId("54abcdbeba070410146d6073"), "name" : "Product 1", "isFree" : true}
{ "_id" : ObjectId("54afe32fec4444481b985711"), "name" : "Product 2", "isFree" : false}
{ "_id" : ObjectId("54b66de68dde7a0c19be987b"), "name" : "Product 3", "isFree" : false}
{ "_id" : ObjectId("54b66de68dde7a0c19bc897d"), "name" : "Product 4", "isFree" : false}
I am expecting output as:
{
"free": 1,
"nonFree": 3
}
This is my query:
db.collection.aggregate([
{
"$group": {
"_id": "$isFree",
"count": {
"$sum": 1
}
}
},
{
"$group": {
_id: null,
free: {
$sum: {
$cond: ["_id",1,0]
}
},
nonFree: {
$sum: {
$cond: ["_id",0,1]
}
}
}
}
])
I am getting output as:
{
"_id": null,
"free": 2,
"nonFree": 0
}
You can skip the first group, and use $cond to evaluate isFree directly:
db.collection.aggregate([
{
"$group": {
_id: null,
free: {
$sum: {
$cond: ["$isFree",1,0]
}
},
nonFree: {
$sum: {
$cond: ["$isFree",0,1]
}
}
}
},
{$project:{_id:0}}
])
Playground

Mongo Aggregation does not analyze all documents

I'm trying to get some statistics using Mongo's aggregation Framework but my queries do not seem to work right. So I have a collection of documents, each document having the structure below:
{
"_id" : ObjectId("5e46af306d0f5d63d4de6d0f"),
"homeEmperor" : {
"name" : "Home",
"units" : {
"Mage" : 15,
"Warrior" : 15,
"Swordmaster" : 15,
"Rogue" : 15,
"Warlock" : 15
}
},
"awayEmperor" : {
"name" : "Away",
"units" : {
"Druid" : 15,
"Ranger" : 15,
"Priest" : 15,
"Monk" : 15,
"Dragon" : 15
}
},
"dateCreated" : ISODate("2020-02-06T00:00:00.000Z"),
"winner" : "away",
"battleType" : "mutual",
"turns" : 45,
"_class" : "com.deathstar.Datahouse.domain.mongo.HistoricMongoRecord"
}
So what I'm doing at the moment is using this query to get the number of wins each unit has
db.getCollection('WarHistory').aggregate([
{ $match: { battleType: "mutual" } },
{ $project: {"winners": {$cond: { if: { $eq: [ "$winner", "home" ] }, then: "$homeEmperor.units", else: "$awayEmperor.units"} }} },
{ $project: {"winnerUnits": { $objectToArray: "$winners" }} },
{ $group: { _id: {unitType: "$winnerUnits.k"}} },
{ $unwind: "$_id.unitType" },
{ $group: { _id: {unitType: "$_id.unitType"}, count:{$sum:1} }},
{ $sort : { count : -1 }}
])
and this query to get each unit's participation
db.getCollection('WarHistory').aggregate([
{ $match: { battleType: "mutual" } },
{ $project: { "participants": { $mergeObjects: [ "$homeEmperor.units", "$awayEmperor.units" ] } } },
{ $project: {"participantUnits": { $objectToArray: "$participants" }} },
{ $group: { _id: {unitType: "$participantUnits.k"}} },
{ $unwind: "$_id.unitType" },
{ $group: { _id: {unitType: "$_id.unitType"}, count:{$sum:1} }},
{ $sort : { count : -1 }}
])
The output of both queries is as shown below which is the way I want it:
{
"_id" : {
"unitType" : "Pirate"
},
"count" : 1331.0
}
After that I divide them and so on. I also have a Java service which produces these documents.
The problem is that while at first the queries seem to work fine, after some point they seem to stop counting all documents, so I see the "Count" counter in the Collection increasing but the query results remain exactly the same.
I've checked and the documents continue to have the same structure along the way.
I also tried the "allowDiskUse:true" parameter in case the problem was the memory capacity(I have 16GB RAM), but it really made no difference.
Can anyone please confirm that the queries above can do what I want them to?
Any help would be really appreciated! Thanks for your time!

How can i retrieve all nested array of object element which has been matched by using "elementMatch" mongodb

We have array of object and every object contain array of object "Conversation".We need to get the count/elements where the field deliveryStatus is sent and userId is exits
[{
_id: "12312312"
conversation:[
{
"messageInformation" : {
"deliveryStatus" : "sent"
},
"userId" : 12
},
{
"messageInformation" : {
"deliveryStatus" : "sent",
}
}
]},
_id: "213123123123"
conversation:[
{
"messageInformation" : {
"deliveryStatus" : "sent"
},
"userId" : 33
},
{
"messageInformation" : {
"deliveryStatus" : "sent",
"userInfo" : [ ]
}
}
]}
]
I need the total count. I had try try elemMatch but it only return the first element
let counter = await ProjectConversation.find({
conversation: {
$elemMatch: {
userId :{$exists: true},
"messageInformation.deliveryStatus": deliveryStatus.Sent
}
}
}, {
_id: 0,
conversation.$: 1
});
It will be fine if I get the object and do the count by using javascript
You can use aggregate $unwind to deconstruct the array and use $count to count the number of documents that matches the $match property, eg :
db.collection.aggregate([{
$unwind: "$conversation"
},
{
$match: {
"conversation.messageInformation.deliveryStatus": "sent",
"conversation.userId": {
$exists: true
}
}
},
{
$count: "NumberOfMsgSent"
}
])
On Mongo Playground

using mongo aggregation how to replace the fields names [duplicate]

I have large collection of documents which represent some kind of events. Collection contains events for different userId.
{
"_id" : ObjectId("57fd7d00e4b011cafdb90d22"),
"userId" : "123123123",
"userType" : "mobile",
"event_type" : "clicked_ok",
"country" : "US",
"timestamp" : ISODate("2016-10-12T00:00:00.308Z")
}
{
"_id" : ObjectId("57fd7d00e4b011cafdb90d22"),
"userId" : "123123123",
"userType" : "mobile",
"event_type" : "clicked_cancel",
"country" : "US",
"timestamp" : ISODate("2016-10-12T00:00:00.308Z")
}
At midnight I need to run aggregation for all documents for the previous day. Documents need to aggregated in the way so I could get number of different events for particular userId.
{
"userId" : "123123123",
"userType" : "mobile",
"country" : "US",
"clicked_ok" : 23,
"send_message" : 14,
"clicked_cancel" : 100,
"date" : "2016-11-24",
}
During aggregation I need to perform two things:
calculate number of events for particular userId
add "date" text fields with date
Any help is greatly appreciated! :)
you can do this with aggregation like this :
db.user.aggregate([
{
$match:{
$and:[
{
timestamp:{
$gte: ISODate("2016-10-12T00:00:00.000Z")
}
},
{
timestamp:{
$lt: ISODate("2016-10-13T00:00:00.000Z")
}
}
]
}
},
{
$group:{
_id:"$userId",
timestamp:{
$first:"$timestamp"
},
send_message:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"send_message"
]
},
1,
0
]
}
},
clicked_cancel:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"clicked_cancel"
]
},
1,
0
]
}
},
clicked_ok:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"clicked_ok"
]
},
1,
0
]
}
}
}
},
{
$project:{
date:{
$dateToString:{
format:"%Y-%m-%d",
date:"$timestamp"
}
},
userId:1,
clicked_cancel:1,
send_message:1,
clicked_ok:1
}
}
])
explanation:
keep only document for a specific day in $match stage
group doc by userId and count occurrences for each event in $group stage
finally format the timestamp field into yyyy_MM-dd format in $project stage
for the data you provided, this will output
{
"_id":"123123123",
"send_message":0,
"clicked_cancel":1,
"clicked_ok":1,
"date":"2016-10-12"
}
Check the following query
db.sandbox.aggregate([{
$group: {
_id: {
userId: "$userId",
date: {
$dateToString: { format: "%Y-%m-%d", date: "$timestamp" }}
},
send_message: {
$sum: {
$cond: { if: { $eq: ["$event_type", "send_message"] }, then: 1, else: 0 } }
},
clicked_cancel: {
$sum: {
$cond: { if: { $eq: ["$event_type", "clicked_cancel"] }, then: 1, else: 0 }
}
},
clicked_ok: {
$sum: {
$cond: { if: { $eq: ["$event_type", "clicked_ok"] }, then: 1, else: 0 }
}
}
}
}])

Cant find duplicate values for array part in mongodb

db.school.find({ "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3" })
this is an example document from school collection of that query:
{
"_id" : ObjectId("57fafasf2323232323232f57682cd42"),
"status" : "wait",
"merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3",
"isValid" : false,
"fields" : { "schoolid" : {
"value" : "2323232",
"detail" : {
"revisedBy" : "teacher",
"revisionDate" : ISODate("2015-06-24T09:22:44.288+0000")
},
"history" : [
]
}}
}
I want to see which has duplcate schoolid. SO i do this:
db.school.aggregate([
{$match:{ "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3"
{ $group: {
_id: { fields.schoolid.value: "$fields.schoolid.value" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
but it gives error.
a lot of errors for a lot of lines
i tried to do like this
_id: { "fields.schoolid.value": "$fields.schoolid.value" },
or
_id: { 'fields.schoolid.value': "$'fields.schoolid.value'" },
but did not work. ow can i use it?
According to the document you provided, there is no fields field, so the group stage can't work. Your query should be :
db.school.aggregate([
{ $match: { "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3"}},
{ $group: {
_id: { value: "$fields.schoolid.value" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
Also note that fields.schoolid.value is not a valid fieldname, you need to enclode it in "" or to remove the "."