Multiple Group By with condition in MongoDB - 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

Related

mongo aggregate group, match, project, count

I am trying to get an aggregate work in mongo
Here is a simple json structure, and how I would like to achieve the aggregate.
// this is the format of the collection
[
{
_id: 6154f64df41fa3628ac2062a,
type: 'email',
platform: 'google',
apiDataId: '17c33ac4735c80bf',
timestamp: '2021-09-01',
userId: '6132a04892559282c40fd29a',
groupId: '6132a0cb74af9d82df74e918',
__v: 0
},
{
_id: 6154f64df41fa3628ac2062b,
type: 'call',
platform: 'yahoo',
apiDataId: '17c2d25e2ccf770d',
timestamp: '2021-09-01',
userId: '6132a04892559282c40fd29a',
groupId: '6132a0cb74af9d82df74e918',
__v: 0
},
{
_id: 6154f64df41fa3628ac2062c,
type: 'email',
platform: 'google',
apiDataId: '2021-09-03',
timestamp: '1632958029720',
userId: '6132a04892559282c40ff2a9',
groupId: '6132a0cb74af9d82df74e918',
__v: 0
},
{
_id: 6154f64df41fa3628ac2062d,
type: 'email',
platform: 'google',
apiDataId: '17c273deffc51cc9',
timestamp: '2021-09-04',
userId: '6132a04892559282c40fd29a',
groupId: '6132a0cb74af9d82df74e918',
__v: 0
},
{
_id: 6154f64df41fa3628ac2062e,
type: 'call',
platform: 'yahoo',
apiDataId: '17c14c85f6a89088',
timestamp: '2021-09-04',
userId: '6132a04892559282c40ff2a9',
groupId: '6132a0cb74af9d82df74e918',
__v: 0
}
]
I've started out wrting the aggregate, but I am having hard time understanding the aggregate,
aggregate([
{ $match: { groupId: groupId } },
{ $group: { _id: "$userId" } },
{ $group: { timestamp: "$timestamp" } },
{ $project: { "groupId": 0, "userId": 0} },
{ $count: "num_data" }
]).exec()
what I am trying to accomplish edited
[
groupId: '6132a04892559282c40fd29a',
userId: {
calls: [
"2021-09-01": {platform: "yahoo"},
"2021-09-03": {platform: "yahoo"},
...
],
emails: [
"2021-09-01": {platform: "google"},
"2021-09-04": {platform: "google"},
...
],
},
userId: { ... }
]
of course that aggregation doesn't work, I am having hard time understanding and trying to figure out the orders and what $things to use.
Is this aggregate is what you looking for?
db.collection.aggregate([
{
$match: {
groupId: "1"
}
},
{
$group: {
_id: "$userId",
count: {
$sum: 1
},
apiDataId: {
"$first": "$$ROOT"
}
}
},
{
"$project": {
"count": "$count",
"apiDataId": {
"timestamp": "$apiDataId.timestamp",
"platform": "$apiDataId.platform",
"type": "$apiDataId.type",
"data": "$apiDataId.data"
}
}
}
])
data
[
{
"groupId": "1",
"timestamp": "1",
"userId": "1",
"platform": "1",
"apiDataId": "1",
"type": "1",
"data": "1"
},
{
"groupId": "1",
"timestamp": "1",
"userId": "2",
"platform": "3",
"apiDataId": "4",
"type": "5",
"data": "6"
},
{
"groupId": "1",
"timestamp": "3",
"userId": "2",
"platform": "7",
"apiDataId": "8",
"type": "9",
"data": "9"
},
{
"groupId": "2",
"timestamp": "1",
"userId": "1",
"platform": "1",
"apiDataId": "1",
"type": "1",
"data": "1"
}
]
result
[
{
"_id": "2",
"apiDataId": {
"data": "6",
"platform": "3",
"timestamp": "1",
"type": "5"
},
"count": 2
},
{
"_id": "1",
"apiDataId": {
"data": "1",
"platform": "1",
"timestamp": "1",
"type": "1"
},
"count": 1
}
]
mongoplayground
Update: 2021-10-03
aggregate by userid then by date
db.collection.aggregate([
{
$match: {
__v: 0
}
},
{
$group: {
_id: {
u: "$userId",
t: "$timestamp"
},
count: {
$sum: 1
},
"timestampList": {
"$push": "$$ROOT"
}
}
},
{
$group: {
_id: "$_id.u",
count: {
$sum: 1
},
"userList": {
"$push": "$$ROOT"
}
}
}
])

