How to get conditional average data for graph in mongodb? - node.js

Data 1:
{
"_id" : "5eb922b4c019811689c8f8e3",
"createdAt" : "2020-05-10T19:30:00.000Z",
"isManual" : false,
"value" : 0.66
}
Data 2:
{
"_id" : "5eb922b4c019811689c8f8e3",
"createdAt" : "2020-05-10T19:30:00.000Z",
"isManual" : false,
"value" : 0.52
}
Data 3:
{
"_id" : "5eb922b4c019811689c8f8e3",
"createdAt" : "2020-05-10T19:30:00.000Z",
"isManual" : true,
"value" : 0.34
}
Now I need to generate a query for getting an average of value field. Consider isManual
key for average:
Response key expected:
total_fields = 3
manual_avg = 0.34 ((0.66 + 0.52)/3)
not_manual_avg = 0.13 ((0.34)/3)

You can try below aggregation query :
db.collection.aggregate([
/** group all docs in collection */
{
$group: {
_id: null,
total_fields: { $sum: 1 }, /** count total no.of docs */
manual_avg: { $avg: { $cond: [ "$isManual", 0, "$value" ] } }, /** If 'isManual' is true pass-in 0 else actual value to average */
not_manual_avg: { $avg: { $cond: [ "$isManual", "$value", 0 ] } }
}
},
/** Optional stage */
{
$project: { _id: 0,total_fields: 1, manual_avg: { $trunc: [ "$manual_avg", 2 ] }, not_manual_avg: { $trunc: [ "$not_manual_avg", 2 ] }
}
}
])
Test : mongoplayground

You can use $group along with $sum to count for total_field and to do the sum based on isManual condition using $cond
[
{
$group: {
_id: null,
count: {
$sum: 1
},
manual: {
$sum: {
$cond: [
"$isManual",
"$value", // add $value when $isManual is true
0
]
}
},
not_manual: {
$sum: {
$cond: [
"$isManual",
0,
"$value" // add $value when $isManual is false
]
}
}
}
},
{
$project: {
_id: false,
total_fields: "$count",
manual_avg: {
$divide: [
"$manual",
"$count"
]
},
not_manual_avg: {
$divide: [
"$not_manual",
"$count"
]
}
}
}
]

Related

How to fill missing documents with values 0 in mongoDB?

