MongoDB query $group is returning null - mongodb

I am learning MongoDb query and my requirement is to calculate the average time between two dates. I wrote a mongoDB query with project and group stages.
{
project: {
OrderObject:1,
date:1
}
},
{
group: {
objectId: '$OrderObject.pharmacy.companyName',
count: {
$sum: 1
},
duration: {
$avg: {
$abs: {
$divide: [
{
$subtract: [
{
$arrayElemAt: [
'$date',
0
]
},
{
$arrayElemAt: [
'$date',
1
]
}
]
},
60000
]
}
}
},
OrderIDs: {
$addToSet: '$OrderObject.orderID'
},
pharmacyName: {
$addToSet: '$OrderObject.pharmacy.companyName'
},
}
}
The output I get is
{
count: 3,
duration: 54.53004444444445,
OrderIDs: [ 'ABCDE', 'EWQSE', 'ERTRE' ],
pharmacyName: [ 'pharmacy business Name' ],
objectId: null
},
Can someone please tell me why objectId is null in this case but the value is printed in pharmacyName field. I am using this pipeline in parse server as query.aggregate(pipeline, {useMasterKey:true})
The my expectation is pharmacyName === objectId

Most probably your nested element here is with different name:
OrderObject.name.companyName
but this is not an issue for the $group stage since it make the aggregation for all elements( in total 3) in the collection when the _id is null and do not give you any errors ...
It is also interesing why in your output the "pharmacyName" appear simply as "name" ? ;)

Related

How to update a property of the last object of a list in mongo

