MongoDB Join on _id field from String to ObjectId aggregate $lookup - node.js

I have two collections.
Customers:
{
"_id" : ObjectId("584aac38686860d502929b8b"),
"name" : "user",
"email" : "user#gmail.com"
}
Posts:
{
"_id" : ObjectId("584aaca6686860d502929b8d"),
"title" : "Post",
"description":"description",
"user_id" : "584aac38686860d502929b8b"
}
I want to join this collection based on the user_id (from posts collection) - _id ( in customers collection).
I tried the below query:
dbo.collection('customers').aggregate([
{
$lookup:
{
from: 'posts',
localField: 'user_id',
foreignField: '_id',
as: 'posts'
}
}
])
but it's not working.
The output I am getting:
{
"_id": "584aac38686860d502929b8b",
"name": "user",
"email": "user#gmail.com",
"posts": []
}

From attached posts collection, user_id was a string but not ObjectId.
To compare, you have to convert user_id to ObjectId first.
db.customers.aggregate([
{
$lookup: {
from: "posts",
let: {
customerId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
{
"$toObjectId": "$user_id"
},
"$$customerId"
]
}
}
}
],
as: "posts"
}
}
])
Sample Mongo Playground
Note: From your existing $lookup, you have reversed localField and foreignField.
Equality Match with a Single Join Condition
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

Related

Query a MongoDB collection by the property of an embedded relation

I am trying to find documents in a collection, but filtered based on the value of an embedded ObjectID relation.
Mongoose schema is as follows:
const UserQualificationSchema = new Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
}
const UserSchema = new Schema(
{
fleet: {
type: [String], // Eg ["FleetA", "FleetB", "FleetC"]
required: true,
}
}
I need to find all UserQualifications where an item in the user's fleet equals a value in a filter array.
For example: Find all User Qualifications where user.fleet: {$in: ["FleetA", "FleetC"]}
I've looked at aggregations and querying inside .populate() but can't seem to get it to work.
Any ideas much appreciated.
Use aggregation query for your problem, I have created a query for you.
users.collection.json
/* 1 */
{
"_id" : ObjectId("61056c4a8cca27df3db2e4c8"),
"firstName" : "Rahul",
"lastName" : "soni",
"fleet" : [
"FleetA",
"FleetB",
"FleetC"
],
"createdAt" : ISODate("2021-07-31T15:29:14.918Z")
}
userqualifications.collection.json
/* 1 */
{
"_id" : ObjectId("61056c908cca27df3db2e4c9"),
"user" : ObjectId("61056c4a8cca27df3db2e4c8"),
"createdAt" : ISODate("2021-07-31T15:30:24.510Z")
}
aggregation query:
it will get the result only if a user has FleetA and FleetC.
if anyone is not matched then it will return 0 records
db.userqualifications.aggregate([{
"$lookup": {
"from": "users",
"localField": "user",
"foreignField": "_id",
"as": "user"
}
}, {
"$unwind": "$user"
}, {
"$match": {
"user.fleet": {
"$elemMatch": {
"$eq": "FleetA",
"$eq": "FleetC"
}
}
}
}])
Result:
/* 1 */
{
"_id" : ObjectId("61056c908cca27df3db2e4c9"),
"user" : {
"_id" : ObjectId("61056c4a8cca27df3db2e4c8"),
"firstName" : "Rahul",
"lastName" : "soni",
"fleet" : [
"FleetA",
"FleetB",
"FleetC"
],
"createdAt" : ISODate("2021-07-31T15:29:14.918Z")
},
"createdAt" : ISODate("2021-07-31T15:30:24.510Z")
}
if the goal is to get only UserQualifications then the following should be an efficient way as it can use an index on the fleet field of the User collection.
db.User.aggregate([
{
$match: {
fleet: { $in: ["FleetA", "FleetB"] }
}
},
{
$lookup: {
from: "UserQualification",
localField: "_id",
foreignField: "user",
as: "qualifications"
}
},
{
$unwind: "$qualifications"
},
{
$replaceWith: "$qualifications"
}
])
on the other hand if you start from the UserQualifications collection, you can't efficiently narrow down the records as you're filtering on something that it doesn't have the data for.
Thank you for the answer - it did achieve the results I was looking for - however I am now struggling to add a $match with $and to the aggregate to only return the qualifications where the user ID equals one inside a submitted array AND a given fleet.
I have the following aggregate:
db.UserQualifications.aggregate([{
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'user',
},
},
{
$unwind: '$user',
},
{
$match: {
$and: [
'user.fleet': {
$in: ["Fleet A", "Fleet C"], // This works on it's own
},
user: { // Also tried 'user._id'
$in: ["6033e4129070031c07fbbf29"] // Adding this returns blank array
}
]
},
}
}])
I have double checked that I am passing in the correct User ID's inside the arrays, but when I add this to the $and inside match, it only returns a blank array.
Is there another way to do this?
// Updated users collection
/* 1 */
{
"_id" : ObjectId("61056c4a8cca27df3db2e4c8"),
"firstName" : "Rahul",
"lastName" : "soni",
"fleet" : [
"Fleet A",
"Fleet B",
"Fleet C"
],
"createdAt" : ISODate("2021-07-31T15:29:14.918Z")
}
Query:
// userqualifications => this is my collection name, you can add your collection name here db.<YOUR>
db.userqualifications.aggregate([{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'user',
},
},
{
$unwind: '$user',
},
{
$match: {
// $and operatory syntax [{}, {}]
$and: [{
'user.fleet': {
// "Fleet A", "Fleet C" (FleetA, FleetC) this is the my first options,
// I have changes according to your problem
$in: ["Fleet A", "Fleet C"], // This works on it's own
}
}, {
// Convert user id to ObjectId type (_bsonType)
"user._id": ObjectId("61056c4a8cca27df3db2e4c8")
}]
}
}
])
Result:
/* 1 */
{
"_id" : ObjectId("61056c908cca27df3db2e4c9"),
"user" : {
"_id" : ObjectId("61056c4a8cca27df3db2e4c8"),
"firstName" : "Rahul",
"lastName" : "soni",
"fleet" : [
"Fleet A",
"Fleet B",
"Fleet C"
],
"createdAt" : ISODate("2021-07-31T15:29:14.918Z")
},
"createdAt" : ISODate("2021-07-31T15:30:24.510Z")
}
Difference Image:

