Joining mongodb collection based on condition [duplicate] - node.js

This question already has an answer here:
Conditional $lookup in MongoDB?
(1 answer)
Closed 3 years ago.
I have two collections in MongoDB and want to join the two collections based on some condition.
I want to join 'order' and 'order-status' table to get all orders assigned to '123' with status 'ready'
orders
{
"_id":"1",
"name": "Fridge",
"assignee": "123"
},
{
"_id":"2",
"name": "TV",
"assignee": "567"
},
{
"_id":"3",
"name": "Music system",
"assignee": "123"
}
order-status
{
"_id":"1",
"status": "ready",
"orderId": "1"
},
{
"_id":"2",
"status": "cancelled",
"orderId": "2"
},
{
"_id":"3",
"status": "cancelled",
"orderId": "3"
}
assignee
{
"_id":"123",
"name": "Jak"
}
{
"_id":"567",
"name": "Mac"
}
I want to join 'order' and 'order-status' table to get all orders assigned to '123' with status 'ready'
Expecting a final result as
[
{
"_id":"1",
"name": "Fridge",
"assignee": "123",
"status": {
"_id":"1",
"status": "ready",
"orderId": "1"
}
}
]
Tried following but how to check order status in another table with lookup
const resultObject = orders.aggregate([
{ $match : {assignee: Objectid('123')} },
{
$lookup: {
from: 'user-status',
localField: 'assignee',
foreignField : '_id',
as : 'assignee'
}
},
{
$unwind: '$assignee'
}
]);

First you need to use match to filter by "assignee": "123", then you need to lookup order-status, match "orderStatus.status": "ready".
const resultObject = orders.aggregate([
{
$match: {
assignee: "123"
}
},
{
$lookup: {
from: "order-status",
localField: "_id",
foreignField: "orderId",
as: "statuses"
}
},
{
$match: {
"statuses.status": "ready"
}
},
{
$project: {
id: "_id",
name: "$name",
assignee: "$assignee",
status: {
$arrayElemAt: ["$statuses", 0]
}
}
}
]);
This will give result like this:
[
{
"_id": "1",
"assignee": "123",
"name": "Fridge",
"status": {
"_id": "1",
"orderId": "1",
"status": "ready"
}
}
]
Playground

I would use the following pipeline:
const resultObject = orders.aggregate([
{
$match: {
assignee: Objectid('123')
}
},
{
$lookup:
{
from: "order-status",
let: {order_id: "$_id"},
pipeline: [
{
$match:
{
$expr:
{
$and:
[
{$eq: ["$orderId", "$$order_id"]},
{$eq: ["$status", "ready"]}
]
}
}
}
],
as: "stock"
}
},
{
$unwind: "$stock"
},
// now we get the assignee info.
{
$lookup: {
from: 'user-status',
localField: 'assignee',
foreignField: '_id',
as: 'assignee'
}
},
{
$unwind: '$assignee'
},
//finaly create the required structure.
{
$project: {
name: "$assignee.name",
assignee: "$assignee._id",
status: "$stock.0"
}
}
]);

Related

MongoDB nested $lookup for 3 levels

MongoDB: How to put all the subcategory in array like child_category? you can see the response, the way its coming. how to merge that clothes category of all sub_category in array
Below is the aggregate lookup:
$Lookup
{
$match: { _id: ObjectId(req.params.id) }
},
{
$lookup: {
from: 'sub_category',
localField: '_id',
foreignField: 'category',
as: 'sub_category',
},
},
{
$unwind: {
path: '$sub_category',
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'child_category',
localField: 'sub_category._id',
foreignField: 'sub_category',
as: 'sub_category.child_category',
},
}
Below is the response I get when run this lookup
response
[
{
"_id": "6219f96e2504161acb0f82a4",
"name": "clothes",
"__v": 0,
"sub_category": {
"_id": "6219fcccb2ba385797e02439",
"name": "Shirt",
"category": "6219f96e2504161acb0f82a4",
"__v": 0,
"child_category": [
{
"_id": "6219fdcd477bd10b4d4bbf12",
"name": "striped",
"sub_category": "6219fcccb2ba385797e02439",
"filters": [],
"__v": 0
},
{
"_id": "621a045be776b1775e9c9ec7",
"name": "checked",
"sub_category": "6219fcccb2ba385797e02439",
"filters": []
}
]
}
},
{
"_id": "6219f96e2504161acb0f82a4",
"name": "clothes",
"__v": 0,
"sub_category": {
"_id": "621a0431e776b1775e9c9ebe",
"name": "Pant",
"category": "6219f96e2504161acb0f82a4",
"child_category": []
}
}
]
I want both sub_category to come in array, so that name: clothes wont repeat in array
Add $group at the end of pipeline.
db.collection.aggregate([
{
$lookup: {
from: "sub_category",
localField: "_id",
foreignField: "category",
as: "sub_category"
}
},
{
$unwind: {
path: "$sub_category",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "child_category",
localField: "sub_category._id",
foreignField: "sub_category",
as: "sub_category.child_category"
}
},
{
$group: {
"_id": {
_id: "$_id",
name: "$name"
},
"sub_category": { "$push": "$sub_category" }
}
}
])
mongoplayground

