How to aggregate the marks of all the subjects in mongoDB - node.js

I have the collection with following data
{
"_id": "SG01",
"name": "Pawan",
"marks": [
{
"English": 93,
"Maths": 90,
"Hindi": 89,
"Sci": 98
}
],
"__v": 0
}
{
"_id": "SG02",
"name": "Dravid",
"marks": [
{
"English": 40,
"Maths": 67,
"Hindi": 56,
"Sci": 45
}
],
"__v": 0
}
{
"_id": "SG03",
"name": "Kartik",
"marks": [
{
"English": 65,
"Maths": 77,
"Hindi": 80,
"Sci": 79
}
],
"__v": 0
}
I would like to perform the operation in which marks should be displayed as total_marks of a particular student.
As I'm newbie with mongo and know how to perform basic aggregation with sum but wasn't able to understand with arrays.. However I tried but failed to get the result.

You can use below aggregation:
db.col.aggregate([
{
$unwind: "$marks"
},
{
$project: {
_id: 1,
name: 1,
marks: {
$objectToArray: "$marks"
}
}
},
{
$project: {
_id :1,
name: 1,
total_marks: {
$reduce: {
input: "$marks",
initialValue: 0,
in: { $add : ["$$value", "$$this.v"] }
}
}
}
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
total_marks: { $sum: "$total_marks" }
}
}
])
Since your marks are stored as an object you should use $objectToArray to get an array of subjects. Then you can use $reduce to sum all subjects for one student.

Related

Mongodb aggregation to pass both a matched array and an unmatched array

I've got a MongoDB / Nodes aggregation that looks a little like this (there are other values in there, but this is the basic idea).
[
{
'$unwind': {
'path': '$Vehicles'
}
},
{
'$match': {
'Vehicles.Manufacturer': 'FORD'
}
},
{
'$facet': {
'makes': [
{
'$group': {
'_id': '$Vehicles.Manufacturer',
'count': {
'$sum': 1
}
}
}
]
}
},
{
'$project': {
'makes': {
'$sortArray': {
'input': '$makes',
'sortBy': 1
}
}
}
}
]
This works fine. But I would also like to pass an unmatched list through. IE an an array of vehicles whose Manufacturer = FORD and an other list of all Manufacturer.
Can't get it to work. Any ideas please?
Thanks in advance.
Edit:-
The current output looks like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
]
}]
and ideally it would look something like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
],
"unfiltered_makes": [
{
"_id": "ABARTH",
"count": 1
},
{
"_id": "AUDI",
"count": 7
},
{
"_id": "BMW",
"count": 2
},
{
"_id": "CITROEN",
"count": 4
},
{
"_id": "DS",
"count": 1
},
{
"_id": "FIAT",
"count": 1
}.... etc
]
}]
The data looks a bit like this:
"Vehicles": [
{
"Id": 1404908,
"Manufacturer": "MG",
"Model": "3",
"Price": 11995 .... etc
},{
"Id": 1404909,
"Manufacturer": "FORD",
"ManufacturerId": 34,
"Model": "Focus",
"Price": 12000 .... etc
} ... etc
]
In this case you can do something like:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$facet: {
makes: [{$match: {_id: "FORD"}}],
unfiltered_makes: [{$group: {_id: 0, data: {$push: "$$ROOT"}}}]
}
},
{$project: {makes: 1, unfiltered_makes: "$unfiltered_makes.data"}}
])
See how it works on the playground example
Another option is:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$group: {
_id: 0,
unfiltered_makes: {$push: "$$ROOT"},
makes: {$push: {$cond: [{$eq: ["$_id", "FORD"]}, "$$ROOT", "$$REMOVE"]}}
}
}
])
See how it works on the playground example
Here's another way to do it using "$function" to generate a histogram of "Manufacturer" and format the returned array. The javascript function only traverses the "Vehicles" array once, so this may be fairly efficient, although I did not do algorithm timing comparisons on a large collection.
N.B.: I'm a javascript noob and there may be a better way to do this.
db.collection.aggregate([
{
"$set": {
"unfiltered_makes": {
"$function": {
// generate histogram of manufacturers and format output
"body": "function(makes) {const m = new Object();makes.forEach((elem) => {m[elem.Manufacturer] = m[elem.Manufacturer] + 1 || 1});return Object.entries(m).map(([make, count]) => {return {'_id':make, 'count':count}})}",
"args": ["$Vehicles"],
"lang": "js"
}
}
}
},
{
"$project": {
"_id": 0,
"unfiltered_makes": 1,
"makes": {
"$filter": {
"input": "$unfiltered_makes",
"as": "make",
"cond": {
"$eq": [
"$$make._id",
// your search "Manufacturer" goes here
"FORD"
]
}
}
}
}
}
])
Try it on mongoplayground.net.

Include array to mongoDB query with group()