I have a collection where I'm storing water dispensed for a particular day. Now for some days when the device isn't operated the data isn't stored in the database and I won't be getting the data in the collection. For example, I am querying water dispensed for the last 7 days where the device only operated for two day gives me something like this:
[{
"uID" : "12345678",
"midNightTimeStamp" : NumberInt(1645381800),
"waterDispensed" : NumberInt(53)
},
{
"uID" : "12345678",
"midNightTimeStamp" : NumberInt(1645641000),
"waterDispensed" : NumberInt(30)
}]
Converting the above two timestamps gives me data for Monday 21st February and Thursday 24th February. Now if I run the query for 21st Feb to 27th Feb something like this,
db.getCollection("analytics").find({ uID: "12345678", midNightTimeStamp: {"$in": [1645381800, 1645468200, 1645554600, 1645641000, 1645727400, 1645813800, 1645900200]}})
This returns me above two documents only, how to fill missing values for midNightTimeStamp supplied to get the document list like this which doesn't exists:
[{
"uID" : "12345678",
"midNightTimeStamp" : 1645381800,
"waterDispensed" : 53
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645468200,
"waterDispensed" : 0
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645554600,
"waterDispensed" : 0
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645641000,
"waterDispensed" : 30
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645727400,
"waterDispensed" : 0
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645813800,
"waterDispensed" : 0
},
{
"uID" : "12345678",
"midNightTimeStamp" : 1645900200,
"waterDispensed" : 0
}
Maybe something like this:
db.collection.aggregate([
{
$group: {
_id: null,
ar: {
$push: "$$ROOT"
},
mind: {
"$min": "$midNightTimeStamp"
},
maxd: {
"$max": "$midNightTimeStamp"
}
}
},
{
$project: {
ar: {
$map: {
input: {
$range: [
"$mind",
{
"$sum": [
"$maxd",
86400
]
},
86400
]
},
as: "dateInRange",
in: {
$let: {
vars: {
dateIndex: {
"$indexOfArray": [
"$ar.midNightTimeStamp",
"$$dateInRange"
]
}
},
in: {
$cond: {
if: {
$ne: [
"$$dateIndex",
-1
]
},
then: {
$arrayElemAt: [
"$ar",
"$$dateIndex"
]
},
else: {
midNightTimeStamp: "$$dateInRange",
"waterDispensed": NumberInt(0)
}
}
}
}
}
}
}
}
},
{
$unwind: "$ar"
},
{
$project: {
_id: 0,
"waterDispensed": "$ar.waterDispensed",
midNightTimeStamp: "$ar.midNightTimeStamp",
"Date": {
$toDate: {
"$multiply": [
"$ar.midNightTimeStamp",
1000
]
}
}
}
}
])
Explained:
$group the documents to find max & min for the timestamps and $push all elements in temporary array named "ar"
$project the array $mapping with a $range of generated dated between max & min with 1x day step ( 86400 ) , fill the empty elements with waterDispanced:0
$unwind the array $ar
$project only the fields we need in the final output.
playground
This is just a little different than the other answer, and it takes care to just grab the "uID" desired. Comments in the MQL explain the process.
db.collection.aggregate([
{ // The uID we want
"$match": { "uID": "12345678" }
},
{ // grab all the uID docs as "water"
// keep uID
"$group": {
"_id": null,
"water": { "$push": "$$CURRENT" },
"uID": { "$first": "$uID" }
}
},
{ // create outArray
"$set": {
"outArray": {
// by mapping time vals
"$map": {
"input": {
"$range": [ NumberInt(1645381800), NumberInt(1645900200), 86400 ]
},
"in": {
"$cond": [
{ // already have doc?
"$in": [ "$$this", "$water.midNightTimeStamp" ]
},
{ // yes! Get it!
"$arrayElemAt": [
"$water",
{ "$indexOfArray": [ "$water.midNightTimeStamp", "$$this" ] }
]
},
{ // no, create it
"uID": "$uID",
"midNightTimeStamp": "$$this",
"waterDispensed": 0
}
]
}
}
}
}
},
{ // only need outArray now
"$project": {
"_id": 0,
"outArray": 1
}
},
{ // create docs
"$unwind": "$outArray"
},
{ // hoist them
"$replaceWith": "$outArray"
},
{ // don't need _id
"$unset": "_id"
}
])
Try it on mongoplayground.net.
As of MongoDB 5.1 you can use the $densify aggregation operator to fill in missing time series data with an average or default value.
https://www.mongodb.com/docs/rapid/reference/operator/aggregation/densify/
In your case, you may need to convert your timestamp field to a date while aggregating so that you can use $densify.
You can also watch a quick explanation of $densify in this presentation from MongoDB World 2022.

Get Total Count using aggregate + facet using Mongo

I want to group my data based on event date with pagination. However what i am getting is whole record totalcount instead of eventDate count. because of this UI part is not working properly. Here is my collection sample:
{
"_id" : ObjectId("5fc4d0009a25e8cfbe306381"),
"eventDate" : ISODate("2021-11-29T01:00:00.000Z"),
"team1" : {
"tName" : "Chicago Bears",
},
"team2" : {
"tName" : "Green Bay Packers",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063ff"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "Nashville SC",
},
"team2" : {
"tName" : "Columbus Crew",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063f4"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "yyyy",
},
"team2" : {
"tName" : "xxxx",
}
}
here is my query:
db.getCollection('game').aggregate([
{ $addFields: { "newEventDate": {$dateToString:{ format: "%Y-%m-%d", date: "$eventDate" }}}},
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$facet": {
"resultData": [
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$group": {
"_id": "$newEventDate",
"games": {$push: {
team1:"$team1",
team2:"$team2"
}}
}
},
{ $sort: {eventDate: 1} },
{
$limit: 1
}
],
"pageInfo": [
{ "$count": "totalRecords" }
]}
}
]);
After executing this query this is my response:
{
"resultData" : [
{
"_id" : "2021-11-29",
"games" : [
{
"awayTeam" : {
"tName" : "Chicago Bears"
},
"homeTeam" : {
"tName" : "Green Bay Packers"
}
},
]
}
],
"pageInfo" : [
{
"totalRecords" : 3 **[here i want 2 ie total event date]**
}
]
}
$match your condition
move your $group stage outside from $facet, convert your date from string inside group, add you date in group stage because we are going to sort in next stage
$sort by eventDate ascending order
$facet, first get single record using $limit, and second part get total count of the record using $count
db.collection.aggregate([
{ $match: { eventDate: { $gte: new Date() } } },
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$eventDate"
}
},
eventDate: { $first: "$eventDate" },
games: {
$push: {
team1: "$team1",
team2: "$team2"
}
}
}
},
{ $sort: { eventDate: 1 } },
{
$facet: {
resultData: [{ $limit: 1 }],
pageInfo: [{ $count: "totalRecords" }]
}
}
])
Playground

