MongoDB group aggregation with condition in $sum - javascript

Collection of my database is something like below
[{
_id:1,
status:"active",
sale: 4,
createdAt:"2019-10-08 08:46:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 06:41:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 02:01:19"
}]
I need to group it by "day".The result which I want
[{
createdAt:"2019-10-08 02:01:19",
inactive: 2,
active:1,
salesOfActive: 4,
salesOfInactive:10
}]
I am not getting the actual result which I want any help will be highly appreciated
I had try with this but won't get an idea how i will get salesOfActive and salesOfInactive per day
{
$group: {
_id: {
day: { $dayOfMonth: "$createdAt" }
},
inActive:{$sum: { status:"inactive"}},
active:{$sum: { status:"active"}},
salesOfActive: { $sum:$sale }
}
}

Basically you need to $sum each field $conditionally here
db.collection.aggregate([
{ "$group": {
"_id": {
"$dayOfMonth": { "$dateFromString": { "dateString": "$createdAt" } }
},
"inactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 1, 0] }
},
"active": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, 1] }
},
"salesOfInactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, "$sale", 0] }
},
"salesOfActive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, "$sale"] }
}
}}
])
MongoPlayground

Related

Group documents in two by array field Mongo Aggregation

How do I group the date field one to the next one in each document?
{
_id: ObjectId('...'),
date: [ISODate('2022-05-27T00:00:00.000+00:00'), ISODate('2022-05-28T00:00:00.000+00:00') ...]
}
The desired document would like this:
{
_id: ObjectId('...'),
date1: ISODate('2022-05-27T00:00:00.000+00:00'),
date2: ISODate('2022-05-28T00:00:00.000+00:00')
}
Here's one way to do it.
db.collection.aggregate([
{ "$replaceWith": {
"$mergeObjects": [
{ "_id": "$_id" },
{ "$arrayToObject": {
"$map": {
"input": { "$range": [ 0, { "$size": "$date" } ] },
"as": "dateNum",
"in": {
"k": {
"$concat": [
"date",
{ "$toString": { "$add": [ "$$dateNum", 1 ] } }
]
},
"v": { "$arrayElemAt": [ "$date", "$$dateNum" ] }
}
}
}
}
]
}
}
])
Try it on mongoplayground.net.

Mongodb: How to pass another stage inside $group?