mongoose aggregate lookup in project

I currently have 3 different collections.
ColA
{
id: 1,
some_other_data: "fff"
}
ColB
{
id: 1,
colA_id: 1,
spec_id: 5,
data: "test"
}
and
ColC
{
id: 5,
colA_id: 1,
name: "xxx"
}
My current code:
const list = await ColA.aggregate([
{
$lookup: {
from: "ColB",
localField: "_id",
foreignField: "ColA_id",
as: "col_b_list",
},
},
{
$lookup: {
from: "ColC",
localField: "col_b_list.spec_id",
foreignField: "_id",
as: "col_c_list",
},
},
{
$project: {
_id: 1,
list: "$col_b_list",
},
},
]);
return list;
I have following output.
[
{
"_id": "6123858b5b8dcc0e749c9e39",
"list": [
{
"_id": "6123858b5b8dcc0e749c9e43",
"spec_id": "60d33125f81840c010052e03",
"createdAt": "2021-08-23T11:24:59.292Z",
"updatedAt": "2021-08-23T11:24:59.292Z",
"__v": 0
},
{
"_id": "612386317dd1cb0ebcef1862",
"spec_id": "60d33125f81840c010052e03",
"createdAt": "2021-08-23T11:27:45.515Z",
"updatedAt": "2021-08-23T11:27:45.515Z",
"__v": 0
}
]
}
]
What I'm trying to achieve is to get spec_id in ColB, and the id from ColC together in one object.
But what I want is, that in the list object the looked up Collection ColC is included, like:
[
{
"_id": "6123858b5b8dcc0e749c9e39",
"some_other_data": "fff",
"list": [
{
"_id": "6123858b5b8dcc0e749c9e43",
"spec_id": "60d33125f81840c010052e03",
"name": "xxx",
"createdAt": "2021-08-23T11:24:59.292Z",
"updatedAt": "2021-08-23T11:24:59.292Z",
"__v": 0
},
{
"_id": "612386317dd1cb0ebcef1862",
"spec_id": "60d33125f81840c010052e03",
"name": "yyy",
"createdAt": "2021-08-23T11:27:45.515Z",
"updatedAt": "2021-08-23T11:27:45.515Z",
"__v": 0
}
]
}
]
I tried to map it in the project stage, but somehow there where multiple same objects, so I could not get it working properly.
Thanks for the help in advance!
You can use nested $lookup, using lookup with aggregation pipeline,
const list = await ColA.aggregate([
{
$lookup: {
from: "ColB",
let: { id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$$id", "$ColA_id"] }
}
},
{
$lookup: {
from: "ColC",
localField: "spec_id",
foreignField: "id",
as: "ColC"
}
},
{
$addFields: {
ColC: { $arrayElemAt: ["$ColC", 0] }
}
},
// { $project: {} }
],
as: "list"
}
}
])

How to make a nested relationship on mongodb?