Find out the count of sign up users according to monthly basis?

I am writing the query which will give the count of signup users on monthly basis.How much Signup is done in particular month.The month will be sort in Ascending order from January to December.Please help me to resolve this issue.The month should be in Words instead of number.My approach is given below:-
User.aggregate([{
$match: {
createdAt: {
$gte: new Date("2016-01-01")
}
}
}, {
$group: {
_id: {
"month": { "$month": "$createdAt" },
},
count:{$sum: 1}
}
},{"$sort": {"createdAt": 1}}]).exec(function(err,data){
if (err) {
console.log('Error Fetching model');
res.status(500).send();
} else {
res.send(data);
}
});
Actual Output:-
{
"_id" : {
"month" : 4
},
"count" : 1.0
}
/* 2 */
{
"_id" : {
"month" : 5
},
"count" : 8.0
}
/* 3 */
{
"_id" : {
"month" : 2
},
"count" : 1.0
}
/* 4 */
{
"_id" : {
"month" : 3
},
"count" : 1.0
}
//Expected Output
{
"_id" : {
"month" : "February"
},
"count" : 1.0
}
{
"_id" : {
"month" : "March"
},
"count" : 1.0
}
{
"_id" : {
"month" : "April"
},
"count" : 1.0
}
{
"_id" : {
"month" : "May"
},
"count" : 8.0
}
Looks like a good use-case for $switch:
db.collection.aggregate([
// your aggregation stages
{
$addFields: {
"_id.month": {
$switch: {
branches: [
{ case: { $eq: [ "$_id.month", 1 ] }, then: "January" },
{ case: { $eq: [ "$_id.month", 2 ] }, then: "February" },
{ case: { $eq: [ "$_id.month", 3 ] }, then: "March" },
{ case: { $eq: [ "$_id.month", 4 ] }, then: "April" }
// ...
],
default: "December"
}
}
}
}
])
Mongo Playground

how to perform group by in mongodb

