Include array to mongoDB query with group() - node.js

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}},

Related

Aggregate total and unique counts based on value type and unique visitorId - MongoDB

Similar to another question I had (Here). But now I'm trying to count unique and total events on daily basis for each event type, based on the following data shape:
{
username: "jack",
events: [
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "01234567-0ebb-4238-8bf7-01234567"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T12:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T11:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T12:26:11.214Z",
visitorInfo: {
visitorId: "01234567-0ebb-4238-8bf7-01234567"
}
},
{
eventType: "party",
createdAt: "2022-01-30T10:26:11.214Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "party",
createdAt: "2022-01-30T10:16:11.214Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "meeting",
createdAt: "2022-01-30T12:36:11.224Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "meeting",
createdAt: "2022-01-30T11:46:11.314Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
}
]
}
I'm trying to count events (all and unique ones based on visitorId) on date (daily).
This is what I have so far (thanks to #R2D2's guide on the approach):
Event.aggregate([
{ $match: { username: 'jack' } },
{ $unwind: "$events" },
{
$project: {
totalPartyEvents: {
$cond: [
{
$eq: ["$events.eventType", "party"],
},
1,
0,
],
},
uniquePartyEvents: { // where I'm stuck. I need to count unique events based on visitorId on current date for 'party' event type.
$cond: [
{
$eq: ["$events.eventType", "party"],
},
1,
0,
],
},
totalMeetingEvents: {
$cond: [
{
$eq: ["$events.eventType", "meeting"],
},
1,
0,
],
},
uniqueMeetingEvents: { // do the same for other events. maybe there's a better way to combine these (with facets).
$cond: [
{
$eq: ["$events.eventType", "meeting"],
},
1,
0,
],
},
date: "$events.createdAt",
},
},
{
$group: {
_id: {
$dateToString: { format: "%Y-%m-%d", date: "$date" },
},
totalPartyEvents: {
$sum: "$totalMeetingEvents",
},
uniquePartyEvents: {
$sum: "$totalMeetingEvents",
},
totalMeetingEvents: {
$sum: "$totalMeetingEvents",
},
uniqueMeetingEvents: {
$sum: "$uniqueMeetingEvents",
},
},
},
{
$project: {
date: "$_id",
uniquePartyEvents: 1,
totalPartyEvents: 1,
totalMeetingEvents:1,
uniqueMeetingEvents: 1,
},
},
{
$group: {
_id: "0",
dateAndEventFrequency: {
$push: "$$ROOT",
},
},
},
{
$project: {
_id: 0,
dateAndEventFrequency: 1,
},
},
]);
I tried using $addToSet but it's not used with $project (it works with $group).
Any new approach is welcome based on the data shape and the desired result I'm expecting. I used $project because I was already using it.
Basically what I'm hoping to get in the end:
dateAndEventFrequency: [
{
_id: "2022-01-23",
totalPartyEvents: 3,
uniquePartyEvents: 2,
totalMeetingEvents: 3,
uniqueMeetingEvents: 2,
date: "2022-01-23",
},
{
_id: "2022-01-30",
totalPartyEvents: 2,
uniquePartyEvents: 1,
totalMeetingEvents: 2,
uniqueMeetingEvents: 1,
date: "2022-01-30",
},
]
I'm using Mongoose and Nodejs. Any help or guidance is appreciated. Thanks!
mongo playground
db.collection.aggregate([
{
$match: {
username: "jack"
}
},
{
"$unwind": "$events"
},
{
"$match": {
"events.eventType": {
"$in": [
"meeting",
"party"
]
}
}
},
{
"$group": {
"_id": {
date: {
"$dateToString": {
format: "%Y-%m-%d",
date: "$events.createdAt"
}
},
"visitorId": "$events.visitorInfo.visitorId",
"eventType": "$events.eventType"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": {
"date": "$_id.date",
"eventType": "$_id.eventType"
},
"uniqueTotal": {
"$sum": 1
},
total: {
"$sum": "$count"
}
}
},
{
"$group": {
"_id": "$_id.date",
"partyUniqueTotal": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"party"
],
},
"$uniqueTotal",
0
]
}
},
"totalPartyEvents": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"party"
],
},
"$total",
0
]
}
},
"meetingUniqueTotal": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"meeting"
],
},
"$uniqueTotal",
0
]
}
},
"totalmeetingEvents": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"meeting"
],
},
"$total",
0
]
}
}
}
}
])

Fill missing records in mongo aggregate

