MongoDb group by combined with multiple sums and match - mongodb

I have the fields, group_id, code, revenue. I want to do an aggregation where the revenue is grouped by the group_id and summed up for each type of code. Also, some of the codes will have a combined revenue. So for example, for codes "abc" and "ghi", there will be a combined revenue with header "agi". How do I do this?
If the input like
[
{
"group_id": "ABC",
"revenue": 100,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC1",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC1",
"revenue": 200,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "ghi"
}
]
then the output will be
[
{
"group_id": "ABC",
"expenses": [
{
"code": "agi",
"revenue": 500
},
{
"code": "def",
"revenue": 400
}
]
},
{
"group_id": "ABC1",
"expenses": [
{
"code": "abc",
"revenue": 200
},
{
"code": "def",
"revenue": 200
}
]
},
]

Then group by group_id and code like this:
let combine = ["abc", "ghi"];
db.items.aggregate([
{
$addFields: {
code: {
$cond: {
if: { $in: ["$code", combine] },
then: "agi",
else: "$code"
}
}
}
},
{
$group: {
_id: {
group_id: "$group_id",
code: "$code"
},
revenue: { $sum: "$revenue" }
}
},
{
$group: {
_id: "$_id.group_id",
expenses: {
$push: {
code: "$_id.code",
revenue: "$revenue"
}
}
}
}
]);
Output
/* 1 */
{
"_id" : "ABC",
"expenses" : [
{
"code" : "def",
"revenue" : 400
},
{
"code" : "agi",
"revenue" : 500
}
]
},
/* 2 */
{
"_id" : "ABC1",
"expenses" : [
{
"code" : "def",
"revenue" : 200
},
{
"code" : "agi",
"revenue" : 200
}
]
}

Related

I am having difficulty in querying the follwing nested document using pymongo

If these are the following nested documents
[
{
"_id": 5,
"name": "Wilburn Spiess",
"scores": [
{
"score": 44.87186330181261,
"type": "exam"
},
{
"score": 25.72395114668016,
"type": "quiz"
},
{
"score": 63.42288310628662,
"type": "homework"
}
]
},
{
"_id": 6,
"name": "Jenette Flanders",
"scores": [
{
"score": 37.32285459166097,
"type": "exam"
},
{
"score": 28.32634976913737,
"type": "quiz"
},
{
"score": 81.57115318686338,
"type": "homework"
}
]
},
{
"_id": 7,
"name": "Salena Olmos",
"scores": [
{
"score": 90.37826509157176,
"type": "exam"
},
{
"score": 42.48780666956811,
"type": "quiz"
},
{
"score": 96.52986171633331,
"type": "homework"
}
]
}
]
I need to access the score part 'type' = exam.
Can somebody help me with this?
If you're asking for a python program to access the score, you can print them out like:
collection = mongo_connection['db']['collection']
documents = collection.find({})
for doc in documents:
for score in doc['scores']:
if score['type'] == 'exam':
print(f'Score: {score["score"]}')
If you are trying to retrieve only the scores and ignore the rest, I'd do an $unwind on the scores, $match on the type, and then project the fields you want (or not).
db.test.aggregate([
{
$unwind: '$scores'
},
{
$match: {
'scores.type': 'exam'
}
},
{
$project: {
'name': '$name',
'score': '$scores.score'
}
}
])
This would output:
{
"_id" : 5,
"name" : "Wilburn Spiess",
"score" : 44.8718633018126
},
{
"_id" : 6,
"name" : "Jenette Flanders",
"score" : 37.322854591661
},
{
"_id" : 7,
"name" : "Salena Olmos",
"score" : 90.3782650915718
}

Multiple Group By with condition in MongoDB

