Problem with an aggregated query in mongoDB - mongodb

I have the following collection in mongodb:
IDcustomer. idServicerequired. ...
001. 13
002. 15
002. 19
002. 10
003. null
From this, i want to get the average number of services required by each customer (in this case, the output should be (1+3+0)/3 = 1.34)
I tried as follows, but in this way, for each customer that has required no service, it is counted 1, as if he had required one service, so the average is higher than expected (in this case it would be (1+3+1)/3=1.67)

first group, check condition if idServicerequired is null then count 0
second $group by null and average count
db.collection.aggregate([
{
$group: {
_id: "$idCustomer",
count: {
$sum: {
$cond: [{ $eq: ["$idServicerequired", null] }, 0, 1]
}
}
}
},
{
$group: {
_id: null,
count: { $avg: "$count" }
}
}
])
Playground

Related

How to get depended max value from another max value?

Assume there are documents with following structure in the collection:
{
_id: ObjectId("63af57637d4f4258c1ba460b"),
metadata: {
billYear: 2022,
billNumber: 1
}
},
{
_id: ObjectId("63af57637d4f4258c1ba460c"),
metadata: {
billYear: 2022,
billNumber: 2
}
},
{
_id: ObjectId("63af57637d4f4258c1ba460d"),
metadata: {
billYear: 2023,
billNumber: 1
}
}
I need to get the max value of billYear and within this year the max value of billNumber. So in this example the max year is 2023 and the max value in this year is 1.
I tried this attempt:
Data.aggregate( [ { $max : { billNumber : "$billYear" } } ] )
Update
Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ])
gives me the max year value:
[ { _id: null, maxBillYear: 2023 } ]
So I would think of running a second query with this year to get the max value for the number. But is it possible to do this directly in a single query?
as for your first attempt to get the max year you were not accessing value correctly if you try like this it will work
Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ])
Now the second part of your question to get the max value for the number in single query you can try like this
To get the max bill year from the data
maxBillYear = Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ]).first().maxBillYear
To get the max value for the number of bills in a year
Data.aggregate([{ $group: { _id: "$metadata.billYear", maxBillNumber: { $max: "$metadata.billNumber" }}} ])
Yes you can get only max number result on single query
db.collection.aggregate([
{
$group: {
_id: "$metadata.billYear",
maxBillNumber: {
$max: "$metadata.billNumber"
}
}
},
{
$sort: {
_id: -1
}
},
{
$limit: 1
}
])

MongoDB - How to select data that has a field equals to the minimum field value

I'm new to MongoDB and I want to select all users having the minimum age.
Something like this:
db.users.find({age: {$min: age}})
Seems really basic but I can't find how to do it.
$gorup by age and make array of users
$sort by _id means age in ascending order
$limit 1 document
db.users.aggregate([
{
$group: {
_id: "$age",
users: { $push: "$$ROOT" }
}
},
{ $sort: { _id: 1 } },
{ $limit: 1 }
])
Playground

Total count and field count with condition in a single MongoDB aggregation pipeline

I have a collection of components. Simplified, a document looks like this:
{
"_id" : "50c4f4f2-68b5-4153-80db-de8fcf716902",
"name" : "C156",
"posX" : "-136350",
"posY" : "-27350",
"posZ" : "962",
"inspectionIsFailed" : "False"
}
I would now like to calculate three things. The number of all components in the collection, the number of all faulty components "inspectionIsFailed": "True" and then the ratio (number of all faulty components divided by the number of all components).
I know how to get the first two things separately and in a row with one aggregation each.
Number of all components:
db.components.aggregate([
{$group: {_id: null, totalCount: {$sum: 1}}}
]);
Number of all faulty components:
db.components.aggregate([
{$match: {inspectionIsFailed: "True"}},
{$group: {_id: null, failedCount: {$sum: 1}}}
]);
However, I want to calculate the two values in a single pipeline and not separately. Then I could use $divide to calculate the ratio at the end of the pipeline. My desired output should then only contain the ratio:
{ ratio: 0.2 }
My problem with a single pipeline is:
If I try to calculate the total number first, then I can no longer calculate the number of the faulty components. If I first calculate the number of faulty components with $match, I can no longer calculate the total number.
You can try,
$group by null, get totalCount with $sum, and get failedCount on the base of $cond (condition) if inspectionIsFailed id True then return 1 and sum other wise 0
$project to get ratio using $divide
db.collection.aggregate([
{
$group: {
_id: null,
totalCount: { $sum: 1 },
failedCount: {
$sum: {
$cond: [{ $eq: ["$inspectionIsFailed", "True"] }, 1, 0 ]
}
}
}
},
{
$project: {
_id: 0,
ratio: {
$divide: ["$failedCount", "$totalCount"]
}
}
}
])
Playground
As I found out, you can not do it in one pipeline, then you have to use $facet as in this answer explained.
Also I suggest to use boolean for inspectionIsFailed.
db.collection.aggregate([
{
$facet: {
totalCount: [
{
$count: "value"
}
],
pipelineResults: [
{
$match: {
inspectionIsFailed: true
}
},
{
$group: {
_id: "$_id",
failedCount: {
$sum: 1
}
}
}
]
}
}
])
You can test it here.

Mongodb aggregate $gt returns non matching records

I would like to count the sum of a field in my database.
I have this pipeline in mongodb:
{
match: {
'user1': user.id,
'unreadMessagesCount': { $exists: true, $gt: 0 },
}
},
{
group: {
objectId: null,
total: { $sum: "$unreadMessagesCount" },
count: { $sum: 1 }
}
}
The results returned is
{total: 3, count: 30}
The total is correct because I only have 3 records with 1 unreadMessagesCount each. But the count returned is 30 which is wrong. There should only be 3 records matched. When i remove group from pipeline, I get 30 records.

How to perform case-insensitive aggregation grouping in MongoDb?

Let's say that I want to aggregate and group by documents in MongoDb by the Description field.
Running the following (case-sensitive by default):
db['Products'].aggregate(
{ $group: {
_id: { 'Description': "$Description" },
count: { $sum: 1 },
docs: { $push: "$_id" }
}},
{ $match: {
count: { $gt : 1 }
}}
);
on my sample data gives me 1000 results, which is fine.
But now I expect that running a case-insensitive query (using $toLower) should give me less than or equal to 1000 results:
db['Products'].aggregate(
{ $group: {
_id: { 'Description': {$toLower: "$Description"} },
count: { $sum: 1 },
docs: { $push: "$_id" }
}},
{ $match: {
count: { $gt : 1 }
}}
);
But instead I get more than 1000 results. That can't be right, can it? More common entries should get grouped together to yield less number of total groupings ... I think.
So then probably my aggregation query is wrong! Which brings me to my question:
How should case-insensitive aggregation grouping in MongoDb be performed?
You approach to case-insensitive grouping is correct so perhaps your observation is not? ;)
Try this example:
// insert two documents
db.getCollection('test').insertOne({"name" : "Test"}) // uppercase 'T'
db.getCollection('test').insertOne({"name" : "test"}) // lowercase 't'
// perform the grouping
db.getCollection('test').aggregate({ $group: { "_id": { $toLower: "$name" }, "count": { $sum: 1 } } }) // case insensitive
db.getCollection('test').aggregate({ $group: { "_id": "$name", "count": { $sum: 1 } } }) // case sensitive
You may have a typo somewhere?
The documentation also states that
$toLower only has a well-defined behavior for strings of ASCII characters.
Perhaps that's what's biting you here?