I have a collection request
{
_Id: '5b8c0f3204a10228b00a1745,
createdAt: '2018-09-07T17:18:40.759Z',
type: "demo" , //["demo","free-try","download",...]
}
And I have a query for fetching the daily number for a specific type.
Query
Model.aggregate([
{
$match: { $expr: { $and: filters } },
},
{
$project: {
day: { $substr: ["$createdAt", 0, 10] },
type: 1,
createdAt: 1,
},
},
{
$group: {
_id: {
day: "$day",
type: "$type",
},
total: { $sum: 1 },
},
},
{
$sort: { _id: 1 },
},
{
$project: {
_id: "$_id.day",
date: "$_id.day",
type: "$_id.type",
total: 1,
},
}
])
So I get these results :
[
{
"total": 1,
"_id": "2021-01-06",
"date": "2021-01-06",
"type": "print"
},
{
"total": 1,
"_id": "2021-01-13",
"date": "2021-01-13",
"type": "download"
},
{
"total": 1,
"_id": "2021-03-09",
"date": "2021-03-09",
"type": "test"
},
{
"total": 2,
"_id": "2021-03-29",
"date": "2021-03-29",
"type": "demo"
},
{
"total": 1,
"_id": "2021-04-20",
"date": "2021-04-20",
"type": "test"
},
{
"total": 1,
"_id": "2021-04-21",
"date": "2021-04-21",
"type": "download"
},
{
"total": 1,
"_id": "2021-04-21",
"date": "2021-04-21",
"type": "renew"
},
{
"total": 1,
"_id": "2021-04-22",
"date": "2021-04-22",
"type": "print"
},
{
"total": 2,
"_id": "2021-04-26",
"date": "2021-04-26",
"type": "renew"
},
{
"total": 1,
"_id": "2021-05-03",
"date": "2021-05-03",
"type": "test"
},
{
"total": 1,
"_id": "2021-05-05",
"date": "2021-05-05",
"type": "print"
},
{
"total": 1,
"_id": "2021-05-05",
"date": "2021-05-05",
"type": "test"
},
{
"total": 2,
"_id": "2021-05-31",
"date": "2021-05-31",
"type": "demo"
},
{
"total": 1,
"_id": "2021-06-03",
"date": "2021-06-03",
"type": "renew"
}
]
up to here, everything is fine, but when I need to fill the missing record, so for example if in '2021-06-03' I don't have any request of type "demo" I need to insert this object with a total of 0
{
"total": 0,
"_id": "2021-05-31",
"date": "2021-05-31",
"type": "demo"
}
so I add this pipeline based on a solution proposed in here
Model.aggregate([
{
$match: { $expr: { $and: filters } },
},
{
$project: {
day: { $substr: ["$createdAt", 0, 10] },
type: 1,
createdAt: 1,
},
},
{
$group: {
_id: {
day: "$day",
type: "$type",
},
total: { $sum: 1 },
},
},
{
$sort: { _id: 1 },
},
{
$project: {
_id: "$_id.day",
date: "$_id.day",
type: "$_id.type",
total: 1,
},
},
{
$group: {
_id: null,
stats: { $push: "$$ROOT" },
},
},
{
$project: {
stats: {
$map: {
input: ["2018-09-01", "2018-09-02", "2018-09-03", "2018-09-04", "2018-09-05", "2018-09-06"],
as: "date",
in: {
$let: {
vars: { dateIndex: { $indexOfArray: ["$stats._id", "$$date"] } },
in: {
$cond: {
if: { $ne: ["$$dateIndex", -1] },
then: { $arrayElemAt: ["$stats", "$$dateIndex"] },
else: { _id: "$$date", date: "$$date", total: 0,type: "download" },
},
},
},
},
},
},
},
},
{
$unwind: "$stats",
},
{
$replaceRoot: {
newRoot: "$stats",
},
},
])
but this solution adds only a single object by missing day, and I need an object per type, so any solution would be appreciated
You can simply do it with $facet
$facet helps to categorize the incoming data. So I get two arrays. One is match dates and another one is non match dates. In the match dates we need to add the condition
$concatArrays to join multiple arrays into one
$unwind to deconstruct the array
$replaceRoot to make it to root
Here is the code
db.collection.aggregate([
{
"$facet": {
"matchDate": [
{
$match: {
date: { $in: [ "2021-01-13","2021-04-21" ] }
}
},
{
$addFields: {
total: { $cond: [{ $eq: [ "$type", "demo" ]}, 0, "$total" ] }
}
}
],
"nonMatchDate": [
{
$match: {
date: { $nin: [ "2021-01-13", "2021-04-21" ] }
}
}
]
}
},
{
$project: {
combined: {
"$concatArrays": [ "$matchDate", "$nonMatchDate" ]
}
}
},
{ "$unwind": "$combined" },
{ "$replaceRoot": { "newRoot": "$combined" }}
])
Working Mongo playground

Percentage of amount in a subdocument grouped per type in Mongoose/NodeJS

