I have two collections like this :
1st collection name is Promotions:
{
"_id": "A019283847466",
"code": "AAA",
"aliases" : [ "AAA1","AAA2","AAA3"]
}
2nd Collection name is PromotionUsages:
{
"customerId": "_1234567890"
"code": "AAA1"
}
{
"customerId": "_0987654321"
"code": "AAA1"
}
Expected output is :
{
"code": "AAA"
"aliasCode": "AAA1"
"countUsages": 2
}
I used mongo $group and $aggregate but I am not getting required output
any help please
Thank You!!!
You can try below aggregation
db.collection.aggregate([
{ "$unwind": "$aliases" },
{ "$addFields": { "aliasesCode": "$aliases" }},
{ "$lookup": {
"from": PromotionUsages.collection.name,
"let": { "aliases": "$aliases" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$code", "$$aliases" ] } } },
{ "$count": "countUsages" }
],
"as": "aliases"
}},
{ "$unwind": "$aliases" },
{ "$project": { "code": 1, "aliasCode": 1, "countUsages": "$aliases.countUsages" }}
])
Output
{
"code": "AAA"
"aliasCode": "AAA1"
"countUsages": 2
}
Made some changes in solution suggested by Anthony Winzlet
db.promotions.aggregate([
{ "$unwind": "$aliases" },
{ "$addFields": { "aliasesCode": "$aliases" }},
{ "$lookup": {
"from": "promotionusages",
"let": { "aliases": "$aliases" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$code", "$$aliases" ] } } },
{ "$count": "countUsages" }
],
"as": "aliases"
}},
{ "$unwind": "$aliases" },
{ "$project": { "_id" : 0, "code": 1, "aliasesCode": 1, "countUsages": "$aliases.countUsages" }}
])
Related
I want to convert my existing code to mongoDB aggregation framework.
Here's my code that I want to convert in aggregation.
const comments = await Comments.find({post:req.params.id})
.populate({
path:"comments",
populate:{
path:"author",
select:"name photo"
},
select:{
createdAt:1,
author:1,
_id:1,
body:1,
likes:1
}
})
.populate("author","photo name")
.select({
createdAt:1,
author:1,
_id:1,
body:1,
comments:1,
likes:1
})
.sort("-createdAt");
You can use below aggregation
Comments.aggregate([
{ "$match": { "post": mongoose.Types.ObjectId(req.params.id) } },
{ "$sort": { "createdAt": -1 } },
{
"$lookup": {
"from": Comment.collection.name,
"let": { "comments": "$comments" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$comments"] } } },
{
"$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [{ "$match": { "$expr": { "$eq": ["$_id", "$$author"] } } }, { "$project": { "name": 1, "photo": 1 } }],
"as": "author"
}
},
{ "$unwind": "$author" },
{ "$project": { "createdAt": 1, "author": 1, "_id": 1, "body": 1, "likes": 1 } }
],
"as": "comments"
}
},
{
"$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [{ "$match": { "$expr": { "$eq": ["$_id", "$$author"] } } }, { "$project": { "name": 1, "photo": 1 } }],
"as": "author"
}
},
{ "$unwind": "$author" },
{
"$project": {
"createdAt": 1,
"author": 1,
"body": 1,
"comments": 1,
"likes": 1
}
}
])
i want to add isFavorite as true/false, if user mark that combination as favorite."isFavorite" : 0 means user already marked that item as favorite.my query is
db.getCollection('itemorders').aggregate([
{ "$match": {"customerId" : ObjectId("5e78a07c0ce36c23dcf85e6a"), 'isCart': 0}},
{ "$lookup": {
"from": "itemlistorders",
"let": { "cartId": { "$toObjectId" : "$_id" }},
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$cartId", "$$cartId"] }}},
],
"as": "itemOrdered"
} },
{$unwind: { path: "$itemOrdered", preserveNullAndEmptyArrays: true }},
{ "$lookup": {
"from": "favoritedrinks",
"let": { "uniqueCartKey": "$itemOrdered.uniqueCartKey" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$uniqueFavKey", "$$uniqueCartKey"] }}},
{ "$addFields": { "isFavorite": true }}
],
"as": "mixerList"
} },
{$unwind: { path: "$itemOrdered", preserveNullAndEmptyArrays: true }},
{
"$group": {
"_id": "$_id",
"grantTotal" : { "$first": "$grantTotal" },
"customerName" : { "$first": "$customerName" },
"barName" : { "$first": "$barName" },
"itemOrdered": { "$addToSet": "$itemOrdered" },
}
},{
$project: {
'_id': 1,
'grantTotal': 1,
'customerName': 1,
'barName': 1,
'itemOrdered': 1,
}
},
]);
My output after excusting this is :
{
"_id" : ObjectId("5e8224768910e83e908d0108"),
"itemOrdered" : [
{
"_id" : ObjectId("5e82257250c82e1abc16d856"),
"barId" : ObjectId("5e55f60eff2f842de4ae6184"),
"customerId" : ObjectId("5e78a07c0ce36c23dcf85e6a"),
"isCart" : 0,
"isAlcoholActive" : 1,
"alcoholId" : "5e81d13e50c82e1abc0a1ebb",
"alcoholName" : "Irish Coffee",
"cartId" : ObjectId("5e8224768910e83e908d0108"),
"itemTotal" : 250,
"noofDrinks" : 2
},
{
"_id" : ObjectId("5e8224e88910e83e908d0110"),
"isCart" : 0,
"isAlcoholActive" : 1,
"isFavorite" : 0,
"customerId" : ObjectId("5e78a07c0ce36c23dcf85e6a"),
"barId" : ObjectId("5e55f60eff2f842de4ae6184"),
"noofDrinks" : 1,
"itemTotal" : 200,
"mixerList" : [
{
"isMixerActive" : 0,
"_id" : ObjectId("5e8224e88910e83e908d0111"),
"mixerId" : "5e820b0250c82e1abc12cffc",
"mixerName" : "Pineapple juice"
}
],
"alcoholId" : "5e81d17150c82e1abc0a2607",
"alcoholName" : "Fireball",
"cartId" : ObjectId("5e8224768910e83e908d0108"),
}
]
};
i want to add isFavorite in each subdocument,so that i can identify whether user favorite this item or not.
below query worked for my scenerio.
db.getCollection('itemorders').aggregate([
{ "$match": {"customerId" : ObjectId("5e78a07c0ce36c23dcf85e6a"), 'isCart': 0}},
{ "$lookup": {
"from": "itemlistorders",
"let": { "cartId": { "$toObjectId" : "$_id" }},
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$cartId", "$$cartId"] }}},
],
"as": "itemOrdered"
} },
{$unwind: { path: "$itemOrdered", preserveNullAndEmptyArrays: true }},
{ "$lookup": {
"from": "favoritedrinks",
"let": { "uniqueCartKey": "$itemOrdered.uniqueCartKey","customerId1": "$itemOrdered.customerId", "modifierfavId": "$itemOrdered._id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$uniqueFavKey", "$$uniqueCartKey"
]
},
{
$eq: [
"$customerId",
"$$customerId1"
]
}
]
}
}
},{ "$addFields": { "isFavorite": 'true' }}
],
"as": "itemListOrdered"
} },
{ "$addFields": { "itemOrdered.isFavorite": { "$gt": ["$itemOrdered.isFavorite", null] } }},
{
"$group": {
"_id": "$_id",
"grantTotal" : { "$first": "$grantTotal" },
"customerName" : { "$first": "$customerName" },
"barName" : { "$first": "$barName" },
"orderDate" : { "$first": "$orderDate" },
"itemOrdered": { "$push": "$itemOrdered" },
}
}
])
I'm using mongoDB 3.6 on node.js 8.11.1 and working with MongoDB Node.js Driver.
I have two collections, 'group' and 'user':
group:
[
{
"_id":1,
"groupName":"group1",
"users":[
{
"userId":1,
"isAdmin":"false"
},
{
"userId":2,
"isAdmin":"true"
}
]
},
{
"_id":2,
"groupName":"group2",
"users":[
{
"userId":2,
"isAdmin":"false"
},
{
"userId":3,
"isAdmin":"true"
}
]
}
]
user:
[
{
"_id":1,
"username":"user1",
"firstname":"a",
"lastname":"aa",
"mobileNo":"+1111111"
},
{
"_id":2,
"username":"user2",
"firstname":"b",
"lastname":"bb",
"mobileNo":"+2222222"
},
{
"_id":3,
"username":"user3",
"firstname":"c",
"lastname":"cc",
"mobileNo":"+3333333"
}
]
I need an aggregate to return something like this:
[
{
"_id":1,
"groupName":"group1",
"members":[
{
"isAdmin":"false",
"username":"user1",
"firstname":"a",
"lastname":"aa"
},
{
"isAdmin":"true",
"username":"user2",
"firstname":"b",
"lastname":"bb"
}
]
},
{
"_id":2,
"groupName":"group2",
"members":[
{
"isAdmin":"false",
"username":"user2",
"firstname":"b",
"lastname":"bb"
},
{
"isAdmin":"true",
"username":"user3",
"firstname":"c",
"lastname":"cc"
}
]
}
]
At "members" in result, "isAdmin" return from "users" at group collection and "username", "firstname" and "lastname" came from user collection
Many thanks,
Milad.
You can try below aggregation from mongodb 3.6 and above
db.group.aggregate([
{ "$unwind": "$users" },
{ "$lookup": {
"from": Users.collection.name,
"let": { "userId": "$users.userId", "isAdmin": "$users.isAdmin" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userId" ] } } },
{ "$project": { "isAdmin": "$$isAdmin", "username": 1, "firstName": 1, "lastName": 1 }}
],
"as": "members"
}},
{ "$unwind": "$members" },
{ "$group": {
"_id": "$_id",
"members": { "$push": "$members" },
"groupName": { "$first": "$groupName" }
}}
])
I have following collection
[
{
"setting": "Volume",
"_id": ObjectId("5a934e000102030405000000"),
"counting": 1
},
{
"setting": "Brightness",
"_id": ObjectId("5a934e000102030405000001"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000002"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000003"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000004"),
"counting": 0
},
{
"setting": "Sharpness",
"_id": ObjectId("5a934e000102030405000005"),
"counting": 1
},
{
"setting": "Sharpness",
"_id": ObjectId("5a934e000102030405000006"),
"counting": 1
},
{
"setting": "Language",
"_id": ObjectId("5a934e000102030405000007"),
"counting": 1
},
{
"setting": "Language",
"_id": ObjectId("5a934e000102030405000008"),
"counting": 0
}
]
Now I want to group by setting and want only top most two data in result rest in useless
So my output should be after sort by counting
[
{
"setting": "Contrast",
"counting": 2
},
{
"setting": "Sharpness",
"counting": 2
},
{
"setting": "Useless",
"counting": 3
}
]
If you can get away with it, then it's probably best to "stuff" the reduced results into a single document and then $slice the top two and $sum the rest:
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$group": {
"_id": null,
"data": { "$push": "$$ROOT" }
}},
{ "$addFields": {
"data": {
"$let": {
"vars": { "top": { "$slice": ["$data", 0, 2 ] } },
"in": {
"$concatArrays": [
"$$top",
{ "$cond": {
"if": { "$gt": [{ "$size": "$data" }, 2] },
"then":
[{
"_id": "Useless",
"counting": {
"$sum": {
"$map": {
"input": {
"$filter": {
"input": "$data",
"cond": { "$not": { "$in": [ "$$this._id", "$$top._id" ] } }
}
},
"in": "$$this.counting"
}
}
}
}],
"else": []
}}
]
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
If it's potentially a very "large" result even reduced, then $limit use a $facet for the "rest":
Model.aggregate([
{ "$facet": {
"top": [
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 }
],
"rest": [
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$skip": 2 },
{ "$group": {
"_id": "Useless",
"counting": { "$sum": "$counting" }
}}
]
}},
{ "$project": {
"data": {
"$concatArrays": [
"$top","$rest"
]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
Or even $lookup with MongoDB 3.6:
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
{ "$group": {
"_id": null,
"top": { "$push": "$$ROOT" }
}},
{ "$lookup": {
"from": "colllection",
"let": { "settings": "$top._id" },
"pipeline": [
{ "$match": {
"$expr": {
"$not": { "$in": [ "$setting", "$$settings" ] }
}
}},
{ "$group": {
"_id": "Useless",
"counting": { "$sum": "$counting" }
}}
],
"as": "rest"
}},
{ "$project": {
"data": {
"$concatArrays": [ "$top", "$rest" ]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
All pretty much the same really, and all return the same result:
{ "_id" : "Contrast", "counting" : 2 }
{ "_id" : "Sharpness", "counting" : 2 }
{ "_id" : "Useless", "counting" : 3 }
Optionally $project right at the end of each instead of the $replaceRoot if control over the field names is really important to you. Generally I just stick with the $group defaults
In the event that your MongoDB predates 3.4 and the resulting "Useless" remainder is actually too large to use any variant of the first approach, then simple Promise resolution is basically the answer, being one for the aggregate and the other for a basic count and simply do the math:
let [docs, count] = await Promise.all([
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
]),
Model.count().exec()
]);
docs = [
...docs,
{
"_id": "Useless",
"counting": count - docs.reduce((o,e) => o + e.counting, 0)
}
];
Or without the async/await:
Promise.all([
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
]),
Model.count().exec()
]).then(([docs, count]) => ([
...docs,
{
"_id": "Useless",
"counting": count - docs.reduce((o,e) => o + e.counting, 0)
}
]).then( result => /* do something */ )
Which is basically a variation on the age old "total pages" approach by simply running the separate query to count the collection items.
Running separate requests is generally the age old way of doing this and it often performs best. The rest of the solutions are essentially aimed at "aggregation tricks" since that was what you were asking for, and that's the answer you got by showing different variations on the same thing.
One variant put's all results into a single document ( where possible, due to the BSON limit of course ) and the others basically vary on the "age old" approach by running the query again in a different form. $facet in parallel and $lookup in series.
I have query something like this:
Message.aggregate([{
"$match": {
$or: [{
"to": userId
}, {
"from": userId
}]
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$group": {
"_id": "$conversationId",
"from": {
"$first": "$from"
},
"to": {
"$first": "$to"
},
"content": {
"$first": "$content"
},
"createDate": {
"$first": "$createDate"
},
"unreaded": {
"$sum": {
"$cond": {
if: {
$and: [{
"$eq": [
"$unreaded", 1
]
},
{
"$eq": ["$to", userId]
}
]
},
then: 1,
else: 0
}
}
}
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$lookup": {
"from": "users",
"localField": "from",
"foreignField": "_id",
"as": "from"
}
},
{
"$lookup": {
"from": "users",
"localField": "to",
"foreignField": "_id",
"as": "to"
}
},
{
"$unwind": {
"path": "$from"
}
},
{
"$unwind": {
"path": "$to"
}
},
{
"$project": {
"from.firstName": "$from.firstName",
"from.lastName": "$from.lastName",
"from.picture": "$from.picture",
"to.firstName": "$to.firstName",
"to.lastName": "$to.lastName",
"to.picture": "$to.picture",
"content": 1,
"createDate": 1,
"unreaded": 1,
"reciver": {
"$cond": {
if: {
"$eq": ["$from._id", mongoose.Types.ObjectId(userId)]
},
then: {
"firstName": "$to.firstName",
"lastName": "$to.lastName",
"_id": "$to._id"
},
else: {
"firstName": "$from.firstName",
"lastName": "$from.lastName",
"_id": "$from._id"
}
}
}
}
},
{
"$limit": 50
}
I am able now to limit records to 50 per request but problem is when I try to make pagination... I get this error when I try to add skip next to limit:
Error: Arguments must be aggregate pipeline operators
any idea how can I do that?
Check your args and correct mistake.
Message.aggregate([{"$match": {$or: [{"to": userId}, {"from": userId}]}}, ..., {$skip: 1}, {$limit: 1}])