How to do inner joining in MongoDB? - mongodb

Is it possible to do SQL inner joins kind of stuff in MongoDB?
I know there is the $lookup attribute in an aggregation pipeline and it is equivalent to outer joins in SQL, but I want to do something similar to inner joins.
I have three collections which need to merge together:
// User Collection
db.User.find({});
// Output:
{
ID : 1,
USER_NAME : "John",
password : "pass"
}
{
ID : 2,
USER_NAME : "Andrew",
PASSWORD : "andrew"
}
// Role Collection
db.ROLE.find({});
// Output:
{
ID : 1,
ROLE_NAME : "admin"
},
{
ID : 2,
ROLE_NAME : "staff"
}
// USER_ROLE Collection
db.USER_ROLE.find({});
// Output:
{
ID : 1,
USER_ID : 1,
ROLE_ID : 1
}
I have the above collections and I want to extract only the documents matched with users and their respective roles, not all the documents. How can I manage it in MongoDB?

I found answer my self it was
$unwind
done the trick to me following query worked for me
db.USER.aggregate([{
$lookup: {
from: "USER_ROLE",
localField: "ID",
foreignField: "USER_ID",
as: "userRole"
}
}, {
$unwind: {
path: "$userRole",
preserveNullAndEmptyArrays: false
}
}, {
$lookup: {
from: "ROLE",
localField: "userRole.ROLE_ID",
foreignField: "ID",
as: "role"
}
}, {
$unwind: {
path: "$role",
preserveNullAndEmptyArrays: false
}
}, {
$match: {
"role.ROLE_NAME": "staff"
}, {
$project: {
USER_NAME: 1,
_id: 0
}
}
]).pretty()
Anyway thanks for the answers

As Tiramisu wrote this looks like schema issue.
You can make a manual inner join, by removing documents where $lookup returned empty array.
....
{$lookup... as myArray},
{$match: {"myArray":{$ne:[]}}},
{$lookup... as myArray2},
{$match: {"myArray2":{$ne:[]}}},
schema change
I personally will go for schema update, like this:
db.User.find({})
{
ID : 1,
USER_NAME : "John",
password : "pass"
roles:[{ID : 1, ROLE_NAME : "admin"}]
}
db.ROLE.find({})
{
ID : 1,
ROLE_NAME : "admin"
},

Will this help
const RolesSchema = new Schema({
....
});
const Roles = mongoose.model('Roles', RolesSchema);
const UserSchema = new Schema({
...
roles: [{ type: mongoose.Schema.Types.ObjectId, ref: "Roles" }]
});
using the populate on userschema you can also reduce the redundancy

Well you are correct, $lookup attribute is equivalent to outer joins in SQL, but in mongoDB, you need additional aggregation stages so as to perform a similar INNER JOIN in mongo. Here's an example for joining User and ROLE collections based on the ID and displaying the results based on USER_NAME and ROLE_NAME
db.User.aggregate([{
$lookup: {
from: "ROLE",
localField: "ID",
foreignField: "ID",
as: "result"
}
},{
$match: {
result: {
$ne: []
}
}
},{
$addFields: {
result: {
$arrayElemAt: ["$result",0]
}
}
},{
$project: {
USER_NAME: "$USER_NAME",
ROLE_NAME: "$result.ROLE_NAME"
}
}])
Hope it helps!!

MongoDB $lookup aggregation is the most formal and the best-optimized method for this question. However, If you are using Node.js as the server-side, Then you can use a little hack as follows.
CollectionOne.find().then((data0) => {
if (data0.length > 0) {
let array = [];
for (let i = 0; i < data0.length; i++) {
let x = data0[i]
let y = x.yourForeignKey;
array.push({_id: y});
}
CollectionTwo.find(
{$or: array}
).then((data1) => {
res.status(200).json(data1);
}).catch((error1) => {
return error1;
})
}
}).catch((error0) => {
return error0;
});
I used the Array Push() method and the $or operator of MongoDB. You can use the $nor operator instead of the $or to find outer join documents. And also you can change the finding algorithm by using $ne, $nor, $or, $and and etc.

$lookup aggregation
Performs a left outer join to a collection in the same database to filter in documents from the "joined" collection for processing. The
$lookup
stage adds a new array field to each input document. The new array field contains the matching documents from the "joined" collection. The
$lookup
stage passes these reshaped documents to the next stage.
Starting in MongoDB 5.1,
$lookup
works across sharded collections.
To combine elements from two different collections, use the
$unionWith
pipeline stage.
Syntax
The $lookup stage has the following syntaxes:
Equality Match with a Single Join Condition
To perform an equality match between a field from the input documents with a field from the documents of the "joined" collection, the
$lookup
stage has this syntax:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
More details: https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/

> show dbs
admin 0.000GB
config 0.000GB
local 0.002GB
> use local
switched to db local
> show collections
startup_log
test1
test2
> db.test2.aggregate([{
... $lookup: {
... from: "test1",
... localField: "id",
... foreignField: "id",
... as: "aggTest"
... }
... }])

Related

How to get left join data using multiple condition in mongodb

I have two table
master_document_table document_table
id | title id | master_document_id | userId
1 | Profile
select * from master_document_table as md
left join document_table as d on md.id = d.master_document_id and d.userId = 2
Result:
id | title | master_document_id | userId
1 | Profile null null
how can this be achieved using mongodb i have tried & also did some research from stack overflow and did not got the expected result.
From mongo v3.2, it is possible to aggregate two or more than two collections.
But here one thing, you need to know is, you won't get the same structure as sql. You will get your data on nested document.
Your tables will look something like this in mongodb:
master_document_table (collection / table)
{
_id: ObjectId("5f28d53c00613e6ada45f702"),
title: "Profile",
}
document_table (collection / table)
{
_id: ObjectId("5f28d53c00613e6ada45e790"),
master_document_id: ObjectId("5f28d53c00613e6ada45f702"), // master_doc_table's ref id.
userId: ObjectId("5f28d53c00613e6ada327f231"), // user's ref id.
}
Your query will be:
db.document_table.aggregate([
{$match: {title: "Profile"}}, // you can give any condition here.
{
$lookup:
{
from: "master_document_table",
localField: "master_document_id",
foreignField: "_id",
as: "master_document_id"
}
}
])
Your query result will look something like:
[{
_id: ObjectId("5f28d53c00613e6ada45e790"),
master_document_id: {
_id: ObjectId("5f28d53c00613e6ada45f702"),
title: "Profile"
},
userId: ObjectId("5f28d53c00613e6ada327f231"),
}]
Now you can restructure it based on your requirement.
I would like to recommend you
https://docs.mongodb.com/manual/reference/operator/aggregation/match/
&
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
article from mongodb docs.
Hope this will help to resolve your problem.
Inorder to join two tables you can use the $lookup aggregation pipeline operator in version 3.6 and newer. Since you need the results like LEFT OUTER JOIN, you have to $unwind the array to objects and filter out the desired results using $match conditions (ie document_table.userId = 2). Dont forget to use preserveNullAndEmptyArrays to true when unwinding.
Sample code for reference:
db.master_document_table.aggregate([
{ "$lookup": {
"from": "document_table",
"localField": "id",
"foreignField": "master_document_id",
"as": "document_table"
}},
{ $unwind: { path: "$document_table", preserveNullAndEmptyArrays: true } },
{
$match:{
$or:[
{ "document_table": { $exists: false } },
{"document_table.userId" : 2 }
]
}
}
])
I some how managed to get desire result thanks for the help
db.master_document_table.aggregate([
{
$lookup: {
from: 'document_table',
localField: 'id',
foreignField: 'master_document_id',
as: 'users'
}
},
{
$project: {
title: 1,
users: {
$filter: {
input: '$users',
as: 'user',
cond: {
$eq: ['$$user.userId', 2]
}
}
}
}
}
]);

Mongo query with inner select (in MongoDB Compass filter)

I would like to create a mongo query with level between my documents. I tried join or inner select without success. I use the filter of MongoDB Compass to query my results.
A parent document :
{
'id':'parent01'
}
There are participation's documents who belong to a parent document :
{
'participation:parent': 'parent01',
'id':'participation01',
...
}
and observation's documents who belong to a participation :
{
'obs:participation':'participation01',
'id':'obs01'
}
I need to extract all documents who belong to the parent : the parent itself, all the participations and all the observations.
I only have the parent id to do the query.
It's really easy in SQL but I can't do it in Mongo (I use the filter of MongoDB Compass).
Thanks a lot for your help.
One answer below
[{
$match: {
'participation:parent': 'parent01'
}
}, {
$lookup: {
from: 'default',
localField: 'id',
foreignField: 'obs:participation',
as: 'myObs'
}
}, {
$unwind: {
path: '$myObs',
preserveNullAndEmptyArrays: false
}
}, {
$project: {
myObs: 1
}
}]

How to make a query in two different collections in mongoDB? (without using ORM)

Suppose, In MongoDB i have two collections. one is "Students" and the another is "Course".
Student have the document such as
{"id":"1","name":"Alex"},..
and Course has the document such as
{"course_id":"111","course_name":"React"},..
and there is a third collection named "students-courses" where i have kept student's id with their corresponding course id. Like this
{"student_id":"1","course_id":"111"}
i want to make a query with student's id so that it gives the output with his/her enrolled course. like this
{
"id": "1",
"name":"Alex",
"taken_courses": [
{"course_id":"111","course_name":"React"},
{"course_id":"112","course_name":"Vue"}
]
}
it will be many to many relationship in MongoDB without using ORM. How can i make this query?
Need to use $loopup with pipeline,
First $group by student_id because we are going to get courses of students, $push all course_id in course_ids for next step - lookup purpose
db.StudentCourses.aggregate([
{
$group: {
_id: "$student_id",
course_ids: {
$push: "$course_id"
}
}
},
$lookup with Student Collection and get the student details in student
$unwind student because its an array and we need only one from group of same student record
$project required fields
{
$lookup: {
from: "Student",
localField: "_id",
foreignField: "id",
as: "student"
}
},
{
$unwind: "$student"
},
{
$project: {
id: "$_id",
name: "$student.name",
course_ids: 1
}
},
$lookup Course Collection and get all courses that contains course_ids, that we have prepared in above $group
$project the required fields
course details will store in taken_courses
{
$lookup: {
from: "Course",
let: {
cId: "$course_ids"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$course_id",
"$$cId"
]
}
}
},
{
$project: {
_id: 0
}
}
],
as: "taken_courses"
}
},
$project details, removed not required fields
{
$project: {
_id: 0,
course_ids: 0
}
}
])
Working Playground: https://mongoplayground.net/p/FMZgkyKHPEe
For more details related syntax and usage, check aggregation

