Here's my collection [test].
{"_id" : "Test1", "enabled": "on", "value" : 10},
{"_id" : "Test2", "enabled": "on", "value" : 50},
{"_id" : "Test3", "enabled": "on", "value" : 10},
{"_id" : "Test4", "value" : 5},
{"_id" : "Test5", "value" : 2}
I would like to get all the total of the value and total value of the field with "enabled":"on" like these:
Desired result:
[ { _id: null,
totalValue: 77,
totalEnabled: 70
} ]
Here's what i have so far but no luck.
db.collection('test').aggregate({
$group: {
_id: null,
totalValue : {
$sum: "$value"
},
totalEnabled: $sum : {"enabled":{$exists:true}}
}
}, function(err, result) {
if (err) return console.dir(err)
console.log(result);
});
You were close but $exists doesn't function is aggregation and has a different function. What you were looking for is $cond
db.items.aggregate([
{$group: {
_id: null,
totalValue: {$sum: "$value"},
enabledValue: {$sum: {
$cond: [
// Condition to test
{$eq: ["$enabled", "on"] },
// True
"$value",
// False
0
]
}}
}}
])
The usage is to provide a different value depending on whether the condition is evaluated to true or false.
Related
I have the following aggregate that displays all the duplicates in my DB:
db.Articles.aggregate([
{"$group" : { "_id": "$url", "count": { "$sum": 1 } } },
{"$match": {"_id" :{ "$ne" : null } , "count" : {"$gt": 1} } },
{"$project": {"url" : "$_id", "_id" : 0} }
]);
My question is, how do I delete the results after running this aggregate?
I have nested data as below,
{
"_id" : ObjectId("5a30ee450889c5f0ebc21116"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "8",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a30eecd3e3457056c93f7af"),
"score" : 20,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a30eefd3e3457056c93f7b0"),
"score" : 10,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a337e53341bf419040865c4"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a337ee2341bf419040865c7"),
"score" : 75,
"rating" : "Very Good"
},
{
"_id" : ObjectId("5a3380b583dde50ddcea350e"),
"score" : 72,
"rating" : "Very Good"
}
]
},
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a57"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "5",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a59"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a37667aee64bce1b14747d2"),
"score" : 74,
"rating" : "Good"
},
{
"_id" : ObjectId("5a3766b3ee64bce1b14747dc"),
"score" : 74,
"rating" : "Good"
}
]
}
We are trying to perform aggregation using this,
db.fbresults.aggregate([{$match:{academicyear:"2017-18",fdept:'Comp'}},{$group:{_id: {fname: "$fname", rating:"$fbValueList.rating"},count: {"$sum":1}}}])
and we get result like,
{ "_id" : { "fname" : "ABC", "rating" : [ "Fair","Fair","Excellent","Very Good", "Very Good", "Excellent", "Good", "Good" ] }, "count" : 2 }
but we are expecting result like,
{ "_id" : { "fname" : "ABC", "rating_group" : [
{
rating: "Excellent"
count: 2
},
{
rating: "Very Good"
count: 2
},
{
rating: "Good"
count: 2
},
{
rating: "Fair"
count: 2
},
] }, "count" : 2 }
We want to get individual faculty group by their name and inside that group by their rating response and count of rating.
We have already tried this one but we did not the result.
Mongodb Aggregate Nested Group
This should get you going:
db.collection.aggregate([{
$match: {
academicyear: "2017-18",
fdept:'Comp'
}
}, {
$unwind: "$fbValueList" // flatten the fbValueList array into multiple documents
}, {
$group: {
_id: {
fname: "$fname",
rating:"$fbValueList.rating"
},
count: {
"$sum": 1 // this will give us the count per combination of fname and fbValueList.rating
}
}
}, {
$group: {
_id: "$_id.fname", // we only want one bucket per fname
rating_group: {
$push: { // we push the exact structure you were asking for
rating: "$_id.rating",
count: "$count"
}
},
count: {
$avg: "$count" // this will be the average across all entries in the fname bucket
}
}
}])
This is a long aggregation pipeline, there may be some aggregations that are un-necessary, so please check and discard whichever are irrelevant.
NOTE: This will only work with Mongo 3.4+.
You need to use $unwind and then $group and $push ratings with their counts.
matchAcademicYear = {
$match: {
academicyear:"2017-18", fdept:'Comp'
}
}
groupByNameAndRating = {
$group: {
_id: {
fname: "$fname", rating:"$fbValueList.rating"
},
count: {
"$sum":1
}
}
}
unwindRating = {
$unwind: "$_id.rating"
}
addFullRating = {
$addFields: {
"_id.full_rating": "$count"
}
}
replaceIdRoot = {
$replaceRoot: {
newRoot: "$_id"
}
}
groupByRatingAndFname = {
$group: {
_id: {
"rating": "$rating",
"fname": "$fname"
},
count: {"$sum": 1},
full_rating: {"$first": "$full_rating"}
}
}
addFullRatingAndCount = {
$addFields: {
"_id.count": "$count",
"_id.full_rating": "$full_count"
}
}
groupByFname = {
$group: {
_id: "$fname",
rating_group: { $push: {rating: "$rating", count: "$count"}},
count: { $first: "$full_rating"}
}
}
db.fbresults.aggregate([
matchAcademicYear,
groupByNameAndRating,
unwindRating,
addFullRating,
unwindRating,
replaceIdRoot,
groupByRatingAndFname,
addFullRatingAndCount,
replaceIdRoot,
groupByFname
])
Here is my item model.
const itemSchema = new Schema({
name: String,
category: String,
occupied: [Number],
active: { type: Boolean, default: true },
});
I want to filter 'occupied' array. So I use aggregate and unwind 'occupied' field.
So I apply match query. And group by _id.
But if filtered 'occupied' array is empty, the item disappear.
Here is my code
Item.aggregate([
{ $match: {
active: true
}},
{ $unwind:
"$occupied",
},
{ $match: { $and: [
{ occupied: { $gte: 100 }},
{ occupied: { $lt: 200 }}
]}},
{ $group : {
_id: "$_id",
name: { $first: "$name"},
category: { $first: "$category"},
occupied: { $addToSet : "$occupied" }
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Here is example data set
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 95, 100, 145, 200 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
The result above query
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
}
]
and the result I want
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
]
how could I do this??
Thanks in advance.
In the simplest form, you keep it simply by not using $unwind in the first place. Your conditions applied imply that you are looking for the "unique set" of matches to specific values.
For this you instead use $filter, and a "set operator" like $setUnion to reduce the input values to a "set" in the first place:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$filter": {
"input": { "$setUnion": [ "$occupied", []] },
"as": "o",
"cond": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
}
}
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Both have been around since MongoDB v3, so it's pretty common practice to do things this way.
If for some reason you were still using MongoDB 2.6, then you could apply $map and $setDifference instead:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$setDifference": [
{ "$map": {
"input": "$occupied",
"as": "o",
"in": {
"$cond": {
"if": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
},
"then": "$$o",
"else": false
}
}
}},
[false]
]
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
It's the same "unique set" result as pulling the array apart, filtering the items and putting it back together with $addToSet. The difference being that its far more efficient, and retains ( or produces ) an empty array without any issues.
The docs are simple as:
[
{'id': '1', 'type': 'a', 'startedAt': '2017-06-11'},
{'id': '2', 'type': 'b', 'startedAt': ''},
{'id': '3', 'type': 'b', 'startedAt': '2017-06-11'}
]
And the expected aggregated result:
[
{'type': 'a', 'started': true, 'count': 1},
{'type': 'b', 'started': true, 'count': 1},
{'type': 'b', 'started': false, 'count': 1}
]
How to get above result with mongodb nodejs driver?
I've tried like below, but it didn't work ('started' was always null):
db.collection('docs').group(
{'type': '$type', 'started': {
$cond: [{$eq: ['$startedAt': '']}, false, true ]
}},
{},
{'total': 0},
'function(curr, result) {result.total++}'
)
You use .aggregate() here and not .group(), which is a different function altogether:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ "$startedAt", "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
The $gt operator returns true when the condition is met. In this case any content in a string is "greater than" an empty string.
If the field is actually "not present at all" then we can adapt with $ifNull. This gives a default have if the property does not actually exist, or otherwise evaluates to null.
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
This would produce:
{ "_id" : { "type" : "b", "started" : true }, "count" : 1 }
{ "_id" : { "type" : "b", "started" : false }, "count" : 1 }
{ "_id" : { "type" : "a", "started" : true }, "count" : 1 }
You can optionally $project afterwards to change the fields from being within _id in the results, but you really should not since this means an additional pass through results, when you can just as easily access the values anyway.
So just .map() on the result:
console.log(
results.map(function(r) {
return { type: r._id.type, started: r._id.started, count: r.count }
})
);
But with $project:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"tcount": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"type": "$_id.type",
"started": "$_id.started",
"count": "$tcount"
}}
],function(err, results) {
console.log(results);
})
Resulting in your desired format
{ "type" : "b", "started" : true, "count" : 1 }
{ "type" : "b", "started" : false, "count" : 1 }
{ "type" : "a", "started" : true, "count" : 1 }
For reference, the correct usage with .group() would be:
db.collection('docs').group(
function(doc) {
return {
"type": doc.type,
"started": (
(doc.hasOwnProperty('startedAt') ? doc.startedAt : "") > ""
)
}
},
[],
{ "count": 0 },
function(curr,result) {
result.count += 1
},
function(err,results) {
console.log(results);
}
);
Which returns:
[
{ "type" : "a", "started" : true, "count" : 1 },
{ "type" : "b", "started" : false, "count" : 1 },
{ "type" : "b", "started" : true, "count" : 1 }
]
But you really should no use that since .group() relies on JavaScript evaluation that runs much slower than what you can do with .aggregate()
I have an array like this:
{
"_id" : ObjectId("581b7d650949a5204e0a6e9b"),
"types" : [
{
"type" : ObjectId("581b7c645057c4602f48627f"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
},
{
"type" : ObjectId("581ca0e75b1e3058521a6d8c"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
}
],
"__v" : 0
},
{
"_id" : ObjectId("581b7d650949a5204e0a6e9c"),
"types" : [
{
"type" : ObjectId("581b7c645057c4602f48627f"),
"quantity" : 4,
"_id" : ObjectId("581b7d650949a5204e0a6e9e")
}
],
"__v" : 0
}
And I want to create a query that will return me the elementswhere the array of types ALL match a $in array.
For example:
query([ObjectId("581b7c645057c4602f48627f"), ObjectId("581ca0e75b1e3058521a6d8c")])
should return elements 1 and 2
query([ObjectId("581b7c645057c4602f48627f")])
should return element 2
query([ObjectId("581ca0e75b1e3058521a6d8c")])
should return nothing
I tried
db.getCollection('elements').find({'types.type': { $in: [ObjectId("581ca0e75b1e3058521a6d8c")]}})
But it returns the elements if only one types matches
You may have to use aggregation as $in and $elematch will return only matching elements. Project stage does set equals to create a all match flag and matches in the last stage with true value.
aggregate([ {
$project: {
_id: 0,
isAllMatch: {$setIsSubset: ["$types.type", [ObjectId("581b7c645057c4602f48627f")]]},
data: "$$ROOT"
}
}, {
$match: {
isAllMatch: true
}
}])
Sample Output
{
"isAllMatch": true,
"data": {
"_id": ObjectId("581b7d650949a5204e0a6e9c"),
"types": [{
"type": ObjectId("581b7c645057c4602f48627f"),
"quantity": 4,
"_id": ObjectId("581b7d650949a5204e0a6e9e")
}],
"__v": 0
}
}
Alternative version:
This version combines both project and match stages into one $redact stage with $cond operator to decide whether to keep or prune the elements.
aggregate([{
"$redact": {
"$cond": [{
$setIsSubset: ["$types.type", [ObjectId("581b7c645057c4602f48627f")]]
},
"$$KEEP",
"$$PRUNE"
]
}
}])