TopDev

MongoDB có hỗ trợ các kiểu join như trong SQL không?

minhdev 📖 7 phút đọc

MongoDB không hỗ trợ join theo cách truyền thống như trong SQL, tức là không có câu lệnh JOIN giống như trong các hệ quản trị cơ sở dữ liệu quan hệ (RDBMS). Tuy nhiên, từ phiên bản 3.2 trở đi, MongoDB đã cung cấp một tính năng tương tự gọi là $lookup trong Aggregation Framework, giúp thực hiện join giữa các collection.



Giải thích về $lookup trong MongoDB#

  • $lookup cho phép kết hợp dữ liệu từ hai collection khác nhau trong cùng một database.

  • Cú pháp của $lookup tương tự join bên SQL nhưng linh hoạt hơn trong việc xử lý dữ liệu dạng document.

  • $lookup thực hiện join kiểu left outer join (liên kết trái), tức là giữ tất cả các document bên collection chính và nối thêm dữ liệu từ collection thứ hai nếu khớp.



Ví dụ sử dụng $lookup:#

Giả sử bạn có hai collection:

  • orders: lưu các đơn hàng, có trường customer_id

  • customers: lưu thông tin khách hàng, có trường _id

Bạn muốn lấy thông tin đơn hàng kèm theo thông tin khách hàng tương ứng:

