mongodb - Subtracts two numbers total to return the difference - mongodb

Consider I have the following collection:
[
{
"total": 48.0,
"status": "CO"
},
{
"total": 11.0,
"status": "CA"
},
{
"total": 15916.0,
"status": "PE"
}
]
I need to realize the difference of PE status - (CO + CA).
The expected result is:
{
"_id" : null,
"total" : 15857.0
}

Use $switch to cater for different cases for your sum. Use $subtract to flip the sign for the partial sum.
db.collection.aggregate([
{
$group: {
_id: null,
total: {
"$sum": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$status",
"PE"
]
},
"then": "$total"
},
{
"case": {
$eq: [
"$status",
"CO"
]
},
"then": {
$subtract: [
0,
"$total"
]
}
},
{
"case": {
$eq: [
"$status",
"CA"
]
},
"then": {
$subtract: [
0,
"$total"
]
}
}
],
default: 0
}
}
}
}
}
])
Mongo Playground

Assuming these are the only status options, one way is to $group using $cond:
db.collection.aggregate([
{$group: {
_id: 0,
total: {
$sum: {$cond: [{$eq: ["$status", "PE"]}, "$total", {$multiply: ["$total", -1]}]}
}
}}
])
See how it works on the playground example

Related

Create a view from mongo collection

I have a mongo collection with records like
and so on.
The sample record in JSON format
[{
empId:'123',
empName:'Emp1',
shiftHours:'Regular'
},
{
empId:'123',
empName:'Emp1',
shiftHours:'Morning'
}
]
Basically an employee can work in regular shift(9am-6 pm) or morning shift (6 am-3 pm) or night shift (9pm-6 am). The hours are just example here but the idea is that the working hours are categorized in 3 shifts. I want to create a view with flat structure per employee like this
and so on.
I am trying to understand what's the best way to create such a flat view (coming from SQL background, I think a procedure/function has to be written) but not sure what's the best way to do so using No-Sql (Mongo db).
Any suggestions?
$group by empId and conditionally $sum by shiftHours.
db.collection.aggregate([
{
$group: {
_id: "$empId",
empName: {
$first: "$empName"
},
Morning: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Morning"
]
},
"then": 1,
"else": 0
}
}
},
Regular: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Regular"
]
},
"then": 1,
"else": 0
}
}
},
Evening: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Evening"
]
},
"then": 1,
"else": 0
}
}
}
}
},
{
$set: {
Morning: {
$cond: [
{
$gt: [
"$Morning",
0
]
},
"Y",
"N"
]
},
Regular: {
$cond: [
{
$gt: [
"$Regular",
0
]
},
"Y",
"N"
]
},
Evening: {
$cond: [
{
$gt: [
"$Evening",
0
]
},
"Y",
"N"
]
}
}
}
])
Mongo Playground

Aggregate and calculate with mongoDB