MongoDB aggregate $lookup to return unmatched items from the main collection

I would like unmatched data from the USERS collection. Here I have two collections 1) USERS, 2) COMPANY.I am able to get the matched data from both USERS using aggregate function. but in this case I want data from USERS table which are not assigned to a company.
USERS table
{
_id: "AAA",
fullName:"John Papa"
},
{
_id: "BBB",
fullName:"Robin Son"
}
COMPANY table
{
_id: "1sd1s",
Name:"Lumbar Company"
User:"AAA"
},
{
_id: "23s1dfs3",
Name:"Patricia"
User:"AAA"
}
$lookup works like LEFT OUTER JOIN so it will remove empty array when there's no match. Then you can use $size to get only empty arrays:
db.users.aggregate([
{
$lookup: {
from: "company",
localField: "_id",
foreignField: "User",
as: "companies"
}
},
{
$match: {
$expr: {
$eq: [ { "$size": "$companies" }, 0 ]
}
}
}
])
Mongo Playground

Doctrine ODM: create $lookup on aggregated field with aggregation builder

In a simplified data model, I have three types of documents: items, users and assignments. Users and items are stored in their own collections, while assignments are embedded in items. A sample item might look like this:
{
"_id" : ObjectId("xxx"),
"name" : "yyy",
"assignments" : [
{
"assignmentDate" : ISODate("2018-01-11T10:05:20.125Z"),
"user" : ObjectId("zzz"),
},
{
"assignmentDate" : ISODate("2018-01-12T10:05:20.125Z"),
"user" : ObjectId("iii"),
}
]
}
I would like to query all items that are currently assigned to a given user. This aggregation pipeline does the job:
db.Item.aggregate([
{
$addFields: {
currentAssignment: { $arrayElemAt: ['$assignments', -1] }
}
},
{
$lookup: {
from: 'User',
localField: 'currentAssignment.user',
foreignField: '_id',
as: 'currentUser'
}
},
{
$match: {
'currentUser.name': { $eq: 'admin' }
}
}
]);
How can I build this with the Doctrine ODM Aggregation Builder? The Stage::lookup method accepts only a from parameter. If I use it on a computed field from the aggregation pipeline (currentAssignment in this case), it results in:
arguments to $lookup must be strings, localField: null is type null
Other solutions (if possible even without aggregation?) for retrieving the described dataset are also welcome.
The Lookup stage has more methods, one of which is localField which sets the localField in the aggregation stage.