I would like to update a property of the last objet stored in a list in mongo. For performance reasons, I can not pop the object from the list, then update the property, and then put the objet back. I can not either change the code design as it does not depend on me. In brief am looking for a way to select the last element of a list.
The closest I came to get it working was to use arrayFilters that I found doing research on the subject (mongodb core ticket: https://jira.mongodb.org/browse/SERVER-27089):
db.getCollection("myCollection")
.updateOne(
{
_id: ObjectId('638f5f7fe881c670052a9d08')
},
{
$set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
},
{
arrayFilters: [{'i.type': 'MyTypeFilter'}]
}
)
I use a filter to only update the objets in theList that have their property type evaluated as MyTypeFilter.
What I am looking for is something like:
db.getCollection("maCollection")
.updateOne(
{
_id: ObjectId('638f5f7fe881c670052a9d08')
},
{
$set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
},
{
arrayFilters: [{'i.index': -1}]
}
)
I also tried using "theList.$last.propertyToUpdate" instead of "theList.$[i].propertyToUpdate" but the path is not recognized (since $last is invalid)
I could not find anything online matching my case.
Thank you for your help, have a great day
You want to be using Mongo's pipelined updates, this allows us to use aggregation operators within the update body.
You do however need to consider edge cases that the previous answer does not. (null list, empty list, and list.length == 1)
Overall it looks like so:
db.collection.update({
_id: ObjectId("638f5f7fe881c670052a9d08")
},
[
{
$set: {
list: {
$concatArrays: [
{
$cond: [
{
$gt: [
{
$size: {
$ifNull: [
"$list",
[]
]
}
},
1
]
},
{
$slice: [
"$list",
0,
{
$subtract: [
{
$size: "$list"
},
1
]
}
]
},
[]
]
},
[
{
$mergeObjects: [
{
$ifNull: [
{
$last: "$list"
},
{}
]
},
{
propertyToUpdate: "NewValueToAssign"
}
]
}
]
]
}
}
}
])
Mongo Playground
One option is to use update with pipeline:
db.collection.update(
{_id: ObjectId("638f5f7fe881c670052a9d08")},
[
{$set: {
theList: {
$concatArrays: [
{$slice: ["$theList", 0, {$subtract: [{$size: "$theList"}, 1]}]},
[{$mergeObjects: [{$last: "$theList"}, {propertyToUpdate: "NewValueToAssign"}]}]
]
}
}}
]
)
See how it works on the playground example

how to set particular field in array of object in mongodb aggregation or pipeline query along with conditional update of the other field in document?

{
id:1,
projectName:'Project1'
start:"17-04-2022",
end:"12-05-2020",
tasks:[
{
taskId:1,
taskName:'first task',
taskStart:'20-04-2022',
taskEnd:'25-04-2022'
},
{
taskid:2,
taskName:'second task',
taskStart:'25-04-2022',
taskEnd:'30-04-2022'
},
]
},
{
id:2,
projectName:'Project2'
start:"27-04-2022",
end:"22-05-2020",
tasks:[
{
taskId:1,
taskName:'first task',
taskStart:'30-04-2022',
taskEnd:'12-05-2022'
},
{
taskid:2,
taskName:'second task',
taskStart:'28-04-2022',
taskEnd:'15-05-2022'
},
]
}
how to update field "taskEnd" of tasks array with document id=1 and taskID=1 AND while updating if data from the frontend which is used to update the "taskEnd" exceeds "end" field, update the "end" field also with same value.
Thanks in Advance,
I know how to update specific field for the array of objects and conditional update of field with two separate queries.
can it be done with single query?
You can do this with a pipeline update instead of using an update document.
I'm assuming you are getting the input date in the same format as the other dates.
var input_date = "11-05-2020";
db.collection.update({
id: 1
},
[
{
$addFields: {
end: {
$cond: [
{
$gt: [
{
$dateFromString: {
dateString: "$end"
}
},
{
$dateFromString: {
dateString: input_date
}
}
]
},
"$end",
input_date
]
},
tasks: {
$map: {
input: "$tasks",
as: "elem",
in: {
$cond: [
{
$eq: [
"$$elem.taskId",
1
]
},
{
$setField: {
field: "taskEnd",
input: "$$elem",
value: input_date
}
},
"$$elem"
]
}
}
}
}
}
])

MongoDB aggregate query to SpringDataMongoDB

I have below MongoDB aggregate query and would like to have it's equivalent SpringData Mongodb query.
MongoDB Aggregate Query :
db.response.aggregate(
// Pipeline
[
// Stage 1 : Group by Emotion & Month
{
$group: {
_id: {
emotion: "$emotion",
category: "$category"
},
count: {
$sum: 1
},
point: {
$first: '$point'
}
}
},
// Stage 2 : Total Points
{
$addFields: {
"totalPoint": {
$multiply: ["$point", "$count"]
}
}
},
// Stage3 : Group By Category - Overall Response Total & totalFeedbacks
{
$group: {
_id: '$_id.category',
totalFeedbacks: {
$sum: "$count"
},
overallResponseTotal: {
$sum: "$totalPoint"
}
}
},
// Stage4 - Overall Response Total & totalFeedbacks
{
$project: {
_id: 1,
overallResponseTotal: '$overallResponseTotal',
maxTotalFrom: {
"$multiply": ["$totalFeedbacks", 3.0]
},
percent: {
"$multiply": [{
"$divide": ["$overallResponseTotal", "$maxTotalFrom"]
}, 100.0]
}
}
},
// Stage4 - Percentage Monthwise
{
$project: {
_id: 1,
overallResponseTotal: 1,
maxTotalFrom: 1,
percent: {
"$multiply": [{
"$divide": ["$overallResponseTotal", "$maxTotalFrom"]
}, 100.0]
}
}
}
]
);
I have tried it's equivalent in Spring Data but got stuck at Stage 2 on how to convert "$addFields" to java code. Though I search about it on multiple sites but couldn't find anything useful. Please see my equivalent java code for Stage 1.
//Stage 1 -Group By Emotion and Category and return it's count
GroupOperation groupEmotionAndCategory = Aggregation.group("emotion","category").count().as("count").first("point")
.as("point");
Aggregation aggregation = Aggregation.newAggregation(groupEmotionAndCategory);
AggregationResults<CategoryWiseEmotion> output = mongoTemplate.aggregate(aggregation, Response.class, CategoryWiseEmotion.class);
Any helps will be highly appreciated.
$addFields is not yet supported by Spring Data Mongodb.
One workaround is to pass the raw aggregation pipeline to Spring.
But since you have a limited number of fields after stage 1, you could also downgrade stage 2 to a projection:
{
$project: {
// _id is included by default
"count" : 1, // include count
"point" : 1, // include point
"totalPoint": {
$multiply: ["$point", "$count"] // compute totalPoint
}
}
}
I haven't tested it myself, but this projection should translate to something like:
ProjectionOperation p = project("count", "point").and("point").multiply(Fields.field("count")).as("totalPoint");
Then you can translate stage 3, 4 and 5 similarly and pass the whole pipeline to Aggregation.aggregate().

MongoDb how to count fields

I have some data like this:
{
user_id:1,
group_id:123,
discription:null
},
{
user_id:1,
group_id:321,
discription:null
},
{
user_id:1,
group_id:123,
discription:"text"
},
{
user_id:1,
group_id:321,
discription:"another text"
},
{
user_id:1,
group_id:321,
discription:"another another text"
},
etc..
I want get all groups (group by group_id), count of document in each group, and count of documents in that group that have "discription" with null value and not null value.
So i need results like:
[
[group_id:123, count:2, isNull:1, isNotNull:1],
[group_id:321, count:3, isNull:1, isNotNull:2]
]
I know how to group fields by "group_id" and get the "count", but I don't know how to get the info about "description".
db.collection.aggregate([
{
$match:{
user_id:1
}
},
{
$group:{
_id:'$group_id',
group_id:{$first:'$group_id'},
count:{'$sum':1}
}
}
])
Please check this Query
db.testing.aggregate([{
$match:{
user_id:1
}
},
{$project:{_id:0,user_id:1,group_id: 1,description: { $ifNull: [ "$discription", 1 ] }}},
{
$group:{
_id:'$group_id',
group_count:{'$sum':1},
IsNull:{"$sum":"$description"}
}
},
{$project:{_id:0,group_id:"$_id", count:"$group_count", isNull:"$IsNull", isNotNull:{ $subtract:["$group_count","$IsNull"]}}}
])

Sum unique properties in different collection elements

I am quite new to MongoDB. Hopefully I am using the correct terminology to express my problem.
I have the following collection:
Data collection
{
"name":"ABC",
"resourceId":"i-1234",
"volumeId":"v-1234",
"data":"11/6/2013 12AM",
"cost": 0.5
},
{
"name":"ABC",
"resourceId":"v-1234",
"volumeId":"",
"data":"11/6/2013 2AM",
"cost": 1.5
}
I want to query the collection such that if a volumeId matches with another entries resourceId, then sum up the corresponding resourceId's cost together.
As a result, the cost would be 2.0 in this case.
Basically I want to match the volumeId of one entry to the resourceId of another entry and sum the costs if matched.
I hope I have explained my problem properly. Any help is appreciated. Thanks
Try this aggregation query:
db.col.aggregate([
{
$project: {
resourceId: 1,
volumeId: 1,
cost: 1,
match: {
$cond: [
{$eq: ["$volumeId", ""]},
"$resourceId",
"$volumeId"
]
}
}
},
{
$group: {
_id: '$match',
cost: {$sum: '$cost'},
resId: {
$addToSet: {
$cond: [
{$eq: ['$match', '$resourceId']},
null,
'$resourceId'
]
}
}
}
},
{$unwind: '$resId'},
{$match: {
resId: {
$ne: null
}
}
},
{
$project: {
resourseId: '$resId',
cost: 1,
_id: 0
}
}
])
And you will get the following:
{ "cost" : 2, "resourseId" : "i-1234" }
This is assuming the statement I wrote in the comment is true.