MongoDB Aggregation total fields and group by field name - mongodb

I have a collection of documents like so:
{
gameId: '0001A',
score: 40,
name: 'Bob',
city: 'London'
}
I am trying to run an aggregation on my documents that will output the following view FOR EACH gameId:
{
cities: [
London: {
totalScore: 500 // sum of the scores for all documents that have a city of London
people: [
'Bob',
'Anna',
'Sally',
'Sue'
],
peopleCount: 4 // a count of all people who also have the city of London
},
Barcelona: {
totalScore: 400 // sum of the scores for all documents that have a city of Barcelona
people: [
'Tim',
'Tina',
'Amir'
], // names of all people who also have the city of Barcelona
peopleCount: 3 // count of how many names appear
},
]
I've tried to achieve this using $facet$ and also $bucket in the aggregation pipeline. However this doesn't seem to fit the bill, as $bucket / $bucketAuto seem to require ranges or a number of buckets respectively. The $bucketAuto then sets a min and max value in the objects.
I'm able to group the total number of people, names, and scores straightforwardly like so at the moment:
$group: {
_id: '$gameId',
totalScore: {
$sum: '$score'
},
uniqueClients: {
$addToSet: '$name'
}
},
$addFields: {
uniqueClientCount: {
$size: '$uniqueClients'
}
}
How do I break it down by city?

you could try two $group stages as follow :
db.collection.aggregate([
{
"$group": {
"_id": {
game: "$gameId",
city: "$city"
},
"totalScore": {
"$sum": "$score"
},
"people": {
"$addToSet": "$name"
}
}
},
{
"$addFields": {
"peopleCount": {
"$size": "$people"
}
}
},
{
"$group": {
"_id": "$_id.game",
"cities": {
"$push": {
"$arrayToObject": [
[
{
k: "$_id.city",
v: {
people: "$people",
totalScore: "$totalScore",
peopleCount: "$peopleCount"
}
}
]
]
}
}
}
}
])
See on mongoplayground https://mongoplayground.net/p/f4uItCb0BwW

Related

Mongodb aggregate grouping elements of an array type field

I have below data in my collection:
[
{
"_id":{
"month":"Jan",
"year":"2022"
},
"products":[
{
"product":"ProdA",
"status":"failed",
"count":15
},
{
"product":"ProdA",
"status":"success",
"count":5
},
{
"product":"ProdB",
"status":"failed",
"count":20
},
{
"product":"ProdB",
"status":"success",
"count":10
}
]
},
...//more such data
]
I want to group the elements of products array on the name of the product, so that we have record of how what was the count of failure of success of each product in each month. Every record is guaranteed to have both success and failure count each month. The output should look like below:
[
{
"_id":{
"month":"Jan",
"year":"2022"
},
"products":[
{
"product":"ProdA","status":[{"name":"success","count":5},{"name":"failed","count":15}]
},
{
"product":"ProdB","status":[{"name":"success","count":10},{"name":"failed","count":20}]
}
]
},
...//data for succeeding months
]
I have tried to do something like this:
db.collection.aggregate([{ $unwind: "$products" },
{
$group: {
"_id": {
month: "$_id.month",
year: "$_id.year"
},
products: { $push: { "product": "$product", status: { $push: { name: "$status", count: "$count" } } } }
}
}]);
But above query doesn't work.
On which level I need to group fields so as to obtain above output.
Please help me to find out what I am doing wrong.
Thank You!
Your first group stage needs to group by both the _id and the product name, aggregate a list of status counts and then another group stage which then forms the products list:
db.collection.aggregate([
{$unwind: "$products"},
{$group: {
_id: {
id: "$_id",
product: "$products.product",
},
status: {
$push: {
name: "$products.status",
count: "$products.count"
}
}
}
},
{$group: {
_id: "$_id.id",
products: {
$push: {
product: "$_id.product",
status: "$status"
}
}
}
}
])
Mongo Playground

Is this query possible in MongoDB?

Imagine a data set like this:
db.test.insertMany([
{ '_id':1, 'name':'aa1', 'price':10, 'quantity': 2, 'category': ['coffe'] },
{ '_id':2, 'name':'aa2', 'price':20, 'quantity': 1, 'category': ['coffe', 'snack'] },
{ '_id':3, 'name':'aa3', 'price':5, 'quantity':10, 'category': ['snack', 'coffe'] },
{ '_id':4, 'name':'aa4', 'price':5, 'quantity':20, 'category': ['coffe', 'cake'] },
{ '_id':5, 'name':'aa5', 'price':10, 'quantity':10, 'category': ['animal', 'dog'] },
{ '_id':6, 'name':'aa6', 'price':5, 'quantity': 5, 'category': ['dog', 'animal'] },
{ '_id':7, 'name':'aa7', 'price':5, 'quantity':10, 'category': ['animal', 'cat'] },
{ '_id':8, 'name':'aa8', 'price':10, 'quantity': 5, 'category': ['cat', 'animal'] },
]);
I'm trying to make a query with this result (or something like it):
[
{ ['animal', 'dog'], 125 },
{ ['animal', 'cat'], 100 },
{ ['coffe', 'cake'], 100 },
{ ['coffe', 'snack'], 70 },
{ ['coffe'], 20 }
]
Meaning that it is:
Grouped by category.
The category is treated as a set (i.e. order is not important).
The result is sorted by price*quantity per unique category 'set'.
I've tried everything I know (which is very limited) and googled for days without getting anywhere.
Is this even possible in an aggregate query or do I have find a different way?
I suppose you need something like this:
db.collection.aggregate([
{
$unwind: "$category"
},
{
$sort: {
_id:-1,
category: -1
}
},
{
$group: {
_id: "$_id",
category: {
$push: "$category"
},
price: {
$first: "$price"
},
quantity: {
$first: "$quantity"
}
}
},
{
$group: {
_id: "$category",
sum: {
$sum: {
$multiply: [
"$price",
"$quantity"
]
}
}
}
},
{
$project: {
mySet: "$_id",
total: "$sum"
}
},
{
$sort: {
total: -1
}
}
])
Explained:
$unwind the $category array so you can sort the categories in same order.
$sort by category & _id so you can have same order per category & _id
$group by _id so you can push the categories back to array but sorted
$group by category set so you can sum the price*quantity
$project the needed fields
$sort by descending order as requested.
Please, note output has name for the set and total for the sum to be valid JSON since it is not possible to have the output as {[X,Y],Z} and need to be {m:[X,Y],z:Z}
playground
db.collection.aggregate([
{
"$match": {}
},
{
"$group": {
"_id": {
$function: {
body: "function(arr) { return arr.sort((a,b) => a.localeCompare(b))}",
args: [ "$category" ],
lang: "js"
}
},
"sum": {
"$sum": { "$multiply": [ "$price", "$quantity" ] }
}
}
},
{
"$sort": { sum: -1 }
}
])
mongoplayground
In mongodb 5.2 version you can use $sortArray instead of function sort that I used.

Getting distinct value for a field in Mongo

I have a database collection and this is its document structure.
{
_id: ObjectId("xxxxddsdsfdfdfdf")
category: electronics
sku: 10902
}
{
_id: ObjectId("dfdfdgfsdfdsgsf")
category: apparels
sku: 90345
}
{
_id: ObjectId("sdfdfdsggfgsgsdgsgsf")
category: electronics
sku: 10345
}
{
_id: ObjectId("dfndsnfkjdfdfsdnfsdf")
category: electronics
sku: 43435
}
I am trying to find the total number of SKUs per category. It should eliminate duplication and keep the values distinct. For example, electronics: 3, apparels: 1.
I have written a query, but it is giving me a total number of SKUs across categories which is not at all intended.
db.ecomm_sku_count.aggregate([
{
$group: {
_id: {
category: '$category',
sku_count: '$sku'
},
total_sku: {
$sum: 1
}
}
},
{
$count: "total_sku_units"
}
])
#output= [ { total_sku_units: 4 } ]
The intended output must be somewhat like this.
[
{ _id: { category: 'electronics', sku_count: 3 } },
{ _id: { category: 'apparels', sku_count: 1} }
]
I am trying to find the distinct SKU values per category.
I am beginner to mongo aggregation framework. Pardon me if the question is of noob type.
I think the below code is what you are looking for:
db.collection.aggregate([
{
"$group": {
"_id": {
"category": "$category",
},
"total_sku": {
"$addToSet": "$sku"
}
},
},
{
"$project": {
"total_sku": {
"$size": "$total_sku"
}
},
},
])
Mongo Playground Sample Execution

Query multiple properties in at the same time getting an overall average and an array

Given the following data, I'm trying to get an average of all their ages, at the same time I want to return an array of their names. Ideally, I want to do this in just one query but can't seem to figure it out.
Data:
users:[
{user:{
id: 1,
name: “Bob”,
age: 23
}},
{user:{
id: 1,
name: “Susan”,
age: 32
}},
{user:{
id: 2,
name: “Jeff”,
age: 45
}
}]
Query:
var dbmatch = db.users.aggregate([
{$match: {"id" : 1}},
{$group: {_id: null, avg_age: { $avg: "$age" }}},
{$group: {_id : { name: "$name"}}}
)]
Running the above groups one at a time outputs the results I expect, either an _id of null and an average of 27.5, or an array of the names.
When I combine them as you see above using a comma, I get:
Issue Generated Code:
[ { _id: {name: null } } ]
Expected Generated Code:
[
{name:"Bob"},
{name:"Susan"},
avg_age: 27.5
]
Any help would be greatly appreciated!
Not sure if this is exactly what you want, but this query
db.users.aggregate([
{
$match: {
id: 1
}
},
{
$group: {
_id: "$id",
avg_age: {
$avg: "$age"
},
names: {
$push: {
name: "$name"
}
}
}
},
{
$project: {
_id: 0
}
}
])
Results in this result:
[
{
"avg_age": 27.5,
"names": [
{
"name": "Bob"
},
{
"name": "Susan"
}
]
}
]
This will duplicate names, so if there are two documents with the name Bob, it will be two times in the array. If you don't want duplicates, change $push to $addToSet.
Also, if you want names to be just an array of names instead of objects, change names query to
names: {
$push: "$name"
}
This will result in
[
{
"avg_age": 27.5,
"names": ["Bob", "Susan"]
}
]
Hope it helps,
Tomas :)
You can use $facet aggregation to run the multiple queries at once
db.collection.aggregate([
{ "$facet": {
"firstQuery": [
{ "$match": { "id": 1 }},
{ "$group": {
"_id": null,
"avg_age": { "$avg": "$age" }
}}
],
"secondQuery": [
{ "$match": { "id": 1 }},
{ "$group": { "_id": "$name" }}
]
}}
])