I have the fields, group_id, category, account, sku and revenue. I want to do an aggregation where the revenue is grouped by the group_id, category, sku and account separately and in sku there will be two divisions, one where there is a comma and one where there isn't. How do I do this?
For example, if my data is
[
{
"group_id": "ABC",
"category": "test",
"account": "abc1",
"sku": "grre,qrvf",
"revenue": 100,
},
{
"group_id": "ABC2",
"category": "test",
"account": "abc",
"sku": "grre,qrvf",
"revenue": 100,
},
{
"group_id": "ABC3",
"category": "test2",
"account": "abc2",
"sku": "qwe",
"revenue": 100,
},
{
"group_id": "ABC",
"category": "test2",
"account": "abc",
"sku": "qwe",
"revenue": 100,
},
{
"group_id": "ABC2",
"category": "test3",
"account": "abc2",
"sku": "asd,fgh",
"revenue": 100,
},
{
"group_id": "ABC",
"category": "test3",
"account": "abc",
"sku": "asd,fgh",
"revenue": 100,
},
{
"group_id": "ABC3",
"category": "test",
"account": "abc2",
"sku": "grre,qrvf",
"revenue": 100,
}
]
then the result should be like
[
{groups: [{group_id: "ABC", "revenue": 300}, {group_id: "ABC2", "revenue": 200}, {group_id: "ABC3", "revenue": 200}]},
{categories: [{category: "test", "revenue": 300}, {category: "test2", "revenue": 200}, {category: "test3", "revenue": 200}]},
{accounts: [{account: "abc", "revenue": 300}, {account: "abc2", "revenue": 100}, {account: "abc3", "revenue": 300}]},
{skus: [{single: [{sku: "qwe", revenue: 100}, {sku: "iou", revenue: 100}]}, {multi: [{sku: "grre,qrvf", revenue: 300}, {sku: "asd,fgh", revenue: 200}]}]},
]
You can use $facet to run multiple aggregations separately in one round-trip and $indexOfBytes to split skus:
db.collection.aggregate([
{
$facet: {
group: [ { $group: { _id: "$group_id", revenue: { $sum: "$revenue" } } }, { $project: { _id: 0, group: "$_id", revenue: 1 } } ],
categories: [ { $group: { _id: "$category", revenue: { $sum: "$revenue" } } }, { $project: { _id: 0, category: "$_id", revenue: 1 } } ],
accounts: [ { $group: { _id: "$account", revenue: { $sum: "$revenue" } } }, { $project: { _id: 0, account: "$_id", revenue: 1 } } ],
skus: [
{ $group: { _id: "$sku", revenue: { $sum: "$revenue" } } },
{ $group: { _id: null, aggregates: { $push: { sku: "$_id", revenue: "$revenue" } } } },
{ $project: {
_id: 0,
single: { $filter: { input: "$aggregates", cond: { $eq: [ { $indexOfBytes: [ "$$this.sku", "," ] }, -1 ] } } },
multi: { $filter: { input: "$aggregates", cond: { $ne: [ { $indexOfBytes: [ "$$this.sku", "," ] }, -1 ] } } },
},
}
],
}
}
])
Mongo Playground

Mongo DB aggregation to convert books to array of authors

