How sum fields with the value true using aggregate - javascript

Model.aggregate([
{
'$group': {
'_id': '$id',
'name': { '$first': '$name' },
'tof': { $sum: { $eq:["$tof",true] } },
}
}
]) .... // rest of the code.
I am trying to sum the tof (true or false) field, but only if the value is true, but is not working like the way i am trying..
What i am doing wrong here? How proceed?
Thanks!!

Instead of extracting and iterating, as you would in this case, documents you don't need using $cond you can just omit them from the find:
Model.aggregate([
{
'$match': {'tof': true}
},
{
'$group': {
'_id': '$id',
'name': { '$first': '$name' },
'tof': { $sum: 1 },
}
}
])
However, since you are using multiple counts you need $cond ( http://docs.mongodb.org/manual/reference/operator/aggregation/cond/ ) so here is an example:
Model.aggregate([
{
'$group': {
'_id': '$id',
'name': { '$first': '$name' },
'tof': { $sum: {$cond: [{$eq:['$tof', true]}, 1, 0]} },
}
}
])

Related

Add aggregation by the condition of the value of your own variable

I have two functions to get an array from monga using aggregation. The functions are completely the same, except for one pipeline - "match ({startTime: {$ gte: start}})". How can I leave one function and add a "math" only by the presence of the "start" variable, which is a date filter?
let groupedByUserSessions
if (lastDay) {
const start = getDateFromString(lastDay);
groupedByUserSessions = await getValuesByDate(start)
} else {
groupedByUserSessions = await getAllValues();
}
The functions are completely the same,
function getValuesByDate(start) {
return Sessions.aggregate()
.match({ startTime: { $gte: start } })
.group({
_id: { departament: "$departament", userAdName: "$userAdName" },
cleanTime: { $sum: { $subtract: ["$commonTime", "$idlingTime"] } }
})
.group({
_id: { departament: "$_id.departament"},
users: { $push: {value: '$cleanTime', name: '$_id.userAdName'} },
commonCleanTime: { $sum: "$cleanTime" }
})
.project({
departament: '$_id.departament',
users: '$users',
commonCleanTime: '$commonCleanTime',
performance: { $divide: [ "$commonCleanTime", { $size: "$users" }] }
});
}
You can break the pipeline into a "head" and a "tail" and construct the head differently when the start argument is given:
function getValuesByDate(start) {
let agg = Sessions.aggregate();
if (start) {
agg = agg.match({ startTime: { $gte: start } });
}
return agg
.group({
_id: { departament: '$departament', userAdName: '$userAdName' },
cleanTime: { $sum: { $subtract: ['$commonTime', '$idlingTime'] } },
})
.group({
_id: { departament: '$_id.departament' },
users: { $push: { value: '$cleanTime', name: '$_id.userAdName' } },
commonCleanTime: { $sum: '$cleanTime' },
})
.project({
departament: '$_id.departament',
users: '$users',
commonCleanTime: '$commonCleanTime',
performance: { $divide: ['$commonCleanTime', { $size: '$users' }] },
});
}

MongoDB find all docs where field doesn't exists, plus if exists apply field operator ($max) condition

I am looking for a query for a $match stage in my aggregation which do almost the same, as in this question, but..
if field (named rank in my case) doesn't exists in document, add document to results
but if field, exists, apply $operator condition (in my case it's $max) to this field, and add all documents that suits this condition to the results.
MongoPlayground with example collection.
Result should be like this:
[
{
"method": 3,
"item": 1,
"rank": 3 //because it has field named rank, and suits condition {rank: $max}
},
{
"method": 4,
"item": 1 //we need this, because document doesn't have rank field at all
},
{
"method": 5,
"item": 1 //we need this, because document doesn't have rank field at all
}
]
Things, that I have tried already:
{
$match: {
$or: [
{item: id, rank: {$exists: true, $max: "$rank"}}, //id === 1
{item: id, rank: {$exists: false}} //id === 1
]
}
}
UPD: As for now, probably I don't limit with $match stage only, $project is also relevant after default match, so I could request every document during $match stage by id no matter, have the doc rank field or not, and then, during $project stage do a "separation" by rank $exists
Try this one:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$group: {
_id: "$item", //<- Change here your searching field
max: {
$max: "$rank" //<- Change here your field to apply $max
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$type: "$data.rank"
},
"missing"
]
},
{
$eq: [
"$data.rank",
"$max"
]
}
]
}
}
},
{
$replaceWith: "$data"
}
])
MongoPlayground
I have found an answer, separated from #Valijon's method, but it's also based on the logic above. My query is:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$project: {
method: 1,
item: 1,
rank: {
$ifNull: [
"$rank",
0
]
}
}
},
{
$group: {
_id: "$item",
data: {
$addToSet: "$$ROOT"
},
min_value: {
$min: "$rank"
},
max_value: {
$max: "$rank"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$or: [
{
$expr: {
$eq: [
"$data.rank",
"$max_value"
]
}
},
{
$expr: {
$eq: [
"$data.rank",
"$min_value"
]
}
},
]
}
}
])
My query is based on $project stage which gives the empty field value 0. It also could be -1, or any value that isn't used in collection. And then I separate results.
MongoPlayground