db.orders.aggregate([ { $lookup: { from: "customers", // collection cần join localField: "customer_id", // trường bên orders foreignField: "_id", // trường bên customers as: "customer_info" // tên trường mới chứa dữ liệu join } } ])

Kết quả mỗi document trong orders sẽ có thêm trường customer_info chứa thông tin khách hàng tương ứng (dưới dạng mảng).



Một số lưu ý khi dùng $lookup:#

  • $lookup chỉ hỗ trợ join giữa các collection trong cùng một database.

  • Hiệu suất join phụ thuộc vào kích thước collection và việc có index phù hợp trên trường join.

  • Không hỗ trợ join phức tạp như join đa bảng hoặc join với điều kiện phức tạp như SQL.

  • Nếu cần join dữ liệu rất phức tạp hoặc liên quan nhiều bảng, thường nên cân nhắc thiết kế lại schema hoặc xử lý join ở tầng ứng dụng.



Tóm lại#

MongoDB không có join truyền thống như SQL nhưng với $lookup, nó cung cấp một giải pháp join mạnh mẽ, phù hợp với mô hình dữ liệu NoSQL dạng document. Bạn hoàn toàn có thể xử lý được các trường hợp join phổ biến mà không cần chuyển sang RDBMS.



Dưới đây là một số ví dụ nâng cao hơn về cách sử dụng $lookup trong MongoDB để thực hiện các kiểu join phức tạp hơn, giúp bạn tận dụng tối đa tính năng này trong xử lý dữ liệu:



1. Join nhiều collection liên tiếp (Nested Lookup)#

Giả sử bạn có ba collection: orders, customersproducts.

  • orders có trường customer_idproduct_ids (mảng id sản phẩm).

  • customers có thông tin khách hàng.

  • products có thông tin sản phẩm.

Bạn muốn lấy đơn hàng kèm theo thông tin khách hàng và thông tin tất cả sản phẩm trong đơn.

db.orders.aggregate([ { $lookup: { from: "customers", localField: "customer_id", foreignField: "_id", as: "customer_info" } }, { $unwind: "$customer_info" // biến mảng thành document để dễ xử lý }, { $lookup: { from: "products", localField: "product_ids", foreignField: "_id", as: "products_info" } } ])

Kết quả trả về sẽ gồm mỗi đơn hàng kèm dữ liệu khách hàng chi tiết và mảng thông tin các sản phẩm.



2. Join với điều kiện phức tạp dùng pipeline trong $lookup (MongoDB 3.6+)#

MongoDB 3.6 hỗ trợ $lookup nâng cao với pipeline cho phép join theo điều kiện phức tạp hơn.

Ví dụ bạn muốn join từ orders đến customers với điều kiện status của khách hàng phải là "active":

db.orders.aggregate([ { $lookup: { from: "customers", let: { customerId: "$customer_id" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$_id", "$$customerId"] }, { $eq: ["$status", "active"] } ]} } } ], as: "active_customer_info" } } ])

Ở đây, $expr cho phép dùng biểu thức để so sánh, let truyền biến từ collection chính vào pipeline join.



3. Join với nhiều điều kiện AND / OR#

Bạn có thể sử dụng $expr kết hợp nhiều điều kiện logic để join phức tạp.

Ví dụ join orders với customers dựa trên customer_idregion phải trùng khớp:

db.orders.aggregate([ { $lookup: { from: "customers", let: { custId: "$customer_id", orderRegion: "$region" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$_id", "$$custId"] }, { $eq: ["$region", "$$orderRegion"] } ] } } } ], as: "matched_customers" } } ])



4. Join nhiều trường và sử dụng unwind để thao tác dữ liệu#

Sau khi join, dữ liệu trả về dạng mảng. Nếu muốn xử lý từng phần tử, bạn có thể dùng $unwind.

Ví dụ:

db.orders.aggregate([ { $lookup: { from: "customers", localField: "customer_id", foreignField: "_id", as: "customer_info" } }, { $unwind: "$customer_info" }, { $lookup: { from: "products", localField: "product_ids", foreignField: "_id", as: "products_info" } }, { $unwind: "$products_info" }, { $group: { _id: "$_id", customer: { $first: "$customer_info" }, products: { $push: "$products_info" } } } ])



Tóm lại#

  • MongoDB hỗ trợ join qua $lookup, tương tự LEFT JOIN trong SQL.

  • Bạn có thể dùng pipeline để join theo điều kiện phức tạp hơn.

  • $unwind giúp thao tác dữ liệu sau join.

  • Mặc dù có giới hạn so với SQL join đa bảng và phức tạp, nhưng $lookup đáp ứng rất tốt nhu cầu phổ biến trong các ứng dụng NoSQL.



Mình sẽ gửi bạn thêm ví dụ cụ thể về cách dùng $lookup trong MongoDB để làm join phức tạp với giải thích chi tiết từng bước.



Ví dụ thực tế: Quản lý đơn hàng, khách hàng và sản phẩm#

Giả sử có 3 collection:

  • orders:

{ "_id": 1, "order_number": "DH001", "customer_id": 101, "product_ids": [201, 202], "region": "Hanoi" }

  • customers:

{ "_id": 101, "name": "Nguyen Van A", "status": "active", "region": "Hanoi" }

  • products:

{ "_id": 201, "product_name": "Laptop Dell", "price": 1500 }



Mục tiêu:#

Lấy đơn hàng kèm theo thông tin khách hàng (chỉ lấy khách hàng active và cùng region với đơn hàng) và thông tin các sản phẩm trong đơn.



Câu truy vấn MongoDB Aggregate với $lookup phức tạp:#

` db.orders.aggregate([ // Join với customers theo điều kiện status = "active" và cùng region { $lookup: { from: "customers", let: { custId: "$customer_id", orderRegion: "$region" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$_id", "$$custId"] }, { $eq: ["$status", "active"] }, { $eq: ["$region", "$$orderRegion"] } ] } } } ], as: "customer_info" } },

// Biến mảng customer_info thành đối tượng (do chỉ 1 khách hàng nên dùng unwind) { $unwind: "$customer_info" },

// Join với products theo mảng product_ids { $lookup: { from: "products", localField: "product_ids", foreignField: "_id", as: "products_info" } },

// Tùy chọn: chọn trường trả về { $project: { order_number: 1, customer_name: "$customer_info.name", products: "$products_info.product_name" } } ]) `



Giải thích chi tiết:#

  • $lookup với pipeline:

    let dùng để truyền biến từ document orders vào pipeline của customers.

    • Trong pipeline $match, dùng $expr để so sánh giá trị _id khách hàng với biến custId, đồng thời kiểm tra statusregion.

    • Chỉ những khách hàng thỏa mãn điều kiện mới được join.

  • $unwind:

    Biến mảng customer_info thành một document đơn, thuận tiện để truy cập trực tiếp các trường như name.

  • $lookup với mảng product_ids:

    Ở đây join dựa trên localField là một mảng, MongoDB sẽ tự động join tất cả các _id trong mảng đó với products.

  • $project:

    Lọc và cấu trúc lại dữ liệu trả về, chỉ lấy số đơn hàng, tên khách hàng và danh sách tên sản phẩm.



Kết quả trả về (ví dụ):#

{ "order_number": "DH001", "customer_name": "Nguyen Van A", "products": ["Laptop Dell", "Chuột Logitech"] }

Bài liên quan trong #Database

✓ Đã sao chép link