I have the following MongoDB schema:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, 'Email is required.']
},
transactions: [
{
categoryName: {
type: String,
required: [true, 'Category name in transaction is required.']
},
categoryType: {
type: String,
required: [true, 'Category type in transaction is required.']
},
amount: {
type: Number,
required: [true, 'Transaction amount is required.']
}
}
]})
transactions.categoryType can only be Income or Expense. Now per queried _id, I want to return the ratio/percentage of transactions.CategoryName per Income and Expense. Meaning if I have the following data:
{
"_id": 000001,
"email": "asdasd#email.com"
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000
},
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000
}
]
}
the result that I would want is:
{ "email": "asdasd#email.com",
"Income": [["Salary", 100]],
"Expense": [["Food", 16.67],["Rent",83.33]],
}
Now, I have the following query:
return User.aggregate([
{ $match: { _id : ObjectId(request.params.id) } },
{ $unwind : "$transactions"},
{ $group : { _id : { type: "$transactions.categoryType" },
        total: {$sum : "$transactions.amount"},
transactionsArray: { $push: "$transactions"}
        }
},
{ $project: {
_id: 0,
transactionsArray:1,
    type: "$_id.type",
total:1
}
}
])
which returns a data like this:
[
{
"total": 1200,
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200,
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000,
}
],
"type": "Expense"
},
{
"total": 15000,
"transactions": [
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000,
}
],
"type": "Income"
}
]
Now, I do not know how am I going to further process the result set to divide the transactions.amount by the total to get the result that I want.
You may go with multiple steps in aggregations
$unwind to deconstruct the array
$group- first group to group by _id and $categoryType. So we can get the total amount and an amount for particular transaction. This helps to calculate the ratio.
$map helps to loop over the array and calculate the ratio
$reduce- You need comma separated string array of objects. So loop it and get the structure.
$group to group by _id only so we can get the key value pair of category type and Income/Expense when we push
$replaceRoot to make the $grp object as root which should be merged with already existing fields ($mergeObjects)
$project for remove unwanted fields
Here is the code
db.collection.aggregate([
{ "$unwind": "$transactions" },
{
"$group": {
"_id": { id: "$_id", catType: "$transactions.categoryType" },
"email": { "$first": "$email" },
"amount": { "$sum": "$transactions.amount" },
"category": {
$push: { k: "$transactions.categoryName", v: "$transactions.amount" }
}
}
},
{
$addFields: {
category: {
$map: {
input: "$category",
in: {
k: "$$this.k",
v: {
"$multiply": [
{ "$divide": [ "$$this.v","$amount" ]},
100
]
}
}
}
}
}
},
{
"$addFields": {
category: {
"$reduce": {
"input": "$category",
"initialValue": [],
"in": {
"$concatArrays": [
[
[ "$$this.k", { $toString: "$$this.v" } ]
],
"$$value"
]
}
}
}
}
},
{
"$group": {
"_id": "$_id.id",
"email": { "$first": "$email" },
"grp": { "$push": { k: "$_id.catType", v: "$category" } }
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ { "$arrayToObject": "$grp" }, "$$ROOT" ]
}
}
},
{ "$project": { grp: 0 } }
])
Working Mongo playground

how to return count value for two match condition by grouping in mongoose?

I wanted my output json to look like the below,
[ {
"_id": "IT",
"count 1": 1,
"count 2": 1,
},
{
"_id": "CSE",
"count1": 1,
"count2": 2,
},
]
I tried the below query in mongoose
{
$match: {
riskstatus: { $in: ["Closed", "Deffered"] },
riskstatus: { $in: [ "Open"] },
}
},
{
"$group": {
"_id": "$phase",
"count1": { "$sum": 1 },
"count2": { "$sum": 1 },
},
}
my input collection is
{"_id":"5df5ca73bb1c4526948e2421","comment":"test","status":"Open","phase":"CSE"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"closed","phase":"IT"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Closed","phase":"CSE"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Open","phase":"IT"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Open","phase":"CSE"}
How to retrieve two count values for the same group with the above matching condition to achieve this result?
Latest I tried is
Risk.aggregate([
{ $match: { $or: [ { status: {$in: ["Closed", "Deffered"]} }, { status: {$in: ["Open"] } } ] } },
{
"$group": {
"_id": "$phase",
" count1": { "$sum": 1 },
"count2": { "$sum": 1 },
},
}
Please use that query
db.posts.aggregate(
{
$group: {
_id: { 'id': '$phase', 'status': '$status' },
count_1: { '$sum': 1 },
}
},
{ $project: { '_id': 0, 'depart': '$_id.id', 'status': '$_id.status', 'count_1': 1 } }
, {
$group: {
_id: '$depart',
data: {
'$push': {
'depart': '$depart',
'count_1': {
$cond: [{ $eq: ["$status", 'Open'] }, '$count_1', 0]
},
'count_2': {
$cond: [{ $eq: ["$status", 'Closed'] }, '$count_1', 0]
}
,
'count_3': {
$cond: [{ $eq: ["$status", 'Deffered'] }, '$count_1', 0]
}
},
}
}
},
{ $project:
{'name':'$_id','_id':0,
openCount: { $max: "$data.count_1"},
Othercount: {
$add:[
{$max: "$data.count_2"},
{$max: "$data.count_3"}]
}
}}
)

How to aggregate the marks of all the subjects in mongoDB

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.

Resources