total of all groups totals using mongodb

i did this Aggregate pipeline , and i want add a field contains the Global Total of all groups total.
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: {
_id:"$_id",
value:"$value",
transaction:"$transaction",
paymentMethod:"$paymentMethod",
createdAt:"$createdAt",
...
}
},
count:{$sum:1},
total:{$sum:"$value"}
}}
{
//i want to get
...project groups , goupsTotal , groupsCount
}
,{
"$skip":cursor.skip
},{
"$limit":cursor.limit
},
])
you need to use $facet (avaialble from MongoDB 3.4) to apply multiple pipelines on the same set of docs
first pipeline: skip and limit docs
second pipeline: calculate total of all groups
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: "$$CURRENT"
},
count:{$sum:1},
total:{$sum:"$value"}
}
},
{
$facet: {
docs: [
{ $skip:cursor.skip },
{ $limit:cursor.limit }
],
overall: [
{$group: {
_id: null,
groupsTotal: {$sum: '$total'},
groupsCount:{ $sum: '$count'}
}
}
]
}
the final output will be
{
docs: [ .... ], // array of {_id, items, count, total}
overall: { } // object with properties groupsTotal, groupsCount
}
PS: I've replaced the items in the third pipe stage with $$CURRENT which adds the whole document for the sake of simplicity, if you need custom properties then specify them.
i did it in this way , project the $group result in new field doc and $sum the sub totals.
{
$project: {
"doc": {
"_id": "$_id",
"total": "$total",
"items":"$items",
"count":"$count"
}
}
},{
$group: {
"_id": null,
"globalTotal": {
$sum: "$doc.total"
},
"result": {
$push: "$doc"
}
}
},
{
$project: {
"result": 1,
//paging "result": {$slice: [ "$result", cursor.skip,cursor.limit ] },
"_id": 0,
"globalTotal": 1
}
}
the output
[
{
globalTotal: 121500,
result: [ [group1], [group2], [group3], ... ]
}
]