For example i have 4 collections. There are:
"company" collection
{
"id_company": "C01"
"company_name": "Sidomuncul"
"like": [
"123",
"121"
]
}
"user" collection
{
"id_user": "123",
"name": "Astra",
"major": "111",
"language": [{
"id_language": "101",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "112",
"language": [{
"id_language": "102",
"level": "Intermediate"
}]
}
"major" collection
{
"id_major": "111",
"name": "IT"
},
{
"id_major": "112",
"name": "Designer"
}
"language" collection
{
"id_language": "101",
"name": "English"
},
{
"id_language": "102",
"name": "Chinese"
}
And when i make a route for get who are like company by id_company "C01", i want show the result relation id_user in "like" field with user collection. Example result:
{
"id_company": C01",
"like": [
{
"id_user": "123",
"name": "Astra",
"major": "IT",
"language": [{
"id_language": "English",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "Designer",
"language": [{
"id_language": "Chinese",
"level": "Intermediate"
}]
}
] //Close Like Field
}
Thanks before.
SOLVED
Use lookup and group also push
Using Mongo aggregate pipeline you can collect required result in multiple stages. For mongo aggregate query you can use $lookup, $set, $unwind and $group stages. Try this query, you might get required result set:
db.company.aggregate([
{
"$unwind": "$like"
},
{
$lookup: {
from: "user",
localField: "like",
foreignField: "id_user",
as: "like"
}
},
{
"$unwind": "$like"
},
{
$lookup: {
from: "major",
localField: "like.major",
foreignField: "id_major",
as: "major"
}
},
{
"$unwind": "$major"
},
{
$set: {
"like.major": "$major.name"
}
},
{
$lookup: {
from: "language",
localField: "like.language.id_language",
foreignField: "id_language",
as: "lang"
}
},
{
"$unwind": "$lang"
},
{
$set: {
"like.language.id_language": "$lang.name"
}
},
{
$group: {
_id: "id_company",
company_name: {
"$first": "$company_name"
},
like: {
$push: {
id_user: "$like.id_user",
major: "$like.major",
name: "$like.name",
language: "$like.language"
}
}
}
}
])

Mongodb, mongoose - Sorting by _id and date using aggregate and group

I am trying to sort by the task._id & date in desc order. I am able to sort by task._id but sortibg bydate doesnt work, I tried changing the order in aggregate still no luck. I get the response but just the order by usertasks were added in collection and not by the usertask.date
User(name, address, etc)
Task(name, icon, assignee)
UserTask(User.ObjectId, Task.ObjectId, date)
User Collection:
{
"users": [
{
"name": "Bill",
"phone": "345"
},
{
"name": "Steve",
"phone": "123"
},
{
"name": "Elon",
"phone": "567"
}
]
}
Task collection:
{
"tasks": [
{
"name": "Run 100m",
"icon": "run",
"assignee": "Elon"
},
{
"name": "Walk 1 hour",
"icon": "walk",
"assignee": "Bill"
},
{
"name": "Jog 30 minutes",
"icon": "jog",
"assignee": "Steve"
}
]
}
UserTasks:
{
"_id": "5e72fec..",
"user": "5e72fa4..",
"task": "5e72fbac..",
"date": "2020-03-03T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:37.027Z",
"updatedAt": "2020-03-19T05:10:37.027Z",
"__v": 0
},
{
"_id": "5e72fed3..",
"user": "5e72fa4e..",
"task": "5e72fbac..",
"date": "2020-03-12T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:43.296Z",
"updatedAt": "2020-03-19T05:10:43.296Z",
"__v": 0
},
{
"_id": "5e72fed6..",
"user": "5e72fa..",
"task": "5e72fb..",
"date": "2020-03-15T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:46.057Z",
"updatedAt": "2020-03-19T05:10:46.057Z",
"__v": 0
},
{
"_id": "5e72feda...",
"user": "5e72fa4..",
"task": "5e72fb..",
"date": "2020-03-07T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:50.785Z",
"updatedAt": "2020-03-19T05:10:50.785Z",
"__v": 0
}
This is the Aggregate that needs changing
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
date: "$date"
}
}
}
},
{
$sort: { _id: 1, "userdata.date": -1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));
The response is shown below, please note the usertask.date. it is NOT sorted
{
"_id": "5e...",
"name": "Run 100m",
"icon": "run",
"assignee": "Elon",
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
}
]
}
As you can see the it is not sorted by date - desc order. The result should be like shown below
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
}
]
$sort will use the last object which comes out from the aggregate pipe and theres no field "date" in this object:
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
your group has to returned a field named date in order to be able to sort it
{
$group: {
_id: "$matchedTask._id",
name: ....,
date: ....
}
}
{ $sort: {date: -1}}
if the value you want to sort is indide another object you must specify it on sort:
{$sort: {"userdata.date": -1}}
I fixed it, had to use sort two times, now I am able to get the result as I want
Solution provided below
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$sort: { date: -1 }
},
{
$group: {
_id: "$matchedTask._id",
name: { $first: "$matchedTask.name" },
icon: { $first: "$matchedTask.icon" },
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
{
$sort: { _id: 1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));

Mongoose Aggregate with Lookup

I have a simple two collections like below :
assignments:
[
{
"_id": "593eff62630a1c35781fa325",
"topic_id": 301,
"user_id": "59385ef6d2d80c00d9bdef97"
},
{
"_id": "593eff62630a1c35781fa326",
"topic_id": 301,
"user_id": "59385ef6d2d80c00d9bdef97"
}
]
and users collection:
[
{
"_id": "59385ef6d2d80c00d9bdef97",
"name": "XX"
},
{
"_id": "59385b547e8918009444a3ac",
"name": "YY"
}
]
and my intent is, an aggregate query by user_id on assignment collection, and also I would like to include user.name in that group collection. I tried below:
Assignment.aggregate([{
$match: {
"topic_id": "301"
}
},
{
$group: {
_id: "$user_id",
count: {
$sum: 1
}
}
},
{
$lookup: {
"from": "kullanicilar",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{
$project: {
"user": "$user",
"count": "$count",
"_id": "$_id"
}
},
But the problem is that user array is always blank.
[ { _id: '59385ef6d2d80c00d9bdef97', count: 1000, user: [] } ]
I want something like :
[ { _id: '59385ef6d2d80c00d9bdef97', count: 1000, user: [_id:"59385ef6d2d80c00d9bdef97",name:"XX"] } ]

Resources