I need to add the paramater sector as an array in the group () statement.
I have the following code:
await Escaneado.aggregate([
{
$match: {
$and: [
{ "gestion": id },
{ "disponible": true }
]
},
},
{
$group: {
_id: {
code:"$codigo",
quantityTarget:"$cantidadObjetivo",
},
quantityTotalScanded : { $sum: "$cantidad" }
}
},
{
$addFields:{
difference:{ $subtract: ["$quantityTotalScanded", "$_id.quantityTarget"]}
}
},
])
output:
{
"ok": true,
"escaneadosDB": [
{
"_id": {
"code": "0V3011123A00",
"quantityTarget": 36
},
"quantityTotalScanded": 36,
"difference": 0
},
{
"_id": {
"code": "0V3011123B00",
"quantityTarget": 36
},
"quantityTotalScanded": 4,
"difference": -32
},
{
"_id": {
"code": "0V3012121D00",
"quantityTarget": 56
},
"quantityTotalScanded": 56,
"difference": 0
}
]}
output expected:
{
"ok": true,
"escaneadosDB": [
{
"_id": {
"code": "0V3011123A00",
"quantityTarget": 36,
"sector": ["A", "B", "C"]
},
"quantityTotalScanded": 36,
"difference": 0
},
{
"_id": {
"code": "0V3011123B00",
"quantityTarget": 36,
"sector": ["A"]
},
"quantityTotalScanded": 4,
"difference": -32
},
{
"_id": {
"code": "0V3012121D00",
"quantityTarget": 56,
"sector": ["A", "B"]
},
"quantityTotalScanded": 56,
"difference": 0
}
]}
I think I can add it as an array, but i do not know how implement! .
The sectors are different parameters, therefore I cannot use it as "_id". I need total quantity and the sectors in the query.
How could I do this with mongo?
this worked for me:
add this in group() sentence:
sectors: { $push: { sector: "$sector" } }
all code:
await Escaneado.aggregate([
{
$match: {
$and: [
{ "gestion": id },
{ "disponible": true }
]
},
},
{
$group: {
_id: {
code:"$codigo",
quantityTarget:"$cantidadObjetivo",
},
quantityTotalScanded : { $sum: "$cantidad" },
sectors: { $push: { sector: "$sector" } }
}
},
{
$addFields:{
difference:{ $subtract: ["$quantityTotalScanded", "$_id.quantityTarget"]}
}
},
{$sort: {"cantidadTotal": -1}},

How to sum values of every document based of filed name using Mongodb and NodeJs

based on other solution
I have the following data stored on my mongo instance and i am trying to sum from each document the occurrences of each word looping on all documents, so final result should be aggregation by name with sum of all occurrences.
[
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88ad0"
},
"cars": 1,
"collision": 1,
"crash": 1,
"eleven": 1,
"injured": 1,
"involving": 1
},
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88b96"
},
"injured": 1,
"and": 1,
"man": 1,
"attack": 1,
"ashfield": 1,
"dog": 1,
"killed": 1,
"labrador": 1,
"sutton": 1
},
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88ad2"
},
"the": 1,
"'var": 1,
"accountable'": 1,
"day": 1,
"goal": 1,
"have": 1,
"lacazette's": 1,
"match": 1,
"should": 1,
"stood": 1
},
....
and i execute the following Nodejs execution using MongoDB driver:
await db.collection('occurrences').aggregate([{
$project: {
_id: 0,
fields: {
$filter: {
input: { $objectToArray: "$$ROOT" },
cond: { $eq: [
{ $type: "$$this.v"
},
"double"
] }
}
}
}
},
{
$unwind: "$fields"
},
{
$group: {
_id: "$fields.k",
total: {
$sum: "$fields.v"
}
}
},
{
$group: {
_id: null,
aggregates: {
$push:
{ k: "$_id",
v: "$total"
}
}
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$aggregates" }
}
}]).toArray(function (err, docs) {
console.log(docs)
})
now my wish is to sum all word occurrences into object as follows:
{injured: 2, attack: 1, ....}
currently i am getting an empty array while i am printing console.log
I found the mistake, in my case all i need to change is
cond: { $eq: [
{ $type: "$$this.v"
},
"double"
] }
into
cond: { $eq: [
{ $type: "$$this.v"
},
"int"
] }

Grouping objects inside an array - MongoDB Aggregation

