I have a collection that looks something like:
[
{
"_id": "5f0307520ac9361c0d7088e2",
"productId": 1,
"stock": 10,
"unit": "item",
"price": 20,
"images": [
"http://productimages.com/1/001.jpg",
"http://productimages.com/1/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e3",
"productId": 1,
"stock": 20,
"unit": "item",
"price": 30,
"images": [
"http://productimages.com/1/003.jpg",
"http://productimages.com/1/004.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e4",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 15,
"images": [
"http://productimages.com/2/001.jpg",
"http://productimages.com/2/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e5",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 12,
"images": [
"http://productimages.com/2/003.jpg",
"http://productimages.com/2/004.jpg"
]
}
]
And I aggregate it as follows:
db.variants.aggregate([
{
"$group": {
"_id": "$productId",
"price": { "$min": "$price" },
"stock": { "$sum": "$stock" },
"unit": { "$first": "$unit" },
"images": { "$push": "$images" },
"variants": { "$push": "$$ROOT" }
}
}
]).pretty()
which produces the following output:
[
{
"_id": 2,
"price": 12,
"stock": 10,
"unit": "item",
"images": [
[
"http://productimages.com/2/001.jpg",
"http://productimages.com/2/002.jpg"
],
[
"http://productimages.com/2/003.jpg",
"http://productimages.com/2/004.jpg"
]
],
"variants": [
{
"_id": "5f0307520ac9361c0d7088e4",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 15,
"images": [
"http://productimages.com/2/001.jpg",
"http://productimages.com/2/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e5",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 12,
"images": [
"http://productimages.com/2/003.jpg",
"http://productimages.com/2/004.jpg"
]
}
]
},
{
"_id": 1,
"price": 20,
"stock": 30,
"unit": "item",
"images": [
[
"http://productimages.com/1/001.jpg",
"http://productimages.com/1/002.jpg"
],
[
"http://productimages.com/1/003.jpg",
"http://productimages.com/1/004.jpg"
]
],
"variants": [
{
"_id": "5f0307520ac9361c0d7088e2",
"productId": 1,
"stock": 10,
"unit": "item",
"price": 20,
"images": [
"http://productimages.com/1/001.jpg",
"http://productimages.com/1/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e3",
"productId": 1,
"stock": 20,
"unit": "item",
"price": 30,
"images": [
"http://productimages.com/1/003.jpg",
"http://productimages.com/1/004.jpg"
]
}
]
}
]
however I would like to get
[
{
"_id": 2,
"price": 12,
"stock": 10,
"unit": "item",
"images": [
"http://productimages.com/2/001.jpg",
"http://productimages.com/2/002.jpg",
"http://productimages.com/2/003.jpg",
"http://productimages.com/2/004.jpg"
],
"variants": [
{
"_id": "5f0307520ac9361c0d7088e4",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 15,
"images": [
"http://productimages.com/2/001.jpg",
"http://productimages.com/2/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e5",
"productId": 2,
"stock": 5,
"unit": "item",
"price": 12,
"images": [
"http://productimages.com/2/003.jpg",
"http://productimages.com/2/004.jpg"
]
}
]
},
{
"_id": 1,
"price": 20,
"stock": 30,
"unit": "item",
"images": [
"http://productimages.com/1/001.jpg",
"http://productimages.com/1/002.jpg",
"http://productimages.com/1/003.jpg",
"http://productimages.com/1/004.jpg"
],
"variants": [
{
"_id": "5f0307520ac9361c0d7088e2",
"productId": 1,
"stock": 10,
"unit": "item",
"price": 20,
"images": [
"http://productimages.com/1/001.jpg",
"http://productimages.com/1/002.jpg"
]
},
{
"_id": "5f0307520ac9361c0d7088e3",
"productId": 1,
"stock": 20,
"unit": "item",
"price": 30,
"images": [
"http://productimages.com/1/003.jpg",
"http://productimages.com/1/004.jpg"
]
}
]
}
]
instead. I have tried replacing the images expression with images.* but this produced an empty set.
I have also tried adding a $reduce projection to the pipeline as shown at combine array fields into a single array field mongo:
db.variants.aggregate([
{
"$group": {
"_id": "$productId",
"price": { "$min": "$price" },
"stock": { "$sum": "$stock" },
"unit": { "$first": "$unit" },
"images": { "$push": "$images" },
"variants": { "$push": "$$ROOT" }
}
},
{
"$project": {
"images": {
"$reduce": {
"input": { "$concatArrays": ["images.*"] },
"initialValue": [],
"in": { "$setUnion": ["$$this", "$$value"] }
}
}
}
}
]).pretty()
which fails with:
{
"ok" : 0,
"errmsg" : "Failed to optimize pipeline :: caused by :: $concatArrays only supports arrays, not string",
"code" : 28664,
"codeName" : "Location28664"
}
You can use $concatArrays together with $reduce.
Instead of $project you can use $addFields, available from MongoDB v3.4 or $set available from v4.2 to keep other fields
db.variants.aggregate([
{
"$group": {
"_id": "$productId",
"price": {
"$min": "$price"
},
"stock": {
"$sum": "$stock"
},
"unit": {
"$first": "$unit"
},
"images": {
"$push": "$images"
},
"variants": {
"$push": "$$ROOT"
}
}
},
{
"$addFields": { // or $set
"images": {
"$reduce": {
"input": "$images",
"initialValue": [],
"in": {
"$concatArrays": ["$$value", "$$this"]
}
}
}
}
}
])
Related
I am struggling with writing an aggregation pipeline to lookup nested documents by their _id and return a specific name without overwriting the existing keys/values in the data. I have managed to do this for the nested array, but am unable to do it for an array that is nested within the nested array.
So I want to lookup the _id of each ingredient and each subIngredient and merge them with the data for these ingredients that already exists (i.e. qty, measure).
Here is what I have so far:
https://mongoplayground.net/p/ft4oIMm_8wg
Products Collection:
[
{
"_id": {
"$oid": "5ecf269bceb735416db0b329"
},
"id": 36,
"title": "Product 1",
"description": {
"generalInformation": "Some information",
"activeIngredients": [
{
"_id": 1636,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1626,
"qty": 16.6,
"measure": "µg"
}
],
},
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
},
{
"_id": 1212,
"qty": 16.6,
"measure": "µg"
}
],
},
]
},
},
{
"_id": {
"$oid": "5ecf269bceb735416db0b346"
},
"id": 36,
"title": "Product 2",
"description": {
"generalInformation": "Some information",
"activeIngredients": [
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
}
],
},
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
},
{
"_id": 1212,
"qty": 16.6,
"measure": "µg"
}
],
},
]
},
}
]
Ingredients Collection:
[
{
"_id": 1234,
"name": "Ingredient 1",
},
{
"_id": 1122,
"name": "Ingredient 2",
},
{
"_id": 1212,
"name": "Ingredient 3",
},
{
"_id": 1636,
"name": "Ingredient 4",
},
{
"_id": 1626,
"name": "Ingredient 5",
}
]
What should be returned:
[
{
"_id": ObjectId("5ecf269bceb735416db0b329"),
"title": "Product 1"
"description": {
"activeIngredients": {
"_id": 1636,
"measure": "µg",
"name": "Ingredient 4",
"qty": 133.5,
"subIngredient": [
{
"_id": 1626,
"measure": "µg",
"qty": 16.6
}
]
},
"generalInformation": "Some information"
},
"ingredients": [
{
"_id": 1636,
"measure": "µg",
"name": "Ingredient 4",
"qty": 133.5,
"subIngredient": [
{
"_id": 1626,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
}
]
},
{
"_id": 1234,
"measure": "µg",
"name": "Ingredient 1",
"qty": 133.5,
"subIngredient": [
{
"_id": 1122,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
},
{
"_id": 1212,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
}
]
}
]
},
]
My current pipeline:
[
{
"$unwind": {
"path": "$description.activeIngredients",
"preserveNullAndEmptyArrays": false
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "description.activeIngredients._id",
"foreignField": "_id",
"as": "description.activeIngredients.name"
}
},
{
"$addFields": {
"description.activeIngredients.name": {
"$arrayElemAt": [
"$description.activeIngredients.name.name",
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"ingredients": {
"$push": "$description.activeIngredients"
},
"description": {
"$first": "$description"
},
"title": {
"$first": "$title"
}
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "ingredients.subIngredient._id",
"foreignField": "_id",
"as": "subIngredients"
}
}
]
Appreciate any help. Thanks.
You're not far off and you can achieve this result in multiple different ways, one of which is to just $unwind the subingredients array, $lookup on that and finally adding another $group stage to restructure the document.
As you've clearly shown you know how to do all these things i'll show a different way that utilizes operators like $map, $indexOfArray and Mongo's v3.6 $lookup syntax.
The strategy is instead of unwinding the subarray we just $lookup all the relevant sub-ingredients and then "merge" the two arrays using the operators i specified.
i.e taking:
[ {id: 5, name: "name"} ];
[ {id: 5, qty: 25} ]
And making them into:
[ {id: 5, name: "name", qty: 25} ]
Here's how we do it:
db.products.aggregate([
{
"$unwind": {
"path": "$description.activeIngredients",
"preserveNullAndEmptyArrays": false
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "description.activeIngredients._id",
"foreignField": "_id",
"as": "description.activeIngredients.name"
}
},
{
"$addFields": {
"description.activeIngredients.name": {
"$arrayElemAt": [
"$description.activeIngredients.name.name",
0
]
}
}
},
{
"$lookup": {
"from": "ingredients",
"let": {
sub: "$description.activeIngredients.subIngredient"
},
"pipeline": [
{
$match: {
$expr: {
"$setIsSubset": [
[
"$_id"
],
{
$map: {
input: "$$sub",
as: "datum",
in: "$$datum._id"
}
}
]
}
}
}
],
"as": "subIngredients"
}
},
{
"$addFields": {
"description.activeIngredients.subIngredient": {
$map: {
input: "$description.activeIngredients.subIngredient",
as: "sub",
in: {
"$mergeObjects": [
"$$sub",
{
name: {
$arrayElemAt: [
"$subIngredients.name",
{
"$indexOfArray": [
"$subIngredients._id",
"$$sub._id"
]
}
]
}
}
]
}
}
}
}
},
{
"$group": {
"_id": "$_id",
"ingredients": {
"$push": "$description.activeIngredients"
},
"description": {
"$first": "$description"
},
"title": {
"$first": "$title"
}
}
}
])
MongoPlayground
I have two collections, products and orders.
products collection look like this.
[
{
"_id": "5efb56741c32133bf43ea9aa",
"title": "Xyz",
"image": "172e4eb73415b3cc8540e651.jpg",
"quantity": "1 Ltr",
"price": 1500,
"status": true,
"creationDate": "2020-06-30T15:12:52.570Z",
"__v": 0
},
{
"_id": "5f0079bd27a734424cb3069a",
"title": "abc",
"image": "122e4eb73413b3cc854n42n1.jpg",
"quantity": "500 ml",
"price": 700,
"status": true,
"creationDate": "2020-06-30T15:12:52.570Z",
"__v": 0
}
]
orders collection look like this.
[
{
"_id": "5efca937def27b74fc9f6aa6",
"products": [
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca937def27b74fc9f6aa8",
"productId": "5efb56741c32133bf43ea9aa",
"productQuantity": 2
},
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca937def27b74fc9f6aa7",
"productId": "5f0079bd27a734424cb3069a",
"productQuantity": 1
}
],
"totalQuantity": 3,
"totalPrice": 3700,
"creationDate": "2020-07-01T15:18:15.756Z",
"__v": 0
},
{
"_id": "5efca897def27b74fc9f6aa2",
"products": [
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca897def27b74fc9f6aa3",
"productId": "5f0079bd27a734424cb3069a",
"productQuantity": 1
}
],
"totalQuantity": 1,
"totalPrice": 700,
"creationDate": "2020-07-01T15:15:35.422Z",
"__v": 0
}
]
using this two collections how can I get result like this:
[
{
"_id": "5efca937def27b74fc9f6aa6",
"products": [
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca937def27b74fc9f6aa8",
"productId": "5efb56741c32133bf43ea9aa",
"productQuantity": 2,
"title": "Xyz",
"image": "172e4eb73415b3cc8540e651.jpg",
"quantity": "1 Ltr",
"price": 1500
},
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca937def27b74fc9f6aa7",
"productId": "5f0079bd27a734424cb3069a",
"productQuantity": 1,
"title": "abc",
"image": "122e4eb73413b3cc854n42n1.jpg",
"quantity": "500 ml",
"price": 700
}
],
"totalQuantity": 3,
"totalPrice": 3700,
"creationDate": "2020-07-01T15:18:15.756Z",
"__v": 0
},
{
"_id": "5efca897def27b74fc9f6aa2",
"products": [
{
"Date": "2020-07-01T15:14:36.630Z",
"_id": "5efca897def27b74fc9f6aa3",
"productId": "5f0079bd27a734424cb3069a",
"productQuantity": 1,
"title": "abc",
"image": "122e4eb73413b3cc854n42n1.jpg",
"quantity": "500 ml",
"price": 700
}
],
"totalQuantity": 1,
"totalPrice": 700,
"creationDate": "2020-07-01T15:15:35.422Z",
"__v": 0
}
]
I got the result using this query.
order.aggregate([
{ "$unwind": "$products" },
{
$lookup: {
from: 'products',
localField: "products.productId",
foreignField: "_id",
as: "product"
}
},
{
"$addFields": {
"products": { $mergeObjects: [{ $arrayElemAt: ["$product", 0] }, "$$ROOT.products"] }
}
},
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"totalQuantity": { $first: '$totalQuantity' },
"totalPrice": { $first: '$totalPrice' },
"creationDate": { $first: '$creationDate' }
}
}
])
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
Example JSON:
{
"groups": [
{
"_id": 1,
"name": "g1"
},
{
"_id": 2,
"name": "g2"
}
],
"items": [
{
"_id": 1,
"name": "item1",
"gid": 1
},
{
"_id": 2,
"name": "item2",
"gid": 2
}
]
}
How to associate two arrays and count ?I tried to use aggregate, I didn't get the results I wanted.
Required Result:
Or can directly find all the items associated with it, perfect....
{"groups": [
{
"_id": 1,
"name": "g1",
"count": 1,
"items": [
{
"_id": 1,
"name": "item1"
}
]
},
{
"_id": 2,
"name": "g2",
"count": 1,
"items": [
{
"_id": 2,
"name": "item2"
}
]
}
]}
db.getCollection('collection').aggregate([
{$unwind:{
path:"$groups",
preserveNullAndEmptyArrays:true
}},
{$unwind:{
path:"$items",
preserveNullAndEmptyArrays:true
}},
{$redact: {$cond: [{
$eq: [
"$groups._id",
"$items.gid"
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{$project:{
_id:1,
groups_id:"$groups._id",
group_name:"$groups.name",
item_data:{
_id:"$items._id",
name:"$items.name",
}
}},
{
$group:{
_id:"$groups_id",
name:{$first:"$group_name"},
count:{$sum:1},
items:{$push:"$item_data"}
}
}
])
How to populate in result of aggregated query in monogdb
Array of followedId
var followeduserId = ["abc","efg","xyz","pqr","acd","rts"];
Feeds Recommended
[
{
"feedsId": "feed1",
"userId": "abc"
},
{
"feedsId": "feed1",
"userId": "efg"
}
]
Feeds collection
[
{
"link": "www.yodo.com",
"recommended": [
"abc",
"efg"
],
"title": "This is my feed7",
"topics": [
"topi1",
"topi2",
"topi3",
"topi4"
]
},
{
"link": "www.yodo.com",
"recommended": [
"abc",
"efg",
"das",
"asd",
"eqw",
"weq"
],
"title": "This is my feed8",
"topics": [
"topi1",
"topi2",
"topi3",
"topi4"
]
}
]
Ran aggregation query
feedsrecommended.aggregate([
{ $match: { userId: { $in: "followersId" }}},
{ $lookup: {
from: "feeds",
localField: "feedsId",
foreignField: "_id",
as: "feedsId"
}},
{ $group: {
"_id": { "feedsId": "$feedsId" },
"count": { "$sum": 1 }
}},
{ $sort: { count: -1 }}
])
result After aggregation
var resultfeeds = [
{
"count": 7,
"id": {
"_id": "feed1",
"link": "www.yodo.com",
"recommended": [
"abc",
"efg",
"xyz",
"pqr",
"acd",
"rts"
],
"title": "This is my feed1",
"topics": [
"topi1",
"topi8",
"topi6",
"topi5"
]
}
},
{
"count": 3,
"id": {
"_id": "feed5",
"link": "www.yodo.com",
"recommended": [
"abc",
"efg",
"acd",
"rts"
],
"title": "This is my feed1",
"topics": [
"topi1",
"topi2",
"topi3",
"topi4"
]
}
},
{
"count": 3,
"id": {
"_id": "feed6",
"link": "www.yodo.com",
"recommended": [
"abc",
"efg",
"xyz",
"pqr"
],
"title": "This is my feed1",
"topics": [
"topi7",
"topi1",
"topi4",
"topi8"
]
}
},
{
"count": 2,
"id": {
"_id": "feed2",
"link": "www.yodo.com",
"recommended": [
"abc",
"acd",
"rts"
],
"title": "This is my feed1",
"topics": [
"topi7",
"topi6",
"topi8"
]
}
},
{
"count": 2,
"id": {
"_id": "feed7",
"link": "www.yodo.com",
"recommended": [
"abc",
"efg"
],
"title": "This is my feed1",
"topics": [
"topi1",
"topi5",
"topi6",
"topi4"
]
}
},
{
"count": 1,
"id": {
"_id": "feed3",
"link": "www.yodo.com",
"recommended": [
"abc",
"asd",
"eqw",
"weq"
],
"title": "This is my feed1",
"topics": [
"topi1",
"topi7",
"topi6",
"topi4"
]
}
},
{
"count": 1,
"id": {
"_id": "feed8",
"link": "www.yodo.com",
"recommended": [
"abc",
"das",
"asd",
"eqw",
"weq"
],
"title": "This is my feed1",
"topics": [
"topi1",
"topi2",
"topi5",
"topi4"
]
}
}
]
I want to populate topics and recommeded userName and image in the result
topic collection
[
{
"topic_name": "tiger"
},
{
"topic_name": "loin"
}
]
user collection
[
{
"name": "deepa",
"profileImg": "www.com/facebook.jpg"
},
{
"name": "nisa",
"profileImg": "www.com/facebook.jpg"
}
]
My last result should be like this
[
{
"count": 2,
"id": {
"_id": "feed2",
"link": "www.yodo.com",
"recommended": [
{
"_id": "abc",
"name": "deepa",
"profileImg": "www.com/facebook.jpg"
},
{
"_id": "acd",
"name": "sigger",
"profileImg": "www.com/facebook.jpg"
},
{
"_id": "rts",
"name": "buster",
"profileImg": "www.com/facebook.jpg"
}
],
"title": "This is my feed1",
"topics": [
{
"_id": "topi6",
"topic_name": "boolena"
},
{
"_id": "topi7",
"topic_name": "mika"
},
{
"_id": "topi8",
"topic_name": "tika"
}
]
}
}
]
You can try below aggregation in mongodb 3.6 and above
Feedsrecommended.aggregate([
{ "$match": { "userId":{ "$in": followersId }}},
{ "$group": {
"_id": "$feedsId",
"count": { "$sum": 1 }
}},
{ "$lookup": {
"from": "feeds",
"let": { "feedsId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$feedsId" ] }}},
{ "$lookup": {
"from": "topics",
"let": { "topics": "$topics" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$topics" ] } } }
],
"as": "topics"
}},
{ "$lookup": {
"from": "users",
"let": { "recommended": "$recommended" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$recommended" ] } } }
],
"as": "recommended"
}}
],
"as": "feedsId"
}}
])