get _id based on value from a collection for a array and insert in mongodb collection

Input:
[{"gradeName":1,"sectionName":"a","maximumStudents":12},{"gradeName":2,"sectionName":"b","maximumStudents":13}]
In mongodb how to get the _id of gradeName and sectionName from two collection and store in the third collection say mergeddetails
mergeddetails:
[{"grade_id":"60f819e04a9d43158cabad55","section_id":"60f819e04a9d43158cabad54","maximumStudents":12},{"grade_id":"60f819e04a9d43158cabad59","section_id":"60f819e04a9d43158cabad5b","maximumStudents":13}]
where grade_id is the _id of corresponding gradeName,section_id is the _id of corresponding sectionName
please help with with mongodb quire i am using nodejs to write the API
You need to use $lookup for both the collections
db.student.aggregate([
{ $lookup:
{ from: "grade",
localField: "gradeName",
foreignField: "grade",
as: "grade"
}
},
{$lookup:
{ from: "section",
localField: "sectionName",
foreignField: "section",
as: "section"
}
},
{ $unwind: "$grade" },
{ $unwind: "$section" },
{$project:
{
_id:0,
"grade_id":"$grade._id",
"section_id":"$section._id",
maxStudent:1
}
}
])

MongoDB $lookup in different collections by _id

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"
}
},
])

Join a collection to an existing aggregate query mongodb

I have the following collection structures in my mongodb database.
Orders
[
{
"_id":"order_abcd",
"name":"Order 1"
},
{
"_id":"order_defg",
"name":"Order 2"
}
]
Session
{
"_id":"session_abcd"
"orders": [ ObjectId("order_abcd"), ObjectId("order_defg") ]
}
Transactions
{
"_id":"transaction_abcd"
"id_session" : ObjectId("session_abcd")
}
What I am trying to achieve is a dataset that looks similar to this
[
{
"_id":"order_abcd",
"name":"Order 1",
"transaction":"transaction_abcd"
},
{
"_id":"order_defg",
"name":"Order 2",
"transaction":"transaction_abcd"
}
]
I do not have any input data other than a start date and an end date which will be used to filter orders, the query is mostly for reporting purposes so in effect I am trying to generate a query to fetch all orders between a given time period and attach the transaction id for each order to it.
We can use couple of $lookup (analogous to join with 2 tables in SQL) with $unwind at each stage to finally $project the key-value pair that is desired.
db.Session.aggregate([
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{$project:{
_id:"$order_info._id",
name:"$order_info.name",
transaction:"$transaction_info._id"
}}
]).pretty();
Which gives an output:
{
"_id" : "order_abcd",
"name" : "Order 1",
"transaction" : "transaction_abcd"
},
{
"_id" : "order_defg",
"name" : "Order 2",
"transaction" : "transaction_abcd"
}
The unwind stages are used to explode the lookup and then cherry pick fields at final project stage.
++UPDATE++
Another option that probably can help reduce the 2nd stage lookup records since $match on dates of Orders can be applied to pass on filtered docs for next stage.
db.Session.aggregate([
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{
$match: {} //filter on "order_info.property" (i:e; date,name,id)
},
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$project: {
_id: "$order_info._id",
name: "$order_info.name",
transaction: "$transaction_info._id"
}
}
]).pretty();

How to show particular columns in mongodb?

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
}
},
]
);

Resources