How to do a pivot and conditional group in MongoDB?

I have data like this:
[
{
"invoice_id": "GRS",
"segment": "D",
"metric": "2019",
"revenue": 100,
},
{
"invoice_id": "RET",
"segment": "D",
"metric": "2019",
"revenue": 100,
},
{
"invoice_id": "GRS",
"segment": "R",
"metric": "2020",
"revenue": 100,
},
{
"invoice_id": "RET",
"segment": "R",
"metric": "2021",
"revenue": 100,
},
{
"invoice_id": "GRS",
"segment": "R",
"metric": "2021",
"revenue": 100,
},
{
"invoice_id": "RET",
"segment": "D",
"metric": "2020",
"revenue": 100,
},
{
"invoice_id": "GRS",
"segment": "D",
"metric": "2021",
"revenue": 100,
}
]
After aggregation, I want the result to come in the following format:
[
{
"invoice_id": "GRS",
"segment": "D",
"2019": 100,
"2021": 100
},
{
"invoice_id": "RET",
"segment": "D",
"2019": 100,
"2020": 100
},
{
"invoice_id": "GRS",
"segment": "R",
"2019": 100,
"2021": 100
},
{
"invoice_id": "RET",
"segment": "R",
"2021": 100
},
]
$group by invoice_id, segment and metric and get total sum of revenue
$group by invoice_id and segment and construct array of years in key-value pair
$arrayToObject convert years array to object key-value format
$mergeObject to merge _id object and years object
$replaceRoot to replace above merge object to root
db.collection.aggregate([
{
$group: {
_id: {
invoice_id: "$invoice_id",
segment: "$segment",
metric: "$metric"
},
revenue: { $sum: "$revenue" }
}
},
{
$group: {
_id: {
invoice_id: "$_id.invoice_id",
segment: "$_id.segment"
},
years: {
$push: {
k: "$_id.metric",
v: "$revenue"
}
}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$_id",
{ $arrayToObject: "$years" }
]
}
}
}
])
Playground

MongoDB inner join with specific condition from both collections

I have got two collections, chapters and courses with one-to-one association,
db={
"chapters": [
{
"_id": 10,
"course_id": 1,
"author": "John"
},
{
"_id": 20,
"course_id": 2,
"author": "John"
},
{
"_id": 30,
"course_id": 3,
"author": "Timmy"
},
{
"_id": 40,
"course_id": 4,
"author": "John"
},
],
"courses": [
{
"_id": 1,
"published": true,
"name": "course 1"
},
{
"_id": 2,
"published": false,
"name": "course 2"
},
{
"_id": 3,
"published": true,
"name": "course 3"
},
{
"_id": 4,
"published": true,
"name": "course 4"
}
]
}
How do I query all chapters with the published course (published=true) and the author is "John"?
You can use $match, $lookup then $group by author then simply $filter it based on published
db.chapters.aggregate([
{
$match: {
author: "John"
}
},
{
"$lookup": {
"from": "courses",
"localField": "course_id",
"foreignField": "_id",
"as": "course"
}
},
{
$project: {
_id: 1,
course: {
$first: "$course"
},
author: 1
}
},
{
$group: {
_id: "$author",
courseChapters: {
$push: "$$ROOT"
}
}
},
{
$project: {
courseChapters: {
$filter: {
input: "$courseChapters",
as: "cc",
cond: {
$eq: [
"$$cc.course.published",
true
]
}
}
}
}
}
])
Output
[
{
"_id": "John",
"courseChapters": [
{
"_id": 10,
"author": "John",
"course": {
"_id": 1,
"name": "course 1",
"published": true
}
},
{
"_id": 40,
"author": "John",
"course": {
"_id": 4,
"name": "course 4",
"published": true
}
}
]
}
]
Mongoplayground: https://mongoplayground.net/p/x-XghXzZUk4