I'm having this data in MongoDB, I want to perform group by on this data to get all the tracking numbers, under the same id in a single array format.
* 1 */
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("597057338c65c002e4285fb3"),
"Sid":2,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "122_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131427"
},
{
"TrackingNo" : "77959131403"
}
]
}
]
}
/* 3 */
{
"_id" : ObjectId("5980ae7ecc71b581b626d20b"),
"Sid":3,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "123_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131408"
},
{
"TrackingNo" : "779591314059"
},
{
"TrackingNo" : "779591315551"
}
]
}
]
}
I.e on performing group by operation on _id, which shows all tracking numbers under the same _Id in a single array, means the result set will be like this :
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}
As you have array within array, you first need to unwind twice and then group again. Following query should work. You need to project additional fields in $group and $project as required.
db.shipments.aggregate(
{$unwind:'$Carriers'},
{$unwind: '$Carriers.Tracking'},
{$group: {_id:'$_id', c: {$push: '$Carriers.Tracking'}}},
{$project:{Carriers: {Tracking: '$c'}}}
)
According to description as mentioned in above question please try executing following aggregate query into MongoDB shell as a solution to above mentioned question.
db.shipments.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: '$Carriers'
}
},
// Stage 2
{
$unwind: {
path: '$Carriers.Tracking'
}
},
// Stage 3
{
$group: {
_id: {
_id: '$_id',
Sid: '$Sid'
},
Carriers: {
$addToSet: {
TrackingNo: '$Carriers.Tracking.TrackingNo'
}
}
}
},
// Stage 4
{
$group: {
_id: '$_id',
Carriers: {
$addToSet: {
Tracking: '$Carriers'
}
}
}
},
// Stage 5
{
$project: {
_id: '$_id._id',
Sid: '$_id.Sid',
Carriers: '$Carriers'
}
}
]
);

Use calculated value for comparison in aggregation

var data_form = {
{
_id : "123",
result:{
run:10
},
result_re:{
run:10
},
result_ch:{
run:10
},
result_qm:{
run:10
}
},
{
_id : "345",
result:{
run:20
},
result_re:{
run:20
},
result_ch:{
run:20
},
result_qm:{
run:20
}
},
{
_id : "567",
result:{
run:30
},
result_re:{
run:30
},
result_ch:{
run:30
},
result_qm:{
run:30
}
}
}
var pipeline = [
{ $project: {
total: { $add: [ "$result.run", "$result_re.run", "$result_ch.run", "$result_qm.run"] } ,
discount:{
$cond: [ { $gt: [ total , 50 ] }, 1, 0]
}
}
},
{ $sort: {total: -1}},
{ $limit : 10 }
]
db.getCollection('game_users').aggregate(pipeline)
I need to compare total output with aggregation condition and counter increase if condition match.
My collection is defined in data_form variable.
total field output get from query and if that total is grater than 50 after that counter increase.
You need to specify the expression within the $cond. You cannot reference the value of another calculated field within the same pipeline stage. Either do it twice or put in separate stages. The same stage is the most efficient:
var pipeline = [
{ $project: {
total: {
$add: [
"$result.run",
"$result_re.run",
"$result_ch.run",
"$result_qm.run"
]
} ,
discount:{
$cond: [
{ $gt: [
{ $add: [
"$result.run",
"$result_re.run",
"$result_ch.run",
"$result_qm.run"
]},
50
]},
1,
0
]
}
}},
{ $sort: {total: -1}},
{ $limit : 10 }
]
Or separate the $project in two stages
var pipeline = [
{ $project: {
total: {
$add: [
"$result.run",
"$result_re.run",
"$result_ch.run",
"$result_qm.run"
]
}
}},
{ $project: {
total: 1,
discount:{
$cond: [
{ $gt: [ "$total", 50 ] }
1,
0
]
}
}},
}}
{ $sort: {total: -1}},
{ $limit : 10 }
]
This looks "prettier" but running another stage means another pass through data, so it's probably best to do in one stage.
To get the "totals" across the collection, run a separate aggregation to the paged results.
var pipeline = [
{ $group: {
_id: null,
total: {
$sum: {
$add: [
"$result.run",
"$result_re.run",
"$result_ch.run",
"$result_qm.run"
]
}
} ,
discount:{
$sum: {
$cond: [
{ $gt: [
{ $add: [
"$result.run",
"$result_re.run",
"$result_ch.run",
"$result_qm.run"
]},
50
]},
1,
0
]
}
}
}}
];
Do not try and get both the paged results and the total in the same response since that is not how you do it. These should be run separately as attempting to return in one result will certainly break the BSON limit in real world use cases.

Resources