I have this collection:
[{
"_id": "5c378eecd11e570240a9b0ac",
"userID": "1",
"isActive": "Active",
"areaCode": "A-1",
"__v": 0
},
{
"_id": "5c378eecd11e570240a9b0bb",
"userID": "1",
"isActive": "Active",
"areaCode": "A-2",
"__v": 0
},
{
"_id": "5c378eecd11e570240a9b0c5",
"userID": "2",
"isActive": "Active",
"areaCode": "A-1",
"__v": 0
}]
Need help in grouping the results by user ID then the area code using aggregation but I'm not getting the desired output. Here's what I've tried:
AreaCodes.aggregate([
{
'$match': { '$and': [
{ 'isActive': 'Active' },
{ 'userID': { '$exists': true } }
]
}
},
{
'$group': {
'_id': {
'userID': '$userID'
},
'entries': {
'$push': {
'areaCode': '$areaCode'
}
}
}
},
{
'$group': {
'_id': '$_id.userID',
'areaCodes': {
'$push': {
'areaCode': '$entries'
}
}
}
},
{
'$project': {
'_id': 0,
'userID': '$_id',
'areaCodes': '$areaCodes'
}
}
])
Which returns the following:
[
{
"userID": "1",
"areaCodes": [
{
"areaCode": [
{
"areaCode": "A-1"
},
{
"areaCode": "A-2"
}
]
}
]
},
{
"userID": "2",
"areaCodes": [
{
"areaCode": [
{
"areaCode": "A-1"
}
]
}
]
}
]
My desired output would be to remove the excess areaCode objects and group them inside an array for each user like:
[
{
"userID": "1",
"areaCodes": ["A-1", "A-2"]
},
{
"userID": "2",
"areaCodes": ["A-1"]
}
]
How to achieve this format? Thanks.
How about:
db.collection.aggregate([
{
$match: {
$and: [{ "isActive": "Active" }, {"userID": {"$exists": true}}]
}
},
{
$group: {
_id: '$userID',
areaCodes: {$addToSet: "$areaCode"}
}
},
{
$project: {
_id: 0,
userID: "$_id",
areaCodes: 1
}
}
])
As you can see on this playgeound example.
If you just want the matching areaCodes, you can simply use $addToSet.
Related
Problem:
I need to only update one document in the spots available array that has an id of "empty". My previous query was updating all matching sub documents with "empty" as the id; which is no good Example Below. So I decided to use aggregation so that I could add a limit stage so that I could only update one item, but come to find out I cannot update the original document with an aggregation. This leaves the only option to use an array filter that only updates one/first of its matches. Is this possible? I feel like there has to be a way to only update one match on an array filter and if there isn't this is definitely something that should be added.
My code:
This code updates every object with "empty"
const client = await clientPromise;
const db = client.db();
// const query = db.collection('events').aggregate(agg);
const query = await db.collection('events').updateOne({
_id: new ObjectId("6398c34ca67dbe3286452f23"),
createdBy: new ObjectId("636c1778f1d09191074f9690"),
"weights.weight": 12
},
{
$set: {
"weights.$.spotsAvailable.$[el2]": {
"name": "Wayne Wrestler",
"userId": new ObjectId("636c1778f1d09191074f9690")
}
}
},
{
arrayFilters: [{ "el2": { "userId": "empty" } }]
})
Example documents:
Event:
{
"_id": {
"$oid": "6398c34ca67dbe3286452f23"
},
"name": "test",
"createdBy": {
"$oid": "636c1778f1d09191074f9690"
},
"description": "testing",
"date": {
"$date": {
"$numberLong": "1645488000000"
}
},
"location": {
"type": "Point",
"coordinates": [
0,
0
]
},
"weights": [
{
"spotsAvailable": [
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
}
],
"weight": 12
},
{
"spotsAvailable": [
{
// only one of these should've been updated, but both were
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
},
{
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
}
],
"weight": 15
}
],
"eventApplicants": [
{
"userId": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"weight": 12
}
]
}
User:
{
"_id": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"email": "wakywayne80#gmail.com",
"image": "https://lh3.googleusercontent.com/a/ALm5wu32gXjDIRxncjjQA9I4Yl-sjFH5EWsTlmvdM_0kiw=s96-c",
"emailVerified": {
"$date": {
"$numberLong": "1670864727212"
}
},
"createdEvents": [
{
"createdEventName": "test",
"createdEventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"createdEventDescription": "testing",
"createdEventWeights": [
{
"weight": "12",
"filled": [
false,
false,
false
]
},
{
"weight": "15",
"filled": [
false,
false
]
}
],
"createdEventId": {
"$oid": "6398c34ca67dbe3286452f23"
}
}
],
"userSignedUpEvents": [],
"availableWeights": [
1,
123
],
"signedUpEvents": [
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "637ec484ac2d675b30590b47"
},
"eventName": "Maybe?",
"eventDate": {
"$date": {
"$numberLong": "1672272000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "638d5274628db2a7bf61df49"
},
"eventName": "Eva's",
"eventDate": {
"$date": {
"$numberLong": "1698019200000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398a922abb5c168ede595fb"
},
"eventName": "Nikko's event",
"eventDate": {
"$date": {
"$numberLong": "1670976000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398a922abb5c168ede595fb"
},
"eventName": "Nikko's event",
"eventDate": {
"$date": {
"$numberLong": "1670976000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
}
]
}
I have tried:
Pluging in variables without the new ObjectId syntax
Plugin in variables with the new ObjectId syntax
Using the exact same hardcoded values that I got from copying the aggregation code from compass for the node driver
All of these either don't work or result in every subdocument with "empty" getting filled
One option is to use update with pipeline:
Since this is a double nested array, it is easier to do it in two steps - internal and external
First create the "external" item to replace in weights array and call it newItem. It is calculated using $reduce which allow us to manipulate the internal array while looping on it.
Replace the relevant item on weights array with our newItem using $map with $cond
db.collection.update(
{_id: ObjectId("6398c34ca67dbe3286452f23"), "weights.weight": 12},
[
{$set: {
newItem: {$reduce: {
input: {$getField: {
input: {$first: {
$filter: {
input: "$weights",
as: "item",
cond: {$eq: ["$$item.weight", 12]}
}
}},
field: "spotsAvailable"
}},
initialValue: [],
in: {$concatArrays: [
"$$value",
{$cond: [
{$and: [
{$eq: ["$$this.userId", "empty"]},
{$not: {$in: [ObjectId("636c1778f1d09191074f9690"), "$$value.userId"]}}
]},
[{
name: "Wayne Wrestler",
userId: ObjectId("636c1778f1d09191074f9690")
}],
["$$this"]
]}
]}
}}
}},
{$set: {
weights: {$map: {
input: "$weights",
in: {$cond: [
{$eq: ["$$this.weight", 12]},
{$mergeObjects: [
"$$this",
{spotsAvailable: "$newItem"}
]},
"$$this"
]}
}},
newItem: "$$REMOVE"
}}
])
See how it works on the playground example
You can first $unwind the weights for easier processing first. Use $reduce to iterate through the weights.spotsAvailable array and use a compound object to store the result and a flag to indicate whether it is updated or not. Finally use the result to $merge back to the original document.
db.collection.aggregate([
{
$match: {
"_id": ObjectId("6398c34ca67dbe3286452f23"),
createdBy: ObjectId("636c1778f1d09191074f9690"),
"weights.weight": 12,
"weights.spotsAvailable.userId": "empty"
}
},
{
"$unwind": "$weights"
},
{
"$addFields": {
"results": {
"$reduce": {
"input": "$weights.spotsAvailable",
"initialValue": {
result: [],
updated: false
},
"in": {
"$cond": {
"if": {
$and: [
{
$eq: [
false,
"$$value.updated"
]
},
{
$eq: [
"empty",
"$$this.userId"
]
}
]
},
"then": {
result: {
"$concatArrays": [
"$$value.result",
[
{
"name": "Wayne Wrestler",
"userId": ObjectId("636c1778f1d09191074f9690")
}
]
]
},
updated: true
},
"else": {
result: {
"$concatArrays": [
"$$value.result",
[
"$$this"
]
]
},
updated: "$$value.updated"
}
}
}
}
}
}
},
{
$set: {
"weights.spotsAvailable": "$results.result",
"results": "$$REMOVE"
}
},
{
$group: {
_id: "$_id",
"name": {
$first: "$name"
},
"createdBy": {
$first: "$createdBy"
},
"description": {
$first: "$description"
},
"date": {
$first: "$date"
},
"location": {
$first: "$location"
},
"weights": {
$push: "$weights"
},
"eventApplicants": {
$first: "$eventApplicants"
}
}
},
{
"$merge": {
"into": "collection",
"on": "_id"
}
}
])
Mongo Playground
I am fetching for category and subcategory i am using the following pipeline
Category:
{
"$match": {
"category": {
"$in": ["list of my categories"]
}
}
},
{
"$group": {
"category": "$category",
"count": {
"$sum": 1
}
}
},
This gives me:
{category: category name,
count: totalcount}
Subcategory pipeline
{
"$match": {
"category": {
"$in": ["list of my categories"]
}
}
},
{
"$group": {
"_id": { subCategory: "$subCategory", category: "$category" },
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.category",
"counts": {
"$push": {
"k": "$_id.subCategory",
"v": "$count"
},
},
"count":{
$sum: "$counts"
}
}
},
{
"$project": {
"counts": { "$arrayToObject": "$counts" },
}
},
This gives me
category: name {
subcategory1 : total count,
...
}
How do I join the two to get a single call to return something like
{category: categoryname,
count: totalcountforcategory,
subcategories: {
subcategory: totalcount,
subcategory2:totalcount}
Update
Here is my sample JSON
{
"category": "Category one",
"name": "Sample name",
"subCategory": "subCategory one",
},
{
"category": "Category one",
"name": "Sample name",
"subCategory": "subCategory two",
},
{
"category": "Category two",
"name": "Sample name",
"subCategory": "subCategory one",
},
{
"category": "Category one",
"name": "Sample name",
"subCategory": "subCategory two",
}
Expected OutPut
{
"Category one": 3,
subCategories: {
"subCategoryone": 2,
"subCategorytwo":3,
}
}
{
"Category two": 5,
subCategories: {
"subCategoryone": 2,
"subCategorytwo":3,
}
}
db.collection.aggregate([
{
"$group": {
"_id": {
cat: "$category",
sub: "$subCategory"
},
"count": {
"$sum": 1
},
"subCategory": {
$push: "$$ROOT"
}
}
},
{
"$group": {
"_id": "$_id.cat",
"counts": {
"$push": {
"k": "$_id.sub",
"v": "$count"
},
},
"count": {
$sum: "$counts"
}
}
},
{
"$project": {
"counts": {
"$arrayToObject": "$counts"
},
}
},
])
Playground
I used $facet to join pipelines i don't know if it is best practice or not
const pipeline = [
{
"$facet": {
"GroupAll":
[{ $project: { name: 1, category: 1,subCategory:1,votes:1 } }]
,
"GroupTotal": [
{
"$match": {
"nominationYear": {
"$eq": "2022"
}
}
},
{
$count: "total"
}
],
"GroupCategories": [
{
"$match": {
"category": {
"$in": ["Categories"]
}
}
},
{
"$group": {
"_id": { category: "$category" },
"count": {
"$sum": 1
}
}
},
],
"GroupSubCategories": [
{
"$match": {
"category": {
"$in": ["Categories"]
}
}
},
{
"$group": {
"_id": { subCategory: "$subCategory", category: "$category" },
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.category",
"counts": {
"$push": {
"k": "$_id.subCategory",
"v": "$count"
},
},
"count": {
$sum: "$counts"
}
}
},
{
"$project": {
"counts": { "$arrayToObject": "$counts" },
}
},
]
}
}
]
And here is my output
{
"nominations": [
{
"GroupAll": [""],
"GroupTotal": [""],
"GroupCategories": [""],
"GroupSubCategories":[""]
}
],
"total": ""
}
I achieved it using $facet to join multiple pipelines
Here is my MongoDB schema:
{
"_id": "603f23ff6c1d862e5ced9e35",
"reviews": [
{
"like": 0,
"dislike": 0,
"_id": "603f23ff6c1d862e5ced9e34",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
},
{
"like": 0,
"dislike": 0,
"_id": "603f242a6c1d862e5ced9e36",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
}
]
productID:"hdy6nch99dndn"
}
I want to use aggregate to get the review object of a particular id. I tried but not with any success.
Here is my code:
ProductReview.aggregate([
{ $match: { productID: productID } }
])
$match
$unwind
db.collection.aggregate([
{
$match: {
productID: 1
}
},
{
$unwind: "$reviews"
},
{
$match: {
"reviews._id": 2
}
}
])
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"productID": 1,
"reviews": {
"_id": 2,
"comment": "second comment",
"dislikes": [
{
"userID": 3
},
{
"userID": 4
}
],
"likes": [
{
"userID": 1
},
{
"userID": 2
}
]
}
}
]
Mongo Playground: https://mongoplayground.net/p/qfWS1rCuMfc
i have a problem with aggregation framework in MongoDB (mongoose) this is the problem. i have the following database scheme.so what i want to do is count number of people who has access through Mobile only , Card only, or both. with out any order,
{
'_id': ObjectId,
'user_access_type': ['Mobile' , 'Card']
}
{
'_id': ObjectId,
'user_access_type': ['Card' , 'Mobile']
}
{
'_id': ObjectId,
'user_access_type': ['Mobile']
}
{
'_id': ObjectId,
'user_access_type': ['Card']
}
Now i am using this but it only groups by the order of the user_access_type array,
[ { "$group" : { "_id": {"User" : "$user_access_type"} , "count": {"$sum" : 1} }]
this is the output:
{
"_id": {
"User": [
"Card",
"Mobile"
]
},
"count": 1
},
{
"_id": {
"_id": "5f7dce2359aaf004985f98eb",
"User": [
"Mobile",
"Card"
]
},
"count": 1
},
{
"_id": {
"User": [
"Mobile"
]
},
"count": 1
},
{
"_id": {
"User": [
"Card"
]
},
"count": 1
},
vs what i want:
{
"_id": {
"User": [
"Card",
"Mobile" // we can say both
]
},
"count": 2 // does not depend on order
},
{
"_id": {
"User": [
"Mobile"
]
},
"count": 1
},
{
"_id": {
"User": [
"Card"
]
},
"count": 1
},
You can use other option as well using $function,
$function can allow to add javascript code, you can use sort() to sort the array
db.collection.aggregate([
{
$addFields: {
user_access_type: {
$function: {
body: function(user_access_type){
return user_access_type.sort();
},
args: ["$user_access_type"],
lang: "js"
}
}
}
},
{
$group: {
_id: "$user_access_type",
count: { $sum: 1 }
}
}
])
Second option,
If user_access_type array having always unique elements then you can use $setUnion operator on user_access_type array as self union, some how this will re-order array in same order,
db.collection.aggregate([
{
$addFields: {
user_access_type: {
$setUnion: "$user_access_type"
}
}
},
{
$group: {
_id: "$user_access_type",
count: { $sum: 1 }
}
}
])
Playground
I have a collection similar to this:
[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
I want to aggregate into something like this:
{
"breakfast":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"lunch":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
I have only managed to group them but I can't change the key meals to either breakfast or lunch depending on the meal.name(group name)
$group: {
_id: { meal: '$meal.name' },
meals: { $push: '$meal' },
}
Using the above code I have managed to produce the output below. My only challenge is changing the key meals to either breakfast or lunch as explained above in the subgroups.
{
"meals":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"meals":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
Here you can have your answer .
After "grouping" to add to an array you similarly $push all that content into array by the "name" grouping key and then convert into keys of a document in a $replaceRoot with $arrayToObject:
db.collection.aggregate([
{ "$group": {
"_id": "$name",
"data": { "$push": "$$ROOT" }
}},
{ "$group": {
"_id": null,
"data": {
"$push": {
"k": "$_id",
"v": "$data"
}
}
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
OUTPUT
[
{
"breakfast": [
{
"_id": 1,
"name": "breakfast",
"time": "10.00"
},
{
"_id": 2,
"name": "breakfast",
"time": "10.10"
}
],
"lunch": [
{
"_id": 3,
"name": "lunch",
"time": "12.07"
},
{
"_id": 4,
"name": "lunch",
"time": "12.45"
}
]
}
]
You can check the result of above query in this LINK