Adding Group counters and Items counter in MongoDB? - mongodb

I have this Json :
[
{
"key": "guitar",
"name": "john"
},
{
"key": "guitar",
"name": "paul"
},
{
"key": "guitar",
"name": "george"
},
{
"key": "drums",
"name": "ringo"
}
]
Let's group by key and add items to each group :
db.collection.aggregate([
{
$sort: {
name: -1
}
},
{
$group: {
_id: "$key",
"projects": {
"$push": "$$ROOT"
},
count: {
$sum: 1
}
}
},
{
$sort: {
"_id": 1
}
}
])
Result:
[
{
"_id": "drums",
"count": 1,
"projects": [
{
"_id": ObjectId("5a934e000102030405000003"),
"key": "drums",
"name": "ringo"
}
]
},
{
"_id": "guitar",
"count": 3,
"projects": [
{
"_id": ObjectId("5a934e000102030405000001"),
"key": "guitar",
"name": "paul"
},
{
"_id": ObjectId("5a934e000102030405000000"),
"key": "guitar",
"name": "john"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"key": "guitar",
"name": "george"
}
]
}
]
Question:
How can I add counter such as this :
[
{
"_id": "drums",
"Counter":0, // <--------------------------------
"count": 1,
"projects": [
{
"_id": ObjectId("5a934e000102030405000003"),
"key": "drums",
"name": "ringo",
"InnerCounter":0 // <--------------------------------
}
]
},
{
"_id": "guitar",
"Counter":1, // <--------------------------------
"count": 3,
"projects": [
{
"_id": ObjectId("5a934e000102030405000001"),
"key": "guitar",
"name": "paul",
"InnerCounter":0 // <--------------------------------
},
{
"_id": ObjectId("5a934e000102030405000000"),
"key": "guitar",
"name": "john",
"InnerCounter":1 // <--------------------------------
},
{
"_id": ObjectId("5a934e000102030405000002"),
"key": "guitar",
"name": "george",
"InnerCounter":2 // <--------------------------------
}
]
}
]
Live Demo