Group items inside of another group

I want to group the following collection by category and sum its total value, then create a subcategory attribute, on same structure, summing subcategories if more than one equal.
[
{
"_id": 1,
"date": ISODate("2019-10-10T00:00:00.000Z"),
"description": "INTERNET BILL",
"credit": "",
"debit": "-100.00",
"category": "home",
"subcategory": "internet",
"__v": 0
},
{
"_id": 2,
"date": ISODate("2019-10-10T00:00:00.000Z"),
"description": "WATER BILL",
"credit": "",
"debit": "-150.00",
"category": "home",
"subcategory": "water",
"__v": 0
},
{
"_id": 3,
"date": ISODate("2019-10-10T00:00:00.000Z"),
"description": "MC DONALDS",
"credit": "",
"debit": "-30.00",
"category": "food",
"subcategory": "restaurants",
"__v": 0
},
{
"_id": 4,
"date": ISODate("2019-10-10T00:00:00.000Z"),
"description": "BURGER KING",
"credit": "",
"debit": "-50.00",
"category": "food",
"subcategory": "restaurants",
"__v": 0
},
{
"_id": 5,
"date": ISODate("2019-10-10T00:00:00.000Z"),
"description": "WALMART",
"credit": "",
"debit": "-20.00",
"category": "food",
"subcategory": "groceries",
"__v": 0
},
]
Desireble output:
[
{
"_id": "home",
"total": "-250.00",
"subcategory" : [
{"id": "internet", "total": "-100"},
{"id": "water", "total": "-150"}
]
},
{
"_id": "food",
"total": "-100.00",
"subcategory" : [
{"id": "restaurants", "total": "-80"},
{"id": "groceries", "total": "-20"}
]
}
]
With the following query, I've almost achieved it, but I haven't find a way to sum values on subcategories.
db.getCollection('expenses').aggregate([
{$match:
{"date" : { "$gte" : new Date("11-10-2019"), "$lte": new Date("2019-10-11") }}
},
{$group: {
_id: "$category",
total: { $sum: { $toDouble: { $cond: { if: { $ne: [ "$debit", ""] }, then: "$debit", else: "$credit" } } } },
subcategories: { $addToSet: {id: "$subcategory" }},
}}
])
You can to $group twice (by subcategory first):
db.collection.aggregate([
{
$group: {
_id: { category: "$category", subcategory: "$subcategory" },
total: { $sum: { $toDouble: "$debit" } }
}
},
{
$group: {
_id: "$_id.category",
total: { $sum: "$total" },
subcategories: { $push: { id: "$_id.subcategory", total: "$total" } }
}
}
])
Mongo Playground

Need to return matched data from mongo db JSON

I have Json which have values like state_city details this contains information like which city belongs to which state -
Need to query it for particular state name which will gives me all cities that belongs to that state.
db.collection.find({
"count": 10,
"state.name": "MP"
})
[
{
"collection": "collection1",
"count": 10,
"state": [
{
"name": "MH",
"city": "Mumbai"
},
{
"name": "MH",
"city": "Pune"
},
{
"name": "UP",
"city": "Kanpur"
},
{
"name": "CG",
"city": "Raipur"
}
]
},
{
"collection": "collection2",
"count": 20,
"state": [
{
"name": "MP",
"city": "Indore"
},
{
"name": "MH",
"city": "Bhopal"
},
{
"name": "UP",
"city": "Kanpur"
},
{
"name": "CG",
"city": "Raipur"
}
]
}
]
You have to use aggregate query to get only matching elements in array :
db.collection.aggregate([{
$unwind: "$content.state"
},
{
$match: {
"content.state.name": "MH",
"count": 10
}
},
{
$group: {
_id: "$content.state.city",
}
},
{
$addFields: {
key: 1
}
},
{
$group: {
_id: "$key",
cities: {
$push: "$_id"
}
}
},
{
$project: {
_id: 0,
cities: 1
}
}
])
This query will return :
{
"cities": [
"Pune",
"Mumbai"
]
}
The following query would be the solution.
db.collection.find({ "count": 10, "state":{"name": "MP"}})
For more complex queries, $elemMatch is also available.