mongodb $sum only postive Numbers - node.js

is their a way i can sum only positive numbers and avoid those with negative in my MongoDB aggregate query i.e
aggregate([
{
$match: { },
},
{ $group: { _id: '$id', total: { $sum: '$amount' } } },
])

You can use $cond operator and simply $sum 0 when the number is negative:
{ $group: { _id: '$id', total: { $sum: { $cond: [ { $gt: [ '$amount', 0 ] }, '$amount', 0 ] } } } }

Related

How to find array greater than document size in MongoDB

I have schema like below
[
{
id:"111"
tags:[222,333,444,555]
},
{
id: "222"
tags:[312,345,534]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I want to find all documents where tags array size is greater than document size returned by $match in aggregation pipeline, so in above Ex. the number of documents are 3 so i want to return all documents having tags array size greater that 3
[
{
id:"111"
tags:[222,333,444,555]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I am using aggregation pipeline to process other info, I am stuck at how to have store document size so that i can find all tags greater than document size
below is query which i am using, i want to do it in aggregation and in one call
.aggregate([
{
"$match":{
"ids":{
"$in":[
"111",
"222",
"333"
]
}
}
})]
Facet helps you to solve this problem.
$facet helps to categorize the incoming documents. We use totalDoc for counting the document and allDocuments for getting all the documents
$arrayElemAt helps to get the first object from totalDoc where we already know that only one object should be inside the totalDoc. Because when we group it, we use _id:null
$unwind helps to de-structure the allDocuments array
Here is the code
db.collection.aggregate([
{
$facet: {
totalDoc: [
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
],
allDocuments: [
{
$project: {
tags: 1
}
}
]
}
},
{
$addFields: {
totalDoc: {
"$arrayElemAt": [
"$totalDoc",
0
]
}
}
},
{
$unwind: "$allDocuments"
},
{
$addFields: {
sizeGtDoc: {
$gt: [
{
$size: "$allDocuments.tags"
},
"$totalDoc.count"
]
}
}
},
{
$match: {
sizeGtDoc: true
}
},
{
"$replaceRoot": {
"newRoot": "$allDocuments"
}
}
])
Working Mongo playground
You can try,
$match you condition
$group by null and make root array of documents and get count of root documents in count
$unwind deconstruct root array
$match tags size and count greater than or not using $expr expression match
$replaceRoot to replace root object in root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{ $unwind: "$root" },
{ $match: { $expr: { $gt: [{ $size: "$root.tags" }, "$count"] } } },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
Second option:
first 2 stages $match and $group both are same as like above query,
$project to filter root array match condition if tags size and count greater than or not, this will return filtered root array
$unwind deconstruct root array
$replaceRoot replace root object to root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{
$project: {
root: {
$filter: {
input: "$root",
cond: { $gt: [{ $size: "$$this.tags" }, "$count"] }
}
}
}
},
{ $unwind: "$root" },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
You can skip $unwind and $replaceRoot stages if you want because this query always return one document in root, so you can easily access like this result[0]['root'], you can save 2 stages processing and execution time.
You could use $facet to get two streams i.e. one with the filtered documents and the counts using $count. The resulting streams can then
be aggregated further with a $filter as follows to get the desired result
db.getCollection('collection').aggregate([
{ '$facet': {
'counts': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
{ '$count': "numberOfMatches" }
],
'docs': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
]
} },
{ '$project': {
'result': {
'$filter': {
'input': '$docs',
'cond': {
'$gt': [
{ '$size': '$$this.tags' },
{ '$arrayElemAt': ['$counts.numberOfMatches', 0] }
]
}
}
}
} }
])

mongodb match and group 2 query in one

var oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
User.aggregate([
{ $match: { isAdmin: false, isActive: true } },
{
$group: {
_id: null,
totalCount: {
$sum: 1
}
}
},
])
User.aggregate([
{ $match: { isAdmin: false, dateCreated: { $gte: oneWeekAgo }, isActive: true } },
{
$group: {
_id: null,
lastWeekTotal: {
$sum: 1
}
}
},
])
is there a way to combine 2 aggregation queries above?
I want to count all the entries in the collection and also entries that are created within a week.
Expected result:
[ { _id: null, totalCount: 100 , lastWeekTotal: 10 } ]
You can combine inside $group together like this,
The $cond operator, there are three arguments ($cond: [if check condition, then, else])
first part if condition checks your conditions using $and operator, if conditions is true then return 1 otherwise 0
User.aggregate([
{
$group: {
_id: null,
totalCount: {
$sum: {
$cond: [
{
$and: [
{ $eq: ["$isAdmin", false] },
{ $eq: ["$isActive", true] }
]
},
1,
0
]
}
},
lastWeekTotal: {
$sum: {
$cond: [
{
$and: [
{ $gte: ["$dateCreated", oneWeekAgo] },
{ $eq: ["$isAdmin", false] },
{ $eq: ["$isActive", true] }
]
},
1,
0
]
}
}
}
}
])
Playground

Mongo Aggregate - Calculating based on dates

Here is a sample document.
{"_id":{"$oid":"5e557779ed588826d84cef11"},
"meter_id":"1001",
"date":{"$date":{"$numberLong":"1509474600000"}},
"parameter_name":"hvac","voltage":{"unit":"V"},
"current":{"unit":"AMP"},
"powerFactor":{"unit":"phi"},
"angle":{"unit":"degree"},
"activePower":{"unit":"kwh"},
"reactivePower":{"unit":"kwh"},
"apparentPower":{"unit":"kwh"},
"frequency":{"unit":"hz"},
"thd":{"unit":"percentage"},
"energy":{"Energy":"5.7"},
"power":{"unit":"watt"},
And there are around 100 000 documents. I want to filter out the documents whose date is greater than the date i specify and calculate the total energy i.e energy.Energy
I used the below aggregation, but it doesn't seem to be working
const endDate = new Date(12-12-2018)
MeterData.aggregate([
{
$group: {
_id: "$meter_id",
total: { $sum: 1 },
totalEnergy: { $sum: { $toDouble: "$energy.Energy"
} },
dateSum: {
$sum: {
$toDouble: {
$not: [{
$cond: {
if: {
$gte: [
"$date", endDate
]
},
then: "$energy.Energy",
else: 0
}
}
]
}
}
}
}
}
])
Would be this:
db.collection.aggregate([
{ $match: { date: { $gte: ISODate("2016-12-12") } } },
{
$group: {
_id: "$meter_id",
total: { $sum: 1 },
totalEnergy: { $sum: "$energy.Energy" }
}
}
])
See Mongo playground

Retrieving different aggregated fields with mongoose

I am trying to wrap my head around the query which I am trying to make with mongoose on Node JS. Here is my dataset:
{"_id":{"$oid":"5e49c389e3c23a1da881c1c9"},"name":"New York","good_incidents":{"$numberInt":"50"},"salary":{"$numberInt":"50000"},"bad_incidents":"30"}
{"_id":{"$oid":"5e49c3bbe3c23a1da881c1ca"},"name":"Cairo","bad_incidents":{"$numberInt":"59"},"salary":{"$numberInt":"15000"}}
{"_id":{"$oid":"5e49c42de3c23a1da881c1cb"},"name":"Berlin","incidents":{"$numberInt":"30"},"bad_incidents":"15","salary":{"$numberInt":"55000"}}
{"_id":{"$oid":"5e49c58ee3c23a1da881c1cc"},"name":"New York","good_incidents":{"$numberInt":"15"},"salary":{"$numberInt":"56500"}}
What I am trying to do is get these values:
The most repeated city in collection
The average of bad_incidents
The maximum value of good_incidents
Maximum salary where there are no bad_incidents
I am trying to wrap my head around how I can do this in one query, because I only need one value per field. I would be glad if somebody would lead me on the right track. No need for full solution
Regards!
You may perform MongoDB aggregation with $facet operator which allows compute several aggregation at once.
db.collection.aggregate([
{
$facet: {
repeated_city: [
{
$group: {
_id: "$name",
name: {
$first: "$name"
},
count: {
$sum: 1
}
}
},
{
$match: {
count: {
$gt: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$limit: 1
}
],
bad_incidents: [
{
$group: {
_id: null,
avg_bad_incidents: {
$avg: {
$toInt: "$bad_incidents"
}
}
}
}
],
good_incidents: [
{
$group: {
_id: null,
max_good_incidents: {
$max: {
$toInt: "$good_incidents"
}
}
}
}
],
max_salary: [
{
$match: {
bad_incidents: {
$exists: false
}
}
},
{
$group: {
_id: null,
max_salary: {
$max: {
$toInt: "$salary"
}
}
}
}
]
}
},
{
$replaceWith: {
$mergeObjects: [
{
$arrayElemAt: [
"$repeated_city",
0
]
},
{
$arrayElemAt: [
"$bad_incidents",
0
]
},
{
$arrayElemAt: [
"$good_incidents",
0
]
},
{
$arrayElemAt: [
"$max_salary",
0
]
}
]
}
}
])
MongoPlayground
[
{
"_id": null,
"avg_bad_incidents": 34.666666666666664,
"count": 2,
"max_good_incidents": 50,
"max_salary": 56500,
"name": "New York"
}
]

Mongodb aggregate $push with $cond and $each

I'm trying to use $cond to conditionally $push multiple integers onto a numbers array during an aggregate $group without any success. Here is my code:
Item.aggregate(
[
{
$group: {
_id: "$_id",
numbers: {
$push: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: { $each: [10,25,50,70] },
else: null,
}
}
}
}
},
]
)
...
Is Mongo DB just not set up for this right now, or am I looking at this all wrong?
Please try it without $each as below
Item.aggregate(
[{
$group: {
_id: "$_id",
numbers: {
$push: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: [10,25,50,70] ,
else: null,
}
}
}
}
}]);
Provided answers will work but they'll add null to the array whenever else block gets executed & at the end you need to filter out the null values from the actual array (numbers) which is an additional step to do!
You can use $$REMOVE to conditionally exclude fields in MongoDB's $project.
Item.aggregate(
[{
$group: {
_id: "$_id",
numbers: { $push: { $cond: [{ $gt: ["$price.percent", 70] }, [10, 25, 50, 70], '$$REMOVE'] } } // With $$REMOVE nothing happens on else
}
}]);
REF: $cond
Have you tried:
Item.aggregate(
[
{
$group: {
_id: "$_id",
numbers: {
$push: {
$each: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: [10,25,50,70],
else: null
}
}
}
}
}
},
]
)

Resources