Expected Output:
[
{
"_id": "healty",
"doc_count": 2,
"ingredients": {
"Leaves": {
"1.2g": 1,
"1.5g": 1
},
"Spinach": {
"12g": 1,
"18g": 1
}
}
},
{
"_id": "junk",
"doc_count": 3,
"ingredients": {
"cheese": {
"100g": 1,
"120g": 2
},
"meat": {
"50g": 1,
"60g": 1,
"70g": 1
}
}
}
]
Aggregation Below: Playground1
db.collection.aggregate([
{
$group: {
"_id": "$type", // grouping the document by "type" field
"ingredients": {
$push: "$$ROOT" //want to run Playground2 aggrgtion in each of it
},
"doc_count": {
$sum: 1 // total count
}
}
}
])
After that,
I've also figured out an Another Step to convert the ingredients array after it: Playground2
But, this aggregation is for All the documents. I want to make Playground2 aggregation work for the ingredients field only in each group Object.
It's similar to combining Playground1 & Playground2. How do I do this?
db.collection.aggregate([
{
"$set": {
"ingredients": {
"$objectToArray": "$ingredients"
}
}
},
{
"$unwind": "$ingredients"
},
{
"$group": {
"_id": {
type: "$type",
ingredient: "$ingredients"
},
"count": {
"$sum": 1
},
"doc_count": {
"$addToSet": "$item"
}
}
},
{
"$group": {
"_id": {
type: "$_id.type",
ingredient: "$_id.ingredient.k"
},
"docs": {
"$push": {
k: "$_id.ingredient.v",
v: "$count"
}
},
"doc_count": {
"$push": "$doc_count"
}
}
},
{
"$group": {
"_id": "$_id.type",
"ingredients": {
"$push": {
k: "$_id.ingredient",
v: {
"$arrayToObject": "$docs"
}
}
},
"doc_count": {
"$push": {
$reduce: {
input: "$doc_count",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
}
},
{
"$set": {
"ingredients": {
"$arrayToObject": "$ingredients"
},
doc_count: {
"$size": {
"$setUnion": {
$reduce: {
input: "$doc_count",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
}
}
}
])
mongoplayground

MongoDB Aggregate sum nested number array

I have the following document structure
{
"_id": "60b7b7c784bd6c2a1ca57f29",
"user": "607c58578bac8c21acfeeae1",
"exercises": [
{
"executed_reps": [8,7],
"_id": "60b7b7c784bd6c2a1ca57f2a",
"exercise_name": "Push up"
},
{
"executed_reps": [5,5],
"_id": "60b7b7c784bd6c2a1ca57f2b",
"exercise_name": "Pull up"
}
],
}
In aggregation, I am trying to sum all the executed_reps so the end value in this example should be 25 (8+7+5+5).
Here is the code I have so far:
const exerciseStats = await UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$group: {
_id: null,
totalReps: {
$sum: {
$reduce: {
input: '$exercises.executed_reps',
initialValue: '',
in: { $add: '$$this' },
},
},
},
},
},
]);
This gives a result of 5 for totalReps. What am I doing wrong?
Well 10 minutes later I found the solution myself:
UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$project: {
total: { $sum: '$exercises.executed_reps' },
},
},
{
$group: {
_id: null,
totalExercises: { $sum: '$total' },
},
},])
Had to use $project first. :)
You can do it like this:
const exerciseStats = await UserWorkoutaggregate([
{
"$addFields": {
"total": {
"$sum": {
"$map": {
"input": "$exercises",
"as": "exercise",
"in": {
"$sum": "$$exercise.executed_reps"
}
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/5_fsPgSh8EP

MongoDb: How to get Group by's total count as a flat count instead of in an array?

This is hard for me to put in words, so I am going to try and explain with a code example:
Working MongoDb Playground
db.collection.aggregate([
{
"$facet": {
"countByStatus": [
{
"$group": {
"_id": "$status.name",
"count": {
"$sum": 1
}
}
}
],
"totalCount": [
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
}
]
}
}
])
Output:
[
{
"countByStatus": [
{
"_id": "Approved",
"count": 2
},
{
"_id": "Rejected",
"count": 1
}
],
"totalCount": [
{
"_id": null,
"count": 3
}
]
}
]
Desired Output:
[
{
"countByStatus": [
{
"_id": "Approved",
"count": 2
},
{
"_id": "Rejected",
"count": 1
}
],
"totalCount": 3 //totalCount is a flat count instead of array here
}
]
I could execute the query get the result and then pick first element of array[0].count but I want to know if this could be done natively?
It can be done in a single stage with $let:
{
$project: {
countByStatus: 1,
totalCount: {
$let: {
vars: { first: { $arrayElemAt: [ "$totalCount", 0 ] } },
in: "$$first.count"
}
}
}
}
Mongo Playground

MongoDB (mongoose) aggregate count instances of specific ObjectIDs in collection

Assuming I have a schema that looks something like this:
{
field: [{
subDoc: ObjectId,
...
}],
...
}
and I have some list of ObjectIds (user input), how would I get a count of those specific ObjectIds? For exmaple, if I have data like this:
[
{field: [ {subDoc: 123}, {subDoc: 234} ]},
{field: [ {subDoc: 234}, {subDoc: 345} ]},
{field: [ {subDoc: 123}, {subDoc: 345}, {subDoc: 456} ]}
]
and the list of ids given by the user is 123, 234, 345, I need to get a count the given ids, so a result approximating this:
{
123: 2,
234: 2,
345: 2
}
What would be the best way to go about this?
The aggregation framework itself if not going to dynamically name keys the way you have presented as a proposed output, and that probably is a good thing really. But you can probably just do a query like this:
db.collection.aggregate([
// Match documents that contain the elements
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// De-normalize the array field content
{ "$unwind": "$field" },
// Match just the elements you want
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// Count by the element as a key
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}}
])
That gives you output like this:
{ "_id" : 345, "count" : 2 }
{ "_id" : 234, "count" : 2 }
{ "_id" : 123, "count" : 2 }
But if you really want to go nuts on this, you are specifying the "keys" that you want as part of your query, so you could form a pipeline like this:
db.collection.aggregate([
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$unwind": "$field" },
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"123": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 123 ] },
"$count",
0
]
}
},
"234": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 234 ] },
"$count",
0
]
}
},
"345": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 345 ] },
"$count",
0
]
}
}
}}
])
Which is a relatively simple thing to construct that last stage in code by just processing the list of arguments:
var list = [123,234,345];
var group2 = { "$group": { "_id": null } };
list.forEach(function(id) {
group2["$group"][id] = {
"$max": {
"$cond": [
{ "$eq": [ "$_id", id ] },
"$count",
0
]
}
};
});
And that comes out more or less how you want it.
{
"_id" : null,
"123" : 2,
"234" : 2,
"345" : 2
}
Not exactly what you're asking for but it can give you an idea:
db.test.aggregate([
{
$unwind: '$field'
},
{
$group: {
_id: {
subDoc: '$field.subDoc'
},
count: {
$sum: 1
}
}
},
{
$project: {
subDoc: '$subDoc.subDoc',
count: '$count'
}
}
]);
Output:
{
"result": [
{
"_id": {
"subDoc": 456
},
"count": 1
},
{
"_id": {
"subDoc": 345
},
"count": 2
},
{
"_id": {
"subDoc": 234
},
"count": 2
},
{
"_id": {
"subDoc": 123
},
"count": 2
}
],
"ok": 1
}

Categories