Now consider the case , i have one document containing below collection like structure.
Below is the order collection
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c815"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
There is also a collection for Brand and Category. This is the
Category collection
{
"_id" : ObjectId("5693d170a2191f9020b8c815"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832+0000"),
"updated_at" : ISODate("2016-01-11T20:32:17.832+0000"),
}
Brand Collection
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951+0000"),
"created_at" : ISODate("2017-07-05T09:18:13.951+0000"),
}
Now after aggregation applied, it should result in below format:
{
'brands': [
{
_id: '*******'
name: 'brand1',
categories: [
{
_id: '*****',
name: 'category_name1',
orders: [
{
_id: '*****',
title: 'order1'
}
]
}
]
}
]
}
You can try below aggregation:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
$mergeObjects: [
"$$category", {
orders: [ {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
} ]
} ]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])
Basically you have to use $lookup twice to "merge" data from all these collections based on brand_id and category_id fields. Since you expect orders in categories in brands you can use $unwind for both arrays and then use $group to get following shape:
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951Z"),
"created_at" : ISODate("2017-07-05T09:18:13.951Z"),
"categories" : [
{
"_id" : ObjectId("5693d170a2191f9020b8c814"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832Z"),
"updated_at" : ISODate("2016-01-11T20:32:17.832Z")
}
],
"orders" : [
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c814"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
]
}
Now you have brand1 with all its subcategories and all orders that should be placed in one of those categories. The only thing is how to "nest" orders in categories. One way to do that might be $map where you can merge each category with all orders that match that category (using $mergeObjects you don't have to specify all properties from categories object).
To match category with orders you can perform $filter on orders array.
Then you can drop orders since those are nested into categories so you don't need them anymore.
EDIT: 3.4 version
In MongoDB 3.4 you can't use $mergeObjects so you should specify all properties for `categories:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
_id: "$$category._id",
name: "$$category.name",
created_at: "$$category.created_at",
updated_at: "$$category.updated_at",
orders: [
{
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
}
]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])
Related
I have 3 mongoDB collections
I need to aggregate them with $lookup operator but I didn't find anything/**or I'm bad looking **
1st one is suppliers
{
"_id" : ObjectId("111"), //for example, in db is mongodb ids
"name" : "supplier 1",
}
{
"_id" : ObjectId("222"),
"name" : "supplier 1",
}
2nd one is clients
{
"_id" : ObjectId("333"), //for example, in db is mongodb ids
"name" : "clients 1",
}
{
"_id" : ObjectId("444"),
"name" : "clients 2",
}
and 3rd is moves
{
"_id" : ObjectId("..."), //for example, in db is mongodb ids
"moveName" : "move 1",
"agent": ObjectId("111") // this is from suppliers collection
}
{
"_id" : ObjectId("..."),
"moveName" : "move 2",
"agent": ObjectId("333") // this one is from CLIENTS collection
}
so like output I need data like this
{
"_id" : ObjectId("..."), //for example, in db is mongodb ids
"moveName" : "move 1",
**"agent": supplier 1** // this is from suppliers collection
}
{
"_id" : ObjectId("..."),
"moveName" : "move 2",
**"agent": clients 1** // this one is from CLIENTS collection
}
back end is nodejs, I`m using mongoose, how I can search in 2nd collection if noresult in 1st?
const moves = await Move.aggregate([
{ $match: query }, // here all wokrs good
{
$lookup: {
from: 'clients',
localField: 'agent',
foreignField: '_id',
as: 'agent'
}
},{ $unwind: {path: "$agent" , preserveNullAndEmptyArrays: true} },
{
$lookup: {
from: 'suppliers',
localField: 'agent',
foreignField: '_id',
as: 'agent2'
}
},
{
$project: {
operationName: 1,
agent: {$ifNull: ['$agent.name', '$agent2.name']}
}
}
])
Thank You!
As suggested by #hhharsha36, we can use $facet operator which allows to run several pipelines within a single stage.
Explanation
facet
suppliers = $lookup suppliers collection and filter only matched results
clientes = $lookup clientes collection and filter only matched results
concatArrays = We concat suppliers and clients results into a single movies array
unwind = We flatten movies array [a, b, c] -> a
b
c
replaceWith = We replace the root element [movies:a, movies:b -> a, b]
mergeObject = allows us to pick the agent name (this way we avoid 1 more stage)
db.moves.aggregate([
{
$facet: {
suppliers: [
{
$lookup: {
from: "suppliers",
localField: "agent",
foreignField: "_id",
as: "agent"
}
},
{
$match: {
agent: {
$not: {
$size: 0
}
}
}
}
],
clients: [
{
$lookup: {
from: "clients",
localField: "agent",
foreignField: "_id",
as: "agent"
}
},
{
$match: {
agent: {
$not: {
$size: 0
}
}
}
}
]
}
},
{
$project: {
movies: {
"$concatArrays": [
"$clients",
"$suppliers"
]
}
}
},
{
$unwind: "$movies"
},
{
$replaceWith: {
"$mergeObjects": [
"$movies",
{
agent: {
"$arrayElemAt": [
"$movies.agent.name",
0
]
}
}
]
}
}
])
MongoPlayground
This aggregation query gives the desired result:
db.moves.aggregate([
{
$lookup: {
from: "suppliers",
localField: "agent",
foreignField: "_id",
as: "moves_sup"
}
},
{
$unwind: { path: "$moves_sup" , preserveNullAndEmptyArrays: true }
},
{
$lookup: {
from: "clients",
localField: "agent",
foreignField: "_id",
as: "moves_client"
}
},
{
$unwind: { path: "$moves_client" , preserveNullAndEmptyArrays: true }
},
{
$addFields: {
agent: {
$cond: [ { $eq: [ { $type: "$moves_sup" }, "object" ] },
"$moves_sup.name",
{ $cond: [ { $eq: [ { $type: "$moves_client" }, "object" ] }, "$moves_client.name", "undefined" ] }
] },
moves_client: "$$REMOVE",
moves_sup: "$$REMOVE"
}
},
])
I want to make the groups from this using mongodb aggregate. I want to immplement this on my project but stuck in this. not finding a better way to do this.
{
"_id" : ObjectId("5e9a21868ed974259c0da402"),
"shopId" : "5e975cc7be7c1b546b7abb17",
"shopType" : "Medium Store",
"products": [{
"isPackedProduct" : true,
"_id" : ObjectId("5e92ff706af877294d63098e"),
"brand" : "ABC",
"category" : "CAT1",
"productName" : "P1",
"subCategory" : "SUB1",
},
{
"isPackedProduct" : true,
"_id" : ObjectId("5e92ff706af877294d63098f"),
"brand" : "EFG",
"category" : "CAT1",
"productName" : "P2",
"subCategory" : "SUB2",
},
{
"isPackedProduct" : true,
"_id" : ObjectId("5e92ff706af84d630977298f"),
"brand" : "EFG",
"category" : "CAT2",
"productName" : "P3",
"subCategory" : "SUB1",
}
....
]
}
From this set of json i want to show the data as:
{
"_id" : ObjectId("5e9a21868ed974259c0da402"),
"shopId" : "5e975cc7be7c1b546b7abb17",
"CAT1":{
"SUB1":{
"products": [{
...ALL the Products which have CAT1 and SUB1
}]
}
},
"CAT2":{
"SUB1":{
"products": [{
...ALL the Products which have CAT2 and SUB1
}]
}
}
...
}
i tried so far but not getting close to solution:
db.shopproducts.aggregate([{$unwind: {path: '$products'}}, {$group: {_id: 'products.category'}}, {$project: {'products.category': 1, 'products.productName': 1}}])
Also, if there is a better way to do this without using aggregate then suggestions are welcome.
Thanks in advance.
We need to apply several $group stages. To transform products.category and products.subCategory into object field, we need to use $arrayToObject operator.
[ {
{ "k" : "CAT1", "v" : "SUB1" }, ----\ "CAT1" : "SUB1",
{ "k" : "CAT2", "v" : "SUB1" } ----/ "CAT2" : "SUB1"
] }
Try this one:
db.shopproducts.aggregate([
{
$unwind: "$products"
},
{
$group: {
_id: {
_id: "$_id",
shopId: "$shopId",
category: "$products.category",
subCategory: "$products.subCategory"
},
products: {$push: "$products"}
}
},
{
$group: {
_id: {
_id: "$_id._id",
shopId: "$_id.shopId",
category: "$_id.category"
},
products: {
$push: {
k: "$_id.subCategory",
v: {products: "$products"}
}
}
}
},
{
$group: {
_id: {
_id: "$_id._id",
shopId: "$_id.shopId"
},
products: {
$push: {
k: "$_id.category",
v: {$arrayToObject: "$products"}
}
}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
{
_id: "$_id._id",
shopId: "$_id.shopId"
},
{
$arrayToObject: "$products"
}
]
}
}
}
])
MongoPlayground
db.shopproducts.aggregate([
{
$unwind: {
path: "$products"
}
},
{
$group: {
_id: {
cat: "$products.category",
sub_cat: "$products.subCategory"
},
products: {
$addToSet: "$products"
}
}
}
])
I guess this is what you want, isn't it?
Mongoplayground
This is result array and I want to "$match" stepQuoteTool = true
{
"quotes" : [
{
"_id" : ObjectId("5e0f02dc5023ec1de34e45bf"),
"firstName" : "Sagar",
"stepQuoteTool" : true,
"stepCarDetail" : true,
}
],
"_id" : ObjectId("5e0f02dc5023ec1de34e45be"),
"firstName" : "Sagar",
"createdAt" : ISODate("2020-01-03T09:01:16.748+0000"),
"device" : "Desktop",
"browser" : "Chrome",
"browserVersion" : "79.0.3945.88",
"os" : "Linux",
"screenSize" : "1920 X 383",
}
and following is my mongodb query. Can any one help with this? I'm beginner In mongodb
db.getCollection("tracks").aggregate([
{ $match: {'stepQuoteTool':true} },
{
$lookup: {
from: 'quotes',
foreignField: 'track',
localField: '_id',
as: 'quotes'
}
},
{
$unwind: {
path: '$quotes',
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: { _id: "$_id" },
firstName: { "$addToSet": "$firstName" },
quotes: { "$addToSet": "$quotes" },
createdAt: { "$addToSet": "$createdAt" },
device: { "$addToSet": "$device" },
browser: { "$addToSet": "$browser" },
browserVersion: { "$addToSet": "$browserVersion" },
os: { "$addToSet": "$os" },
screenSize: { "$addToSet": "$screenSize" },
}
}
])
Please give me possible solution,
Thank you.
db.getCollection("tracks").aggregate([
{
$lookup: {
from: 'quotes',
foreignField: 'track',
localField: '_id',
as: 'quotes'
}
}, {
$project: {
quotes: {
$filter: {
input: "$quotes",
as: "data",
cond: {
$eq: ["$$data.stepQuoteTool", true]
}
}
}
}
}
])
Trying to figure out something from Mongo using mongoose in optimal way.
I have following documents
Regions
{
"_id" : ObjectId("5cf21263ff605c49cd6d8016"),
"name" : "Asia"
}
Countries can be part of multiple regions
{
"_id" : ObjectId("5d10a4ad80a93a1d7cd56cc6"),
"regions" : [
ObjectId("5d10a50080a93a1d7cd56cc7"),
ObjectId("5cf2126bff605c49cd6d8017")
],
"name" : "India"
}
Places belongs to one country
{
"_id" : ObjectId("5d11bb8180a93a1d7cd56d26"),
"name" : "Delhi",
"country" : ObjectId("5d136e7a4e480863a51c4056"),
}
Programs each in dayshows array represents one day. On a day show can cover multiple places.
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d31"),
"dayshows" : [
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d41"),
"places" : [
ObjectId("5d11bb8180a93a1d7cd56d26")
],
},
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d3c"),
"places" : [
ObjectId("5d11bb8180a93a1d7cd56d26"),
ObjectId("5d11bc7c80a93a1d7cd56d2e")
]
}
]
}
What am I trying to figure out?
For a given region, for each country in region which all places are covered and count of programs for each place. Using nodejs and mongoose.
Example
Input - Asia
Output
India
- Delhi (3)
- Mumbai (5)
Thailand
- Pattaya (2)
- Bangkok (5)
New to mongo.
You need to use $lookup to cross different collections.
Pipeline:
Stages 1-6 serves to get all related data.
(Optional) Stages 7-10 serves to transform aggregated data into key:pair object.
ASSUMPTION
Programs to visit 2 places counted as is (Place1: +1, Place2: +1)
You know how to execute MongoDB aggregation in node.js
db.Regions.aggregate([
{
$match: {
name: "Asia"
}
},
{
$lookup: {
from: "Countries",
let: {
region: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$$region",
"$regions"
]
}
}
},
{
$lookup: {
from: "Places",
localField: "_id",
foreignField: "country",
as: "Places"
}
}
],
as: "Countries"
}
},
{
$unwind: "$Countries"
},
{
$unwind: "$Countries.Places"
},
{
$lookup: {
from: "Programs",
localField: "Countries.Places._id",
foreignField: "dayshows.places",
as: "Countries.Places.Programs"
}
},
{
$project: {
"name": 1,
"Countries.name": 1,
"Countries.Places.name": 1,
"Countries.Places.Programs": {
$size: "$Countries.Places.Programs"
}
}
},
{
$group: {
_id: {
name: "$name",
Countries: "$Countries.name"
},
Places: {
$push: {
k: "$Countries.Places.name",
v: "$Countries.Places.Programs"
}
}
}
},
{
$project: {
_id: 1,
Places: {
$arrayToObject: "$Places"
}
}
},
{
$group: {
_id: "$_id.name",
Countries: {
$push: {
k: "$_id.Countries",
v: "$Places"
}
}
}
},
{
$project: {
_id: 0,
name: "$_id",
Countries: {
$arrayToObject: "$Countries"
}
}
}
])
MongoPlayground
I have two collections (promotions,product) and product collection map to promotions its working fine.but I have doubt how to show particular columns in product collection.
promotional collection
{
"_id" : ObjectId("5cf7679a0b0bed2e7483b998"),
"group_name" : "Latest",
"products" :
[ObjectId("5cecc161e8c1e73478956333"),ObjectId("5cecc161e8c1e73478956334")]
}
product collection
{
"_id" : ObjectId("5cecc161e8c1e73478956333"),
"product_name" : "bourbon"
},
{
"_id" : ObjectId("5cecc161e8c1e73478956334"),
"product_name" : "bour"
}
mapping query
db.promotional.aggregate(
[
{
$lookup: {
from: "product",
localField: "products",
foreignField: "_id",
as: "products"
}
}
]
)
I tried to map product collection to promotional collection
I got Output
{
"_id" : ObjectId("5cf7679a0b0bed2e7483b998"),
"group_name" : "Latest",
"products" :
[
{
"_id" : ObjectId("5cecc161e8c1e73478956333"),
"product_name" : "bourbon"
},
{
"_id" : ObjectId("5cecc161e8c1e73478956334"),
"product_name" : "bour"
}
]
}
Expected output
{
"_id" : ObjectId("5cf7679a0b0bed2e7483b998"),
"group_name" : "Latest",
"products" :
[
{
"product_name" : "bourbon"
},
{
"product_name" : "bour"
}
]
}
You can exclude those columns using $project operator:
db.promotional.aggregate(
[
{
$lookup: {
from: "product",
localField: "products",
foreignField: "_id",
as: "products"
}
},
{
$project: {
"products._id": 0
}
}
]
)
db.promotional.aggregate([
{
$lookup: {
from: "product",
localField: "products",
foreignField: "_id",
as: "products"
}
},{$project :{products :{product_name : 1}}}
])
db.getCollection("promotional").aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$products"
}
},
// Stage 2
{
$lookup: {
from: "product",
localField: "products",
foreignField: "_id",
as: "products"
}
},
// Stage 3
{
$group: {
_id: {
_id: '$_id',
group_name: '$group_name'
},
products: {
$push: {
product_name: {
$arrayElemAt: ["$products.product_name", 0]
}
}
}
}
},
// Stage 4
{
$project: {
_id: '$_id._id',
group_name: '$_id.group_name',
products: 1
}
},
]
);