My book collection looks like this:
{
"book": {
"title": "Book1",
"author": "Tom"
},
"quantity": 3
},
{
"book": {
"title": "Book2",
"author": "Tom"
},
"quantity": 4
},
{
"book": {
"title": "Book3",
"author": "Dick"
},
"quantity": 9
},
{
"book": {
"title": "Book4",
"author": "Harry"
},
"quantity": 6
},
{
"book": {
"title": "Book5",
"author": "Chris"
},
"quantity": 7
},
{
"book": {
"title": "Book6",
"author": "Dick"
},
"quantity": 10
}
This collection has book documents with title, author and quantity.
I want help in coming up with aggregation which would output the array of authors with their books and quantity. Example - Tom has authored "Book1" and "Book2", "Dick" has authored "Book6" and "Book3".
authors: [
{
"name": "Tom",
"books": [
{
"title": "Book1",
"quantity": "3"
},
{
"title": "Book2",
"quantity": "4"
}
]
},
{
"name": "Dick",
"books": [
{
"title": "Book6",
"quantity": "10"
},
{
"title": "Book3",
"quantity": "9"
}
]
},
{
"name": "Harry",
"books": [
{
"title": "Book4",
"quantity": "6"
}
]
}
]
Try this aggregation:
db.collection.aggregate([{
$group: {
_id: "$book.author",
books: {
$push: {
title: "$book.title",
quantity: "$quantity"
}
}
}},{
$project: {
"name": "$_id",
"books": 1,
"_id": 0
}}])
I tried it on mongo playground:
https://mongoplayground.net/p/m-QbX-uzkkt
Try as below:
db.collection.aggregate([
{
$group: {
_id: "$book.author",
books: { $push: { "title": "$book.title", "quantity": "$quantity"} }
},
}
])
Output will be
/* 1 */
{
"_id" : "Chris",
"books" : [
{
"title" : "Book5",
"quantity" : 7
}
]
},
/* 2 */
{
"_id" : "Harry",
"books" : [
{
"title" : "Book4",
"quantity" : 6
}
]
},
/* 3 */
{
"_id" : "Dick",
"books" : [
{
"title" : "Book3",
"quantity" : 9
},
{
"title" : "Book6",
"quantity" : 10
}
]
},
/* 4 */
{
"_id" : "Tom",
"books" : [
{
"title" : "Book1",
"quantity" : 3
},
{
"title" : "Book2",
"quantity" : 4
}
]
}

Mongodb - Aggregation and Sum for each object