I am using a training grades database from MongoDB. It is structured as follows.
"_id": {
"$oid": "56d5f7eb604eb380b0d8d8fa"
},
"class_id": {
"$numberDouble": "173"
},
"scores": [
{
"score": {
"$numberDouble": "19.81430597438296"
},
"type": "exam"
},
{
"score": {
"$numberDouble": "16.851404299968642"
},
"type": "quiz"
},
{
"score": {
"$numberDouble": "60.108751761488186"
},
"type": "homework"
},
{
"score": {
"$numberDouble": "22.886167083915776"
},
"type": "homework"
}
],
"student_id": {
"$numberDouble": "4"
}
}
I am trying to run aggregation which returns all documents grouped first by class_id and then by student_id with all homework scores like the following.
{
class_id: 3,
all_scores: [
{
student_id: 110,
scores : [
{
type: "homework",
score: 89.98
},
{
type: "homework",
score: 90.98
},
]
},
{
student_id:190,
scores : [
{
type: "homework",
score: 18.98
},
{
type: "homework",
score: 99.98
},
]
},
]
}
I am running the following aggregation function.
[
{
'$unwind': {
'path': '$scores'
}
}, {
'$match': {
'scores.type': 'homework'
}
}, {
'$group': {
'_id': '$class_id',
'scores': {
'$push': {
'type': '$scores.type',
'score': '$scores.score',
'student_id': '$student_id'
}
}
}
}
]
But it is returning the following result:
{
_id: 3,
scores: [
{
"type": "homework",
"score": 89.98,
"student_id": 110
},
{
"type": "homework",
"score": 90.98,
"student_id": 110
},
{
"type": "homework",
"score": 18.98,
"student_id": 190
},
{
"type": "homework",
"score": 99.98,
"student_id": 190
},
]
}
If even if there are multiple objects in the scores array, it is not combining them with the student_id group and shows them separate. I am not sure of what I should add to the aggregation. Any help would be appreciated!
Mongo Playground Link
I think this is the precise format you wanted.
The aggregation pipeline:
[
{
"$unwind": {
"path": "$scores"
}
},
{
"$match": {
"scores.type": "homework"
}
},
{
"$group": {
"_id": {
"class_id": "$class_id",
"student_id": "$student_id"
},
"scores": {
"$push": {
"type": "$scores.type",
"score": "$scores.score"
}
}
}
},
{
$group: {
_id: "$_id.class_id",
all_scores: {
$push: {
"student_id": "$_id.student_id",
scores: "$scores"
}
}
}
},
{
"$project": {
_id: 0,
class_id: "$_id",
all_scores: "$all_scores"
}
}
]
The first two stages of the pipeline I guess are simply to filter out the non-homework documents.
To perform a "nested grouping" of sorts, where not only does the data have an outer grouping over class_id but an inner grouping in the scores over student_id, first we group the data in the first $group stage over both those fields, much like described here.
The scores array in each document here will be the same as the arrays we need in each inner grouping (over student_id), so, now we can just group by the class_name (in the _id object after the result of the first group stage) and add the student_id along with the scores in each object to push in the all_scores array. Then the final $project stage is pretty trivial, just to get it in the format that we want.
Try With this Aggregate Query,
[
{
'$unwind': {
'path': '$scores'
}
}, {
'$match': {
'scores.type': 'homework'
}
}, {
'$group': {
'_id': {class_id:'$class_id',
student_id:'$student_id'},
'scores': {
'$push': {
'type': '$scores.type',
'score': '$scores.score'
}
}
}
}
]

MongoDB - How to convert every date-field in an array of objects?

I Have a bunch of sensordata stored in mongoDB. They are stored like this:
{
"data": [
{
"date": ISODate("2020-02-08T18:06:25.507+00:00"),
"temperature": 20.3,
"humidity": 53.7
},
{
"date": ISODate("2020-02-08T18:07:25.507+00:00"),
"temperature": 21,
"humidity": 54
}
]
}
The day-field is generated by new Date() with JavaScript.
Now i just want the get all the data and convert the "date"-field to a time-field. The result should look like this:
{
"data": [
{
"date": "18:06:25",
"temperature": 20.3,
"humidity": 53.7
},
{
"date": "18:07:25",
"temperature": 21,
"humidity": 54
}
]
}
So is there a way to convert every "date"-field in the array to a "time"-field by using db.collection.aggregate?
I tried using this:
db.collection.aggregate([
{},
{
"$project": {
"data.date": { $dateToString: { format: "%H:%M:%S",date: "$date" } },
"daydata.temperature": 1,
"daydata.humidity": 1
}
}
])
I know it doesnt work, because i dont have any "date"-field outside of "data". But i dont know how to reach the date-field of every data-object and convert it.
You can use $unwind then $project then $group in aggregate.
db.data.aggregate([
{ $unwind: "$data" },
{
$project: {
"data.date": {
$dateToString: { format: "%H:%M:%S", date: "$data.date" },
},
"data.temperature": 1,
"data.humidity": 1,
},
},
{
$group: {
_id: "$_id",
data: { $push: "$data" },
},
},
]);
Also, date should be ISODate
{
"data": [
{
"date": ISODate("2020-02-08T18:06:25.507+00:00"),
"temperature": 20.3,
"humidity": 53.7
},
{
"date": ISODate("2020-02-08T18:07:25.507+00:00"),
"temperature": 21,
"humidity": 54
}
]
}
Since the data.date field is a string, use the sub-string operator to extract the time part of the date field.
db.test.aggregate( [
{
$unwind: "$data"
},
{
$addFields: {
"data.time": { $substrCP: [ "$data.date", 11, 8 ] }
}
},
{
$project: { "data.date": 0 }
},
{
$group: {
_id: "$_id",
data: { $push: "$data" },
// other_fld: { $first: "$other_fld" }
}
}
] ).pretty()

Resources