I don't think is there any other option to do this, You can try using $unwind to create index field in array using includeArrayIndex property,
db.collection.aggregate([
{ $sort: { name: -1 } },
{
$group: {
_id: "$key",
projects: { $push: "$$ROOT" }
}
},
// $unwind "projects" array and store index in field "projects.InnerCounter"
{
$unwind: {
path: "$projects",
includeArrayIndex: "projects.InnerCounter"
}
},
// again group by _id and reconstruct "projects" array
{
$group: {
_id: "$_id",
projects: { $push: "$projects" },
count: { $sum: 1 }
}
},
// sort by "_id"
{ $sort: { "_id": 1 } },
// group by $$ROOT and construct root array
{
$group: {
_id: null,
root: { $push: "$$ROOT" }
}
},
// deconstruct root array and add field "Counter" ofr index counter
{
$unwind: {
path: "$root",
includeArrayIndex: "root.Counter"
}
},
// replace to root
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
Another option for projects array without $unwind,
Playground

Related

Find duplicate name in MongoDB

I'm having a problem in getting the duplicate name in my mongodb to delete duplicates.
{
"users": [
{
"_id": {
"$oid": "61441890a6566a001623b8ed"
},
"name": "Jollibee",
},
{
"_id": {
"$oid": "61441890a6566a001623b8ed"
},
"name": "Jollibee",
},
{
"_id": {
"$oid": "61441890a6566a001623b8ed"
},
"name": "MCDO",
},
{
"_id": {
"$oid": "61441890a6566a001623b8ed"
},
"name": "Burger King",
},
]
}
I want to show in my output only the duplicate names. which is Jollibee.
tried this approach but it only returns me the count of all the users not the duplicated ones. I want to show 2 Jollibee only.
db.collection.aggregate([
{
"$unwind": "$users"
},
{
"$group": {
"_id": "$_id",
"count": {
"$sum": 1
}
}
},
{
"$match": {
"_id": {
"$ne": null
},
"count": {
"$gt": 1
}
}
}
])
Suppose the documents are:
[
{
"_id": {
"$oid": "6226dd742ef592186422ad1d"
},
"name": "Stack test"
},
{
"_id": {
"$oid": "6226dd7d2ef592186422ad1e"
},
"name": "Stack test"
},
{
"_id": {
"$oid": "6226dd912ef592186422ad1f"
},
"name": "Stack test 001"
}
]
Aggreagtion Query:
db.users.aggregate(
[
{
$group: {
_id: "$name",
names: {$push: "$name"}
}
}
]
)
Result:
{
_id: 'Stack test',
names: [ 'Stack test', 'Stack test' ]
},
{
_id: 'Stack test 001',
names: [ 'Stack test 001' ]
}
But a better way to do it will be
Aggregation Query:
db.users.aggregate(
[
{
$group: {
_id: "$name",
count: {$sum: 1}
}
}
]
)
Result:
{
_id: 'Stack test',
count: 2
},
{
_id: 'Stack test 001',
count: 1
}
Now, you can iterate through the count and use the name value in _id
since the $unwind step gives you same _id for all documents grouping by _id is not correct. Instead try grouping by users.name
db.collection.aggregate([
{
"$unwind": "$users"
},
{
"$group": {
"_id": "$users.name",
"count": {
"$sum": 1
}
}
},
{
"$match": {
"_id": {
"$ne": null
},
"count": {
"$gt": 1
}
}
}
])
demo

MongoDB match filters with grouping and get total count

My sample data:
{
"_id": "random_id_1",
"priority": "P1",
"owners": ["user-1", "user-2"],
},
{
"_id": "random_id_2",
"priority": "P1",
"owners": ["user-1", "user-2"],
},
{
"_id": "random_id_3",
"priority": "P2",
"owners": ["user-1", "user-2"],
},
I want to run an aggregation pipeline on the data involving match filters and grouping, also I want to limit the number of groups returned as well as the number of items in each group.
Essentially, if limit=2, limit_per_group=1, group_by=owner, priority=P1, I want the following results:
[
{
"data": [
{
"group_key": "user-1",
"total_items_in_group": 2,
"limited_items": [
{
"_id": "random_id_1",
"priority": "P1",
"owners": ["user-1", "user-2"],
},
],
},
{
"group_key": "user-2",
"total_items_in_group": 2,
"limited_items": [
{
"_id": "random_id_1",
"priority": "P1",
"owners": ["user-1", "user-2"],
},
],
},
]
},
{
"metadata": {
"total_items_matched": 2,
"total_groups": 2
}
},
]
Need some help on how to write an aggregation pipeline to get the required result.
My current query is as follows:
{
"$match": {
"priority": "P1"
}
},
{
"$facet": {
"data": [
{
$addFields: {
"group_by_owners": "$owners"
}
},
{
$unwind: "$group_by_owners"
},
{
$group: {
"_id": "$group_by_owners",
"total_items_in_group": {
$sum: 1
},
"items": {
$push: "$$ROOT"
}
}
},
{
$sort: {
"total": -1
}
},
{
$unset: "items.group_by_owners"
},
{
$project: {
"_id": 1,
"total_items_in_group": 1,
"limited_items": {
$slice: [
"$items",
1
]
}
}
},
{
"$limit": 2
}
],
"metadata": [
{
$count: "total_items_matched"
}
]
}
}
Mongo playground link
I am unable to calculate the total number of groups.
add new stage of $addfields at the end of pipeline
db.collection.aggregate([
{
"$match": {
"priority": "P1"
}
},
{
"$facet": {
"data": [
{
$addFields: {
"group_by_owners": "$owners"
}
},
{
$unwind: "$group_by_owners"
},
{
$group: {
"_id": "$group_by_owners",
"total_items_in_group": {
$sum: 1
},
"items": {
$push: "$$ROOT"
}
}
},
{
$sort: {
"total": -1
}
},
{
$unset: "items.group_by_owners"
},
{
$project: {
"_id": 0,
"group_key": "$_id",
"total_items_in_group": 1,
"limited_items": {
$slice: [
"$items",
1
]
}
}
},
{
"$limit": 2
}
],
"metadata": [
{
$count: "total_items_matched",
}
]
}
},
{
"$addFields": {
"metadata.total_groups": {
"$size": "$data"
}
}
}
])
https://mongoplayground.net/p/y5a0jvr6fxI

Group Subdocuments Array by key in MongoDB

Is it possible to use MongoDB's aggregation framework to group channels using folder key and without joining documents?
[{key: 1, channels: {A: [], B: [], etc}}, {key: 2, channels: {A: [], B: [], etc}}]
I am trying to do using $unwind and then $group by folder name but it seems impossible.
Mongo Playground
Documents:
[
{
key: 1,
channels: [
{
"id": 1,
"name": "XXX",
"folder": "C"
},
{
"id": 2,
"name": "XXX",
"folder": "A"
},
{
"id": 3,
"name": "XXX",
"folder": "B"
},
{
"id": 4,
"name": "XXX",
"folder": "A"
},
{
"id": 5,
"name": "XXX",
"folder": "B"
},
{
"id": 6,
"name": "XXX",
"folder": "C"
}
]
},
{
key: 2,
channels: [
{
"id": 1,
"name": "XXX",
"folder": "D"
},
{
"id": 2,
"name": "XXX",
"folder": "B"
},
{
"id": 3,
"name": "XXX",
"folder": "A"
},
{
"id": 4,
"name": "XXX",
"folder": "C"
},
{
"id": 5,
"name": "XXX",
"folder": "A"
},
{
"id": 6,
"name": "XXX",
"folder": "D"
}
]
}
]
Expected Result:
[
{
key: 1,
channels: {
A: [{
"id": 2,
"name": "XXX"
},{
"id": 4,
"name": "XXX"
}],
B: [{
"id": 3,
"name": "XXX"
}, {
"id": 5,
"name": "XXX"
}],
C: [{
"id": 1,
"name": "XXX"
},{
"id": 6,
"name": "XXX"
}]
}
},
{
"key": 2,
"channels": ...
}
]
Thank you very much in advance.
$group by key and folder, construct channels array by providing required fields
$group by only key and construct the channels array in key-vlaue format
$arrayToObject convert above constructed channels to object
db.collection.aggregate([
{ $match: { key: { $in: [1, 2] } } },
{ $unwind: "$channels" },
{
$group: {
_id: {
id: "$key",
folder: "$channels.folder"
},
channels: {
$push: {
id: "$channels.id",
name: "$channels.name"
}
}
}
},
{
$group: {
_id: "$_id.id",
channels: {
$push: {
k: "$_id.folder",
v: "$channels"
}
}
}
},
{ $project: { channels: { $arrayToObject: "$channels" } } }
])
Playground
You just have to add one more $group stage followed by project stage which will alter the data as per requirement.
db.collection.aggregate([
{
$match: {
key: {
$in: [
1,
2
]
}
}
},
{
$unwind: "$channels"
},
{
$group: {
_id: {
key: "$key",
folder: "$channels.folder"
},
value: {
$push: "$channels",
},
"foldersRef": {
$push: "$channels.folder"
},
}
},
{
$group: {
_id: "$_id.key",
"channels": {
"$push": "$value"
},
},
},
{
"$project": {
"channels": {
"$map": {
"input": "$channels",
"as": "channel",
"in": {
"$arrayToObject": [
[
{
"v": {
"$map": {
"input": "$$channel",
"as": "subChannel",
"in": {
"id": "$$subChannel.id",
"name": "$$subChannel.name",
}
}
},
"k": {
"$arrayElemAt": [
"$$channel.folder",
0
]
},
}
]
],
},
}
},
},
},
])
Mongo Playground Sample Execution
What you have done is more appreciated. You might need two grop to easly preform the aggregations
$unwind to deconstruct the array
$group to group by key and foldername and second group to group by key only, but make the grp as key value pair which helps for $arrayToObject
$replaceRoot to make it to root with other fields _id ($mergeobjects)
$project for projection
Here is the code
db.collection.aggregate([
{ $unwind: "$channels" },
{
"$group": {
"_id": { key: "$key", fname: "$channels.folder" },
"channels": { "$push": "$channels" }
}
},
{
"$group": {
"_id": "$_id.key",
"grp": { "$push": { k: "$_id.fname", v: "$channels" } }
}
},
{ "$addFields": { "grp": { "$arrayToObject": "$grp" } } },
{
"$replaceRoot": {
"newRoot": { "$mergeObjects": [ "$grp", "$$ROOT" ] }
}
},
{
"$project": { grp: 0 }
}
])
Working Mongo playground

How to create nested array inside $group in MongoDB?

After some of the aggregation pipeline stages, my MongoDB Document has the following format:
{
"key": "A",
"status": "To Do",
"val": 4
},
{
"key": "A",
"status": "Done",
"val": 1
},
{
"key": "A",
"status": "To Do",
"val": 3
},
{
"key": "A",
"status": "Done",
"val": 2
},
{
"key": "B",
"status": "Done",
"val": 5
},
{
"key": "B",
"status": "Done",
"val": 6
}
Expected output:
Group by key
Group by status
Push val
{
"key": "A",
"status_val": [
{
"status": "To Do",
"val": [
3,
4
]
},
{
"status": "Done",
"val": [
1,
2
]
}
]
},
{
"key": "B",
"status_val": "Done",
"val": [
5,
6
]
}
Here is what I tried:
db.collection.aggregate([
{
$group: {
_id: {
key:"$key",
status:"$status"
},
v: {
$push: "$val"
}
}
},
{
$group: {
_id: "$_id.key",
status_val: {
$addToSet: {
status: "$_id.status",
val: "$_id.v"
}
}
}},
{
$project: { _id: 0,key:"$_id",status_val:1 }
}
]).pretty()
But I get the wrong output:
[
{
"key": "B",
"status_val": [
{
"status": "Done"
}
]
},
{
"key": "A",
"status_val": [
{
"status": "Done"
},
{
"status": "To Do"
}
]
}
]
How to get nested array inside $group?
You're correctly running $group twice but this line needs to be changed:
val: "$_id.v"
v is not a part of _id, your data after first $group looks like this:
{
"_id": { "key": "B, "status": "Done" },
"v": [ 5, 6 ]
}
try:
db.collection.aggregate([
{
$group: {
_id: { key: "$key", status: "$status" },
val: { $push: "$val" }
}
},
{
$group: {
_id: "$_id.key",
status_val: {
$push: {
status: "$_id.status",
val: "$val"
}
}
}
},
{
$project: {
_id: 0,
key: "$_id",
status_val: 1
}
}
])
Mongo Playground

mongodb aggregation with multiple sub groups

I have a collection with documents that look similar to this:
[
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeA",
"color": "ColorA",
"soldFor": 12.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeA",
"color": "ColorB",
"soldFor": 13.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeB",
"color": "ColorA",
"soldFor": 12.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeB",
"color": "ColorB",
"soldFor": 12.15
}
]
I know that this is not a good way to store such information, but unfortunately I have no influence in that.
What I need to get out of the collection is something like this:
[
2017: {
typeA: {
colorA: {
sum: 125.00
},
colorB: {
sum: 110.00
}
},
typeB: {
colorA: {
sum: 125.000
}
}
},
2016: {
typeA: {
colorB: {
sum: 125.000
}
}
}
]
At the moment I have two group stages that give me everything grouped by year, but I have no clue how to get the two other sub-groups. Building the sum would be a nice to have, but I am certain that I can figure out how that would be done in a group.
So far my pipeline looks like this:
[
{
$group: {
_id: { type: '$type', color: '$color', year: { $year: '$date' } },
docs: {
$push: '$$ROOT'
}
}
},
{
$group: {
_id: { year: '$_id.year' },
docs: {
$push: '$$ROOT'
}
}
}
]
which results in something like this:
[
{
"_id": {
"year": 2006
},
"docs": {
"_id": {
"type": "typeA",
"color": "colorA",
"year": 2006
},
"docs": [
{
... root document
}
]
}
},
{
"_id": {
"year": 2016
},
"docs": [
{
"_id": {
"type": "typeA",
"color": "colorB",
"year": 2016
},
"docs": [
{
... root document
}
]
}
... more docs with three keys in id
]
}
]
Help is much appreciated!
Using a cohort of operators found in MongoDB 3.4.4 and newer, i.e. $addFields, $arrayToObject and $replaceRoot, you can compose a pipeline like the following to get the desired result:
[
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"type": "$type",
"color": "$color"
},
"count": { "$sum": "$soldFor" }
} },
{ "$group": {
"_id": {
"year": "$_id.year",
"type": "$_id.type"
},
"counts": {
"$push": {
"k": "$_id.color",
"v": { "sum": "$count" }
}
}
} },
{ "$addFields": {
"counts": { "$arrayToObject": "$counts" }
} },
{ "$group": {
"_id": "$_id.year",
"counts": {
"$push": {
"k": "$_id.type",
"v": "$counts"
}
}
} },
{ "$addFields": {
"counts": { "$arrayToObject": "$counts" }
} },
{ "$group": {
"_id": null,
"counts": {
"$push": {
"k": { "$substr": ["$_id", 0, -1 ]},
"v": "$counts"
}
}
} },
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": "$counts" },
"$$ROOT"
]
}
} },
{ "$project": { "counts": 0 } }
]