I need to aggregate for each "Product" object from the sales database, and sum "Price" and "Quantity" to make a product classification.
I was able to group by "Products" but I can't sum Price and Quantity to the each object.
{$group: {
_id: '$products.items',
totalprice:{$sum: "$products.items.price"},
}}
Below sample of sales database, where I need to return the sum of the "Price" and "Quantity" fields sold for each "Products".
{
"_id": {
"$oid": "5d753707c0cd851e80da914c"
},
"created_on": {
"$date": {
"$numberLong": "1567962886000"
}
},
"custumer": {
"name": "Teste",
"cep": "teste",
"address": "teste",
"district": "test",
"city": "test",
"numb": "50",
"comple": "test",
"state": "test",
"cpf": "test",
"birth": "30/09/1977",
"email": "test#gmail.com",
"phone": {
"$numberDouble": "1111111111111"
},
"gender": "M",
"portalt": {
"status": "true",
"vendor": "test",
"phone": {
"$numberDouble": "11111111111"
},
"sim": "011111111111",
"salesnumb": "1222222222222222222"
}
},
"payment": {
"method": "Boleto",
"type": "Parcelado",
"installments": "5",
"billing_date": "15"
},
**"products": {
"items": {
"5d515979736802000415a561": {
"item": {
"_id": "5d515979736802000415a561",
"name_produto": "Product 1",
"resumo": "Minutos ilimitados,20GB + 2GB",
"price": "110",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "2"
},
"price": {
"$numberInt": "220"
}
},
"5d515aba736802000415a562": {
"item": {
"_id": "5d515aba736802000415a562",
"name_produto": "Product 2",
"resumo": "Minutos ilimitados,3GB + 1GB",
"price": "80",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "1"
},
"price": {
"$numberInt": "80"
}
},
"5d515dbf736802000415a564": {
"item": {
"_id": "5d515dbf736802000415a564",
"name_produto": "Product 3",
"resumo": "Minutos ilimitados,30GB + 3GB",
"price": "150",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "1"
},
"price": {
"$numberInt": "150"
}
}
},**
"totalItems": {
"$numberInt": "4"
},
"totalPrice": {
"$numberInt": "450"
}
},
"seller": {
"_id": {
"$oid": "5cd086787dc59921bcad94d8"
},
"name": "test"
}
}
I need output something like:
_id:Object
5d515979736802000415a561:{sum_price: 300, sum_quantity: 30 }
5d515aba736802000415a562:{sum_price: 500, sum_quantity: 60 }
5d515dbf736802000415a564:{sum_price: 600, sum_quantity: 70 }
Thanks so much!
So let's go with the OP ask to sum the individual product price and quantity. Stripping away the other fields which are not relevant to the ask, we arrive at something like this:
var r =
[
{
_id:0,
"products": {
"items": {
"5d515979736802000415a561": {
"quantity": 2,
"price": 220,
},
"5d515aba736802000415a562": {
"quantity": 1,
"price": 80
}
}
}
}
, {
_id:1,
"products": {
"items": {
"5d515979736802000415a561": { // deliberately same id as doc above but different quantity and price
"quantity": 3,
"price": 330
},
"5d515aba736802000415a562": { // same
"quantity": 2,
"price": 160
},
"5d515979736802000415ZZZZ": { // different than above; adds "third item"
"quantity": 4,
"price": 200
}
}
}
}
];
Note that the whole inner item field is basically not important, not the least of which it only contains the unit price, not the total price (amount) and quantity per product.
"5d515dbf736802000415a564": {
"item": {
"_id": "5d515dbf736802000415a564",
// etc
So now we employ the $objectToArray to turn the keys into rvals. That gives us something we can $group on, and so here is a solution:
db.foo.aggregate([
{$project: {X: {$objectToArray: "$products.items"}}}
,{$unwind: "$X"}
,{$group: {_id: "$X.k", tot_q: {$sum:"$X.v.quantity"}, tot_amt: {$sum:"$X.v.price"}} }
]);
which given the input above yields:
{ "_id" : "5d515979736802000415ZZZZ", "tot_q" : 4, "tot_amt" : 200 }
{ "_id" : "5d515aba736802000415a562", "tot_q" : 3, "tot_amt" : 240 }
{ "_id" : "5d515979736802000415a561", "tot_q" : 5, "tot_amt" : 550 }

Aggregate and grouping in Mongo

How do I get a summary count in Mongo. I have the following record structure and I would like to get a summary by date and status
{
"_id": "1",
"History": [
{
"id": "11",
"message": "",
"status": "send",
"resultCount": 0,
"createdDate": "",
"modifiedDate": ""
},
{
"id": "21",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "",
"modifiedDate": ""
}
]
}
This is what I would like..
date x
status :
count : nn
This is my Mongo structure
Let's assume you have the following data in 'history' collection:
{
"_id": "1",
"History": [
{
"id": "21",
"message": "",
"status": "send",
"resultCount": 0,
"createdDate": "date1",
"modifiedDate": ""
},
{
"id": "22",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date1",
"modifiedDate": ""
},
{
"id": "23",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date2",
"modifiedDate": ""
},
{
"id": "24",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date2",
"modifiedDate": ""
}
]
}
You can design your query in the following way to get the desired summary.
db.history.aggregate([
{
$unwind:"$History"
},
{
$group:{
"_id":{
"createdDate":"$History.createdDate",
"status":"$History.status"
},
"createdDate":{
$first: "$History.createdDate"
},
"status":{
$first:"$History.status"
},
"count":{
$sum:1
}
}
},
{
$group:{
"_id":"$createdDate",
"createdDate":{
$first:"$createdDate"
},
"info":{
$push:{
"status":"$status",
"count":"$count"
}
}
}
},
{
$project:{
"_id":0
}
}
]).pretty()
It would result in the following:
{
"createdDate" : "date1",
"info" : [
{
"status" : "skipped",
"count" : 1
},
{
"status" : "send",
"count" : 1
}
]
}
{
"createdDate" : "date2",
"info" : [
{
"status" : "skipped",
"count" : 2
}
]
}
Aggregation stages details:
Stage I: The 'History' array is unwinded i.e. the array would be split and each element would create an individual document.
Stage II: The data is grouped on the basis of 'createdDate' and 'status'. In this stage, the count of status is also calculated.
Stage III: The data is further grouped on the basis of 'createdDate'
only
Stage IV: Eliminating non-required fields from the result