I am trying to count distinct(not unique) or Emp No in same department.but getting error
query failed: unknown group operator '$group'
here is my code
https://mongoplayground.net/p/UvYF9NB7vZx
db.collection.aggregate([
{
$group: {
_id: "$Department",
total: {
"$group": {
_id: "$Emp No"
}
}
}
}
])
Expected output
[
{
"_id": "HUAWEI”,
“total”:1
},
{
"_id": "THBS”,
“total”:2
}
]
THBShave two different Emp No A10088P2C and A20088P2C
HUAWEI have only one Emp No A1016OBW
so, $group is Pipeline stage, you can only use it in upper level.
But for your required output there is lots of ways i believe,
we can do something like this as well:
db.collection.aggregate([
{
$group: {
_id: {
dept: "$Department",
emp: "$Emp No"
},
total: {
"$sum": 1
}
}
},
{
$group: {
_id: "$_id.dept",
total: {
"$sum": 1
}
}
}
])
Here, in first stage we are grouping with Department and its Emp No , and also we are having count of how many Emp No is in each dept.
[this count you can remove though as we are not using it.]
result of this stage will be:
[
{
"_id": {
"dept": "THBS",
"emp": "A10088P2C"
},
"total": 2
},
{
"_id": {
"dept": "THBS",
"emp": "A20088P2C"
},
"total": 1
},
{
"_id": {
"dept": "HUAWEI",
"emp": "A1016OBW"
},
"total": 3
}
]
next on top of this part data, i'm grouping again, with the dept. which comes in $_id.dept, and making count in the same way, which gives the result in your required format.
[
{
"_id": "HUAWEI",
"total": 1
},
{
"_id": "THBS",
"total": 2
}
]
Demo
Related
I need to count the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the false value in the excluded property, how can I mount the query?
So far, I've been able to select the number of parking spaces with the excluded: false property. But are selecting from all parking lots
Note that there are two documents symbolizing a parking lot, where each has an array, called parkingSpaces, to record parking space documents.
The first document has 2 vacancies not excluded, so with the property excluded: false and the second has only one vacancy, which is not excluded either.
{
"_id": "5d752c544f4f1c0f1c93eb23",
"name": "estacionamento um",
"parkingSpace": [
{
"_id": "5d752cf54f4f1c0f1c93eb26",
"name": "vg001",
"excluded": true
},
{
"_id": "5d752cf54f4f1c0f1c93eb27",
"name": "vg001",
"excluded": false
},
{
"_id": "5d75339bc411423a9c14ac52",
"name": "vg002",
"excluded": false
}
]
},
{
"_id": "5d7706b60d354b72388a38f4",
"name": "estacionamento dois",
"parkingSpace": [
{
"_id": "5d77078a5173bb63bc87b7ca",
"name": "vg004",
"excluded": false
}
]
}
I need to add the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the value false in the excluded property.
In the end, I need to return the value 2, referring to _id: 5d752c544f4f1c0f1c93eb23 parking spaces, which have the value false in the excluded property.
So far, with the following query, I was able to select the vacancies with the excluded property with the false value, but it is selecting from all parking lots.
const registeredParkingSpaces = await Parking.aggregate([
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
returns:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 3
}
]
}
But it needs to return:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 2
}
]
}
In aggregation 1st you match with id then go for the next step you will get your desire count because at this moment you are considering entire document "parkingSpace". Below is the sample code I guess it will work for you
const ObjectId = require('mongoose').Types.ObjectId;
const registeredParkingSpaces = await Parking.aggregate([
{ $match: { _id: objectId(body.id) } }
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
This is the query through which you can get the count of total parking .
** UPDATED **
db.collection.aggregate([
{
$match: {
"parkingSpace.excluded": false,
}
},
{
$project: {
parkingSpace: 1
}
},
{
"$group": {
"_id": "$_id",
"count": {
"$sum": {
"$size": {
"$filter": {
"input": "$parkingSpace",
"as": "el",
"cond": {
"$eq": [
"$$el.excluded",
false
]
}
}
}
}
}
}
}
])
You can check the solution from this LINK
For more information about $sum : Visit the official official document
Through this you get your solution .
Is it possible to have facet to return as an object instead of an array? It seems a bit counter intuitive to need to access result[0].total instead of just result.total
code (using mongoose):
Model
.aggregate()
.match({
"name": { "$regex": name },
"user_id": ObjectId(req.session.user.id),
"_id": { "$nin": except }
})
.facet({
"results": [
{ "$skip": start },
{ "$limit": finish },
{
"$project": {
"map_levels": 0,
"template": 0
}
}
],
"total": [
{ "$count": "total" },
]
})
.exec()
Each field you get using $facet represents separate aggregation pipeline and that's why you always get an array. You can use $addFields to overwrite existing total with single element. To get that first item you can use $arrayElemAt
Model
.aggregate()
.match({
"name": { "$regex": name },
"user_id": ObjectId(req.session.user.id),
"_id": { "$nin": except }
})
.facet({
"results": [
{ "$skip": start },
{ "$limit": finish },
{
"$project": {
"map_levels": 0,
"template": 0
}
}
],
"total": [
{ "$count": "total" },
]
})
.addFields({
"total": {
$arrayElemAt: [ "$total", 0 ]
}
})
.exec()
You can try this as well
Model
.aggregate()
.match({
"name": { "$regex": name },
"user_id": ObjectId(req.session.user.id),
"_id": { "$nin": except }
})
.facet({
"results": [
{ "$skip": start },
{ "$limit": finish },
{
"$project": {
"map_levels": 0,
"template": 0
}
}
],
"total": [
{ "$count": "total" },
]
})
.addFields({
"total": {
"$ifNull": [{ "$arrayElemAt": [ "$total.total", 0 ] }, 0]
}
})
.exec()
imagine that you want to pass the result of $facet to the next stage, let's say $match. well $match accepts an array of documents as input and return an array of documents that matched an expression, if the output of $facet was just an element we can't pass its output to $match because the type of output of $facet is not the same as the type of input of $match ($match is just an example). In my opinion it's better to keep the output of $facet as array to avoid handling those types of situations.
PS : nothing official in what i said
I have the following schema:
{ "_id": {
"$oid": "58c0204d9f10810115f13e5d"
},"OrgName": "A",
"modules": [
{
"name": "test",
"fullName": "john smith",
"_id": {
"$oid": "58c0204d9f10810115f13e5e"
},
"TimeSavedPlanning": 520,
"TimeSavedWorking": 1000,
"costSaved": 0
},
{
"name": "test1",
"fullName": "john smith",
"_id": {
"$oid": "58c020f85437c22215be92cc"
},
"TimeSavedPlanning": 0,
"TimeSavedWorking": 1000,
"costSaved": 500
}
]
}
I want to aggregate the data within the "modules" array for all documents where OrgName = A and outputs the following totals.
TimeSavedPlanning = 520 (because 520 + 0 = 520)
TimeSavedWorking = 2000 (because 1000 + 1000 = 2000)
costSaved = 500 (because 0 + 500)
Just supply each field for the $group accumulators. And use the "double barreled" $sum to "sum" both from arrays, and from documents:
Model.aggregate([
{ "$match": { "OrgName": "A" } },
{ "$group": {
"_id": null,
"TimeSavedPlanning": { "$sum": { "$sum":"$modules.TimeSavedPlanning" } },
"TimeSavedWorking": { "$sum": { "$sum": "$modules.TimeSavedWorking" } },
"costSaved": { "$sum": { "$sum": { "$modules.costSaved" } }
}}
])
You have been allowed to use $sum like that since MongoDB 3.2. Since that release it has "two" functions:
Takes an "array" of values and "sums" them together.
Acts and an "accumulator" within $group to "sum" values provided from documents.
So here you use "both" functions by "reducing" the arrays down to numeric values per document, and then "accumulating" via the $group.
Of course the $match does the "selection" right at the beginning of the operation chain. Since that determines the selection of data, and you put that there for that purpose, as well as the fact you can use an "index" from that "first" stage.
I have a collection of documents holding a list of feedbacks for different items. It looks something like this:
{
{
item: "item_1"
rating: "neutral"
comment: "some comment"
},
{
item: "item_2"
rating: "good"
comment: "some comment"
},
{
item: "item_1"
rating: "good"
comment: "some comment"
},
{
item: "item_1"
rating: "bad"
comment: "some comment"
},
{
item: "item_3"
rating: "good"
comment: "some comment"
},
}
I want a way to find out how many different ratings each item got.
so the output should look something like this:
{
{
item: "item_1"
good: 12
neutral: 10
bad: 67
},
{
item: "item_2"
good: 2
neutral: 45
bad: 8
},
{
item: "item_3"
good: 1
neutral: 31
bad: 10
}
}
This is what I've done
db.collection(collectionName).aggregate(
[
{
$group:
{
_id: "$item",
good_count: {$sum: {$eq: ["$rating", "Good"]}},
neutral_count:{$sum: {$eq: ["$rating", "Neutral"]}},
bad_count:{$sum: {$eq: ["$rating", "Bad"]}},
}
}
]
)
The format of the output looks right, but the counts are always 0.
I'm wondering what's the properway of summing things up by looking at the distinct values of the same field?
Thanks!
You were very close, but of course $eq just returns a true/false value, so to make that numeric you need $cond:
db.collection(collectionName).aggregate([
{ "$group" : {
"_id": "$item",
"good_count": {
"$sum": {
"$cond": [ { "$eq": [ "$rating", "good" ] }, 1, 0]
}
},
"neutral_count":{
"$sum": {
"$cond": [ { "$eq": [ "$rating", "neutral" ] }, 1, 0 ]
}
},
"bad_count": {
"$sum": {
"$cond": [ { "$eq": [ "$rating", "bad" ] }, 1, 0 ]
}
}
}}
])
As a "ternary" operator $cond takes a logical condition as it's first argument (if) and then returns the second argument where the evaluation is true (then) or the third argument where false (else). This makes true/false returns into 1 and 0 to feed to $sum respectively.
Also note that "case" is sensitive for $eq. If you have varing case then you likely want $toLower in the expressions:
"$cond": [ { "$eq": [ { "$toLower": "$rating" }, "bad" ] }, 1, 0 ]
On a slightly different note, the following aggregation is usually more flexible to different possible values and runs rings around the conditional sums in terms of performance:
db.collection(collectionName).aggregate([
{ "$group": {
"_id": {
"item": "$item",
"rating": { "$toLower": "$rating" }
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.item",
"results": {
"$push": {
"rating": "$_id.rating",
"count": "$count"
}
}
}}
])
That would instead give output like this:
{
"_id": "item_1"
"results":[
{ "rating": "good", "count": 12 },
{ "rating": "neutral", "count": 10 }
{ "rating": "bad", "count": 67 }
]
}
It's all the same information, but you did not have to explicitly match the values and it does execute much faster this way.
I am using this code inside a route:
MyModel.aggregate(
[
{ "$group": {
"_id": "$date",
"participants": { "$sum": "$participants" },
"peoples": { $sum: 1 }
}
},
],
function(err, result) {
console.log(result)
}
......etc......
This is aggregating the date, participants and peoples, but i want access the name field, and i was trying like this:
"_id": "$date",
"name": "$name",
"participants": { "$sum": "$participants" },
"peoples": { $sum: 1 }
but is returning:
{ [MongoError: exception: the group aggregate field 'name' must be defined as an expression inside an object]
name: 'MongoError',
errmsg: 'exception: the group aggregate field \'some\' must be defined as an expression inside an object',
code: 15951,
ok: 0 }
What i am doing wrong?
You need the $first operator for this. ie.:
MyModel.aggreagate(
[
{ "$group": {
"_id": "$date",
"name": { "$first": "$name" },
"participants": { "$sum": "$participants" },
"peoples": { "$sum": 1 }
},
],
function(err,results) {
// rest of processing.
}
);
Suspect that your "peoples" and "participants" fields are also wrong. But that's another question out of context here.