Hello I heard that mongoDB was very good at aggregating data compared to, for example SQL server.
I tried to translate an SQL query to a mongoDB query but it's a complete failure :
For SQL server we had like 4 minutes which is manageable and now we top at 34 minutes for 128 days using this request in mongoDB :
function(thresholdObs, days)
{
var name = "period-" + days + "j"
var startDate = new Date('2020', '00', '01');
var endDate = new Date(startDate.getTime() + 1000 * 60 * 60 * 24 *[days]);
db.getCollection('measures').aggregate([
{
$match: {
$and: [
{
measureDate: {
$gte: startDate
}
},
{
measureDate: {
$lte: endDate
}
}
]
}
},
{
$addFields: {
multiplyIahobs: {
$cond: [
{$or: [
{$eq: ["$averageIAH", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averageIAH", "$obs"] }
]
},
multiplyPressionobs: {
$cond: [
{$or: [
{$eq: ["$averagePressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averagePressure", "$obs"] }
]
},
multiplyFuitesobs: {
$cond: [
{$or: [
{$eq: ["$averageLeakage", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averageLeakage", "$obs"] }
]
},
multiplyinspiratoryPressureobs: {
$cond: [
{$or: [
{$eq: ["$inspiratoryPressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$inspiratoryPressure", "$obs"] }
]
},
multiplyexpiratoryPressureobs: {
$cond: [
{$or: [
{$eq: ["$expiratoryPressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$expiratoryPressure", "$obs"] }
]
},
enoughDays: {
$cond: [ { $gte: ["$obs", thresholdObs ] }, 1, 0]
},
missingDays: {
$cond: [ { $lt: ["$obs", thresholdObs ] }, 1, 0]
},
daysWithoutUsage: {
$cond: [ { $eq: ["$obs", 0 ] }, 1, 0]
},
daysWithData: { $sum: 1 }
}
},
{
$group: {
_id: "$deviceID",
obsUsage: { $avg: "$obs" },
enoughDays: { $sum: "$enoughDays" },
missingDays: { $sum: "$missingDays" },
daysWithoutUsage: { $sum: "$daysWithoutUsage" },
daysWithData: { $sum: "$daysWithData" },
sumobs: { $sum: "$obs" },
sumMultiplyIahobs: { $sum: "$multiplyIahobs" },
sumMultiplyPressureObs: { $sum: "$multiplyPressionobs" },
sumMultiplyLeakageObs: { $sum: "$multiplyFuitesobs" },
sumMultiplyinspiratoryPressureobs: { $sum: "$multiplyinspiratoryPressureobs" },
sumMultiplyexpiratoryPressureobs: { $sum: "$multiplyexpiratoryPressureobs" },
}
},
{
$addFields: {
fullObs: { $divide: [ "$sumobs", days ] },
daysWithoutData: { $subtract: [ days, "$daysWithData" ]},
averageIAH: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyIahobs", "$sumobs"] } ] },
averagePressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyPressureObs", "$sumobs"] } ] },
averageLeakage: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyLeakageObs", "$sumobs"] } ] },
inspiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyinspiratoryPressureobs", "$sumobs"] } ] },
expiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyexpiratoryPressureobs", "$sumobs"] } ] },
}
},
{
$project: {
_id: 1,
[name] : {
obsUsage: "$obsUsage",
enoughDays: "$enoughDays",
missingDays: "$missingDays",
daysWithoutUsage: "$daysWithoutUsage",
daysWithData: "$daysWithData",
sumobs: "$sumobs",
fullObs: "$fullObs",
daysWithoutData: "$daysWithoutData",
averageIAH: "$averageIAH",
averagePressure: "$averagePressure",
averageLeakage: "$averageLeakage",
inspiratoryPressure: "$inspiratoryPressure",
expiratoryPressure: "$expiratoryPressure"
}
}
},
{
$merge: {
into: "periods",
on: "_id",
whenMatched: "merge",
whenNotMatched: "insert"
}
}
],{allowDiskUse: true})
}
Before throwing out the baby (mongoDB) with the bathwater (the query). I came here asking if my understanding on how to write a good query is off. Should I try to alter this query to make it work under 4 minutes ? Is it possible ? How ?
NB : measures collection contains 374.670.449 documents.
Sample documents :
{
_id: ObjectId('6127a15fef44a9ed52a5bf62'),
deviceId: 5,
measureDateAdded: ISODate('2013-03-15T10:30:35.753Z'),
measureDate: ISODate('2012-03-20T06:00:00.000Z'),
obs: 20,
averageIAH: NumberDecimal('5.50'),
averagePressure: NumberDecimal('6.30'),
averageLeakage: NumberDecimal('13.00'),
inspiratoryPressure: null,
expiratoryPressure: null
},
{
_id: ObjectId('6127a15fef44a9ed52a5bfc6'),
deviceId: 5,
measureDateAdded: ISODate('2013-03-15T10:30:40.063Z'),
measureDate: ISODate('2012-06-28T05:00:00.000Z'),
obs: 197,
averageIAH: NumberDecimal('5.30'),
averagePressure: NumberDecimal('6.90'),
averageLeakage: NumberDecimal('15.00'),
inspiratoryPressure: null,
expiratoryPressure: null
},
{
_id: ObjectId('6127aa2bef44a9ed52a0922a'),
deviceId: 367959,
measureDateAdded: ISODate('2019-01-19T14:13:19.620Z'),
measureDate: ISODate('2019-01-16T11:00:00.000Z'),
obs: 375,
averageIAH: NumberDecimal('2.00'),
averagePressure: NumberDecimal('9.60'),
averageLeakage: NumberDecimal('6.00'),
inspiratoryPressure: null,
expiratoryPressure: null
}
What cost the most seems to be the group with deviceId 4+minutes

how to calculate avg, median, min, max in mongodb query?

I have ListPrice field in collection on that price Ii have to calculate min, max, median, avg of all data, active standardStatus , sold standardStatus.
I have tried to calculate using aggregation and for loop but it won't work
db.collection('selected_properties').aggregate([
{ presentation_id : ObjectId(req.body.presentation_id),
checked_status : true}
},
{
$lookup : { from :'properties', localField : 'property_id', foreignField : '_id', as : 'property_info'}
},
{
$unwind : {path : '$property_info', preserveNullAndEmptyArrays : true}
},
{
$sort : {'property_info.ListPrice' : 1}
},
{
$group:{
_id: "$user_id",
minActiveListPrice: { $min: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice','' ] } },
maxActiveListPrice: { $max: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice',0 ] } },
avgActiveListPrice: { $avg: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice','' ] } },
medianActiveListprice: { $push: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice','' ] } },
minsoldListPrice: { $min: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "S" ]},
'$property_info.ListPrice','' ] } },
maxsoldListPrice: { $max: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "S" ]},
'$property_info.ListPrice',0 ] } },
avgsoldListPrice: { $avg: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "S" ]},
'$property_info.ListPrice','' ] } },
avgPrice: { $avg: "$property_info.ListPrice" },
maxPrice: { $max: "$property_info.ListPrice" },
minPrice: { $min: "$property_info.ListPrice" },
}
median: { $push: "$property_info.ListPrice"}
}
},
db.collection('selected_properties').aggregate([
{
$match : { presentation_id : ObjectId(req.body.presentation_id),
checked_status : true}
},
{
$lookup : { from :'properties', localField : 'property_id',
foreignField : '_id', as : 'property_info'}
},
{
$unwind : {path : '$property_info', preserveNullAndEmptyArrays : true}
},
{
$sort : {'property_info.ListPrice' : 1}
},
{
$group:
{
_id: "$user_id",
minActiveListPrice: { $min: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice','' ] } },
maxActiveListPrice: { $max: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice',0 ] } },
avgActiveListPrice: { $avg: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice','' ] } },
medianActiveListprice: { $push: { $cond: [ {
$eq: [ "$property_info.StandardStatus", "A" ]},
'$property_info.ListPrice',null ] } },
avgPrice: { $avg: "$property_info.ListPrice" },
maxPrice: { $max: "$property_info.ListPrice" },
minPrice: { $min: "$property_info.ListPrice" },
median: { $push: "$property_info.ListPrice"}
}
},
{ "$project": {
"minActiveListPrice":1,
"maxActiveListPrice":1,
"avgActiveListPrice":1,
"avgPrice": 1,
"maxPrice": 1,
"minPrice": 1,
"medianActiveListpricevalue": {
$let: {
vars: {
arr: { $filter: {
input: "$medianActiveListprice",
as: "aa",
cond: {$ne:["$$aa",null]}
}},
},
in: { "$cond": {
"if": {
"$eq": [{$mod: [ {$size:"$$arr"}, 2 ]}, 0]
},
"then": {
$avg:[
{ $arrayElemAt: [ "$$arr", {$subtract:[{$divide: [ {$size:"$$arr"}, 2 ]},1]}]},
{ $arrayElemAt: [ "$$arr", {$divide: [ {$size:"$$arr"}, 2 ]}]}
]
},
"else": {
$arrayElemAt: [ "$$arr",{$floor : {$divide: [ {$size:"$$arr"}, 2 ]}}]
}
}}
}
},
"medianvalue":{ "$cond": {
"if": {
"$eq": [{$mod: [ {$size:"$median"}, 2 ]}, 0]
}
"then": {
$avg:[
{ $arrayElemAt: [ "$median", {$subtract:[{$divide: [ {$size:"$median"}, 2 ]},1]}]},
{ $arrayElemAt: [ "$median", {$divide: [ {$size:"$median"}, 2 ]}]}
]
},
"else": {
$arrayElemAt: [ "$median",{$floor : {$divide: [ {$size:"$median"}, 2 ]}}]
}
}}
} }
])

How to get count of multiple fields based on value in mongodb?

Collection exists as below:
[
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Delhi", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Chennai"}
]
Expected Output:
[
{"city": "Chennai", "currentLocationCount": 3, "baseLocationCount": 1},
{"city": "Bengaluru", "currentLocationCount": 0, "baseLocationCount": 3},
{"city": "Delhi", "currentLocationCount": 1, "baseLocationCount": 0}
]
What I have tried is:
db.getCollection('users').aggregate([{
$group: {
"_id": "$baselocation",
baseLocationCount: {
$sum: 1
}
},
}, {
$project: {
"_id": 0,
"city": "$_id",
"baseLocationCount": 1
}
}])
Got result as:
[
{"city": "Chennai", "baseLocationCount": 1},
{"city": "Bengaluru", "baseLocationCount": "3"}
]
I'm not familiar with mongo, so any help?
MongoDB Version - 3.4
Neil Lunn and myself had a lovely argument over this topic the other day which you can read all about here: Group by day with Multiple Date Fields.
Here are two solutions to your precise problem.
The first one uses the $facet stage. Bear in mind, though, that it may not be suitable for large collections because $facet produces a single (potentially huge) document that might be bigger than the current MongoDB document size limit of 16MB (which only applies to the result document and wouldn't be a problem during pipeline processing anyway):
collection.aggregate(
{
$facet:
{
"current":
[
{
$group:
{
"_id": "$currentLocation",
"currentLocationCount": { $sum: 1 }
}
}
],
"base":
[
{
$group:
{
"_id": "$baseLocation",
"baseLocationCount": { $sum: 1 }
}
}
]
}
},
{ $project: { "result": { $setUnion: [ "$current", "$base" ] } } }, // merge results into new array
{ $unwind: "$result" }, // unwind array into individual documents
{ $replaceRoot: { newRoot: "$result" } }, // get rid of the additional field level
{ $group: { "_id": "$_id", "currentLocationCount": { $sum: "$currentLocationCount" }, "baseLocationCount": { $sum: "$baseLocationCount" } } }, // group into final result)
{ $project: { "_id": 0, "city": "$_id", "currentLocationCount": 1, "baseLocationCount": 1 } } // group into final result
)
The second one works based on the $map stage instead:
collection.aggregate(
{
"$project": {
"city": {
"$map": {
"input": [ "current", "base" ],
"as": "type",
"in": {
"type": "$$type",
"name": {
"$cond": {
"if": { "$eq": [ "$$type", "current" ] },
"then": "$currentLocation",
"else": "$baseLocation"
}
}
}
}
}
}
},
{ "$unwind": "$city" },
{
"$group": {
"_id": "$city.name",
"currentLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "current" ] },
"then": 1,
"else": 0
}
}
},
"baseLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "base" ] },
"then": 1,
"else": 0
}
}
}
}
}
)

How to group query with multiple $cond?

I want to query like below, but this contains only one $cond.
How to query with two $cond?
collection.aggregate(
{
$match : {
'_id' : {$in:ids}
}
},
{
$group: {
_id: '$someField',
...
count: {$sum: { $cond: [ { $eq: [ "$otherField", false] } , 1, 0 ] }}
}
},
function(err, result){
...
}
);
You want to use a compound expression inside {$cond:[]} - something like:
collection.aggregate(
{
$match : {
'_id' : {$in:ids}
}
},
{
$group: {
_id: '$someField',
...
count: {$sum: { $cond: [ {$and : [ { $eq: [ "$otherField", false] },
{ $eq: [ "$anotherField","value"] }
] },
1,
0 ] }}
}
},
function(err, result){
...
}
);
The $and operator is documented here: http://docs.mongodb.org/manual/reference/operator/aggregation/#boolean-operators
you can add multiple $cond and multiple criterias inside $cond like this
`
collection.aggregate(
[
{
"$match": {
//matching criteria
}
},
{
"$project": {
"service": {
"$cond": {
"if": {
"$eq": [
"$foo",
"bar"
]
},
"then": "return string1",
"else": {
"$cond": {
"if": {
"$eq": [
"$foo",
"bar"
]
},
"then": "return string2",
"else": {
"$cond": {
"if": {
"$or": [
{
"$eq": [
"$foo",
"bar1"
]
},
{
"$eq": [
"$foo",
"bar2"
]
}
]
},
"then": "return string3",
"else": "$foo"
}
}
}
}
}
},
"_id": 0
}
}
]
)
`