0
0
MongodbHow-ToBeginner · 2 min read

MongoDB Query to Join Collections Using $lookup

Use the aggregation pipeline with $lookup to join collections in MongoDB, for example: db.orders.aggregate([{ $lookup: { from: 'products', localField: 'product_id', foreignField: '_id', as: 'product_info' } }]).
📋

Examples

Inputorders collection: [{ _id: 1, product_id: 101 }], products collection: [{ _id: 101, name: 'Pen' }]
Output[{ _id: 1, product_id: 101, product_info: [{ _id: 101, name: 'Pen' }] }]
Inputorders collection: [{ _id: 2, product_id: 202 }], products collection: [{ _id: 101, name: 'Pen' }]
Output[{ _id: 2, product_id: 202, product_info: [] }]
Inputorders collection: [], products collection: [{ _id: 101, name: 'Pen' }]
Output[]
🧠

How to Think About It

To join two collections in MongoDB, think of matching a field from the first collection with a field in the second collection. Use $lookup to specify the second collection, the fields to match, and the name of the new array field to hold matched documents.
📐

Algorithm

1
Start with the first collection you want to query.
2
Use the aggregation pipeline and add a $lookup stage.
3
In $lookup, specify the second collection name in 'from'.
4
Set 'localField' to the field in the first collection to match.
5
Set 'foreignField' to the field in the second collection to match.
6
Set 'as' to the name of the new field to store joined documents.
💻

Code

mongodb
db.orders.aggregate([
  {
    $lookup: {
      from: 'products',
      localField: 'product_id',
      foreignField: '_id',
      as: 'product_info'
    }
  }
])
Output
[ { _id: 1, product_id: 101, product_info: [ { _id: 101, name: 'Pen' } ] } ]
🔍

Dry Run

Let's trace joining orders with products where order has product_id 101.

1

Start with orders document

{ _id: 1, product_id: 101 }

2

Match product_id with products._id

Find product where _id = 101: { _id: 101, name: 'Pen' }

3

Add matched product to product_info array

{ _id: 1, product_id: 101, product_info: [{ _id: 101, name: 'Pen' }] }

order._idorder.product_idmatched productsresult.product_info
1101{ _id: 101, name: 'Pen' }[{ _id: 101, name: 'Pen' }]
💡

Why This Works

Step 1: Use $lookup to join collections

$lookup lets you combine documents from two collections by matching fields.

Step 2: Specify matching fields

You tell MongoDB which field in the first collection (localField) matches which field in the second (foreignField).

Step 3: Store matched documents in an array

The matched documents from the second collection are added as an array in the field named by as.

🔄

Alternative Approaches

Manual client-side join
mongodb
const orders = db.orders.find().toArray();
const products = db.products.find().toArray();
const joined = orders.map(order => ({
  ...order,
  product_info: products.filter(p => p._id === order.product_id)
}));
printjson(joined);
This approach fetches data separately and joins in application code; less efficient for large data.
Using $lookup with pipeline (MongoDB 3.6+)
mongodb
db.orders.aggregate([
  {
    $lookup: {
      from: 'products',
      let: { pid: '$product_id' },
      pipeline: [
        { $match: { $expr: { $eq: ['$_id', '$$pid'] } } }
      ],
      as: 'product_info'
    }
  }
])
More flexible join with conditions, but slightly more complex syntax.

Complexity: O(n*m) time, O(n + m) space

Time Complexity

The $lookup stage may scan the foreign collection for each document in the local collection, leading to O(n*m) in worst case, where n and m are sizes of collections.

Space Complexity

Additional space is used to store the joined arrays in the output documents, proportional to the number of matches.

Which Approach is Fastest?

$lookup is efficient for server-side joins; manual client-side joins require more memory and network overhead.

ApproachTimeSpaceBest For
$lookupO(n*m)O(n + m)Server-side joins with moderate data
$lookup with pipelineO(n*m)O(n + m)Complex join conditions
Manual client-side joinO(n*m)O(n + m)Small datasets or custom logic
💡
Always ensure the fields you join on have the same data type to avoid empty matches.
⚠️
Forgetting that $lookup adds an array even if only one matching document exists, so access joined data accordingly.