Mongodb Group and get other fields from documents

update so Mohammad Faisal has the best solution.However it breaks when a new document is added lol! so i learned a lot from his code and modified it and it Works! =) the code is all the way in the bottom.
But here's what i said..
So i have this document
{"_id":"5ddea2e44eb407059828d740",
"projectname":"wdym",
"username":"easy",
"likes":0,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059822d740",
"projectname":"thechosenone",
"username":"easy",
"likes":30,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059828d740",
"projectname":"thanos",
"username":"wiley",
"likes":10,
"link":["ssss"]
}
and basically what i want is the document that contains the highest
likes with it's associated project name
For example the output would be
"projectname":"thechosenone",
"username":"easy",
"likes":30
}
,
{
"projectname":"thanos",
"username":"wiley",
"likes":10,
}
the code i have for this is the following
db
.collection("projects")
.aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" }
}
},
{
$project:{projectname:1}
}
])
$project gives me a strange output. However,
the output was correct without the $project.
But i wanted to project the projectname, the user and the highest likes. Thanks for hearing me out :)
heres the solution =)
db
.collection("projects")
.aggregate([
{
$sort: {
likes: -1
}
},
{
$group: {
_id: {
username: "$username"
},
likes: {
$max: "$likes"
},
projectname: {
$push: "$projectname"
},
link: {
$push: "$link"
}
}
},
{
$project: {
username: "$_id.username",
projectname: {
$arrayElemAt: ["$projectname", 0]
},
link: {
$arrayElemAt: ["$link", 0]
}
}
}
])
.toArray()
If you don't have to use $group this will solve your problem:
db.projects.aggregate([
{$sort:{likes:-1}},
{$limit:1}
]).pretty()
the result would be
{
"_id" : ObjectId("5ddee7f63cee7cdf247059db"),
"projectname" : "thechosenone",
"username" : "easy",
"likes" : 30,
"links" : ["ssss"]
}
Try this:-
db.collection("projects").aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" },
projectname: { $push : { $cond: [ { $max: "$likes" }, "$projectname", "" ]}}
}
}
,
{
$project:{
username:"$_id.username",
projectname:{"$reduce": {
"input": "$projectname",
"initialValue": { "$arrayElemAt": ["$projectname", 0] },
"in": { "$cond": [{ "$ne": ["$$this", ""] }, "$$this", "$$value"] }
}},
likes:1
}
}
])

Get sum of values from unique docs in collection of duplicate docs in MongoDB

I am new to MongoDB and I am stuck in the below scenario.
I have a collection that contains duplicate docs.
I just want to get the sum of the property in each doc excluding the duplicate docs.
My Docs looks like this:
{"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":10,
"transactionId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"uid":"2019-11-20T17:39:17.405Z",
"createdAt":"2019-11-21T08:56:56.589+00:00",
"updatedAt":"2019-11-21T08:56:56.589+00:00","__v":0
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
Currently, I am doing this:
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"}}
},
])
It returns the sum of product_quantity all the matching docs (including the duplicate docs).
Current Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:110}
Expected Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:60}
I want to get the sum of only unique docs. How can I achieve it?
Thanks in advance!
You need to sum inside of the $group _id field, and then use the replaceRoot to achieve the the result you wanted.
MaterialsTrack.aggregate([
{
$match: {
$and: [
{
product_name: "Bottle"
},
{
account_name: "mrfsahas1234"
},
{
action_type: "purchase"
},
{
uid: "2019-11-20T17:39:17.405Z"
}
]
}
},
{
$group: {
_id: {
sellingId: "$sellingId",
PurchaseQuantity: {
$sum: "$product_quantity"
}
}
}
},
{
$replaceRoot: {
newRoot: {
_id: "$_id.sellingId",
PurchaseQuantity: "$_id.PurchaseQuantity"
}
}
}
]);
Sample Output:
[
{
"PurchaseQuantity": 50,
"_id": "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914"
}
]
Playground:
https://mongoplayground.net/p/MOneCRiSlO0
What about adding $addToSet to your aggregations pipeline
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"},"list" : {$addToSet : "$list"}}
},
])

Compare Dates from arrays of different objects in aggregation

on my project i have users that complete combinations (called sessions) of courses. the fact of playing a course is called an attempt. During the attempt they can close it and come back later (so we keep a timelog object).
I have a request from the client which needs to return for each session, the users (and their attempts) that have played whole or part of their session during a certain timeframe.
During a certain timeframe means that the client sends a begin and end date and we count a user for a specific session if:
- the first attempt has begun before the end of the timeframe => the started of the first timelog of the first < ending date
- the last attempt has been finished after the begining of the timeframe => the end of the last timelog of the last attempt > starting date
Here is an example of an attempt object (the only one we need to use here):
{
"_id" : ObjectId("5b9148650ab5f43b5e829a4b"),
"index" : 0,
"author" : ObjectId("5acde2646055980a84914b6b"),
"timelog" : [
{
"started" : ISODate("2018-09-06T15:31:49.163Z"),
"ended" : ISODate("2018-09-06T15:32:03.935Z")
},
...
],
"session" : ObjectId("5b911d31e58dc13ab7586f9b")}
My idea was to make an aggregate on the attempts, to group those using author and session as an _id for the $group stage, and to push all the attempts of the user for this particular session into an array userAttempts.
Then to make an $addField stage to retrieve the started field of the first timelog of the first attempt and the last ended of the last attempt.
And finally to $filter or $match using those new fields.
Here is my aggregate:
const newDate = new Date()
_db.attempts.aggregate([
{ $match: {
author: { $in: programSessionsData.users },
$or: [{ programSession: { $in: programSessionIds } }, { oldTryFor: { $in: programSessionIds } }],
globalTime: $ex,
timelog: $ex }
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
userAttempts: { $push: { attemptId: "$_id", lastTimelog: { $arrayElemAt: ["$timelog", -1] }, timelog: "$timelog" } }
}
},
{
$addFields: { begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: {
$cond: {
if: { $lt: ["$$this.timelog.0.started", "$$value"] },
then: "$$this.timelog.0.started",
else: "$$value"
} }
} } }
}
I also tried this for the addFields stage:
{
$addFields: { begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: { $min: ["$$this.timelog.0.started", "$$value] }
} } }
}
However everytime begin is an empty array.
I do not really know how i can extract those two date, or compare dates between them.
To Note: the end one is more difficult that is why i have to first extract lastTimelog. If you an other method i would gladly take it.
Also this code is on a node server so i cannot use ISODate. and the mongo version used is 3.6.3.
After playing with aggregate a bit i came up with 2 solutions:
Solution 1
_db.attempts.aggregate([
{ $match: {
query
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog" } }
}
}, {
$addFields: {
begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: { $min: [{ $reduce: {
input: "$$this.timelog",
initialValue: newDate,
in: { $min: ["$$this.started", "$$value"] }
} }, "$$value"] }
} },
end: { $reduce: {
input: "$userAttempts",
initialValue: oldDate,
in: { $max: [{ $reduce: {
input: "$$this.timelog",
initialValue: oldDate,
in: { $max: ["$$this.ended", "$$value"] }
} }, "$$value"] }
} }
}
},
{
$match: {
begin: { $lt: req.body.ended },
end: { $gt: req.body.started }
}
}
], { allowDiskUse: true });
newDate is today and oldDate is an arbitrary date in the past.
I had to chain 2 reduce because "$$this.timelog.0.started" would always return nothing. Don't really know why though.
Solution 2
_db.attempts.aggregate([
{ $match: {
query
},
{
$addFields: {
firstTimelog: { $arrayElemAt: ["$timelog", 0] },
lastTimelog: { $arrayElemAt: ["$timelog", -1] }
}
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
begin: { $min: "$firstTimelog.started" },
end: { $max: "$lastTimelog.ended" },
userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog"} }
}
},
{
$match: {
begin: { $lt: req.body.ended },
end: { $gt: req.body.started }
}
}
], { allowDiskUse: true });
This one is a lot more straight forward and seems simpler, but oddly enough, from my testing, Solution 1 is always quicker at least in the object distribution for my project.

Categories