Chuyển đến nội dung chính

Bắt đầu với MongoDB Query API

Làm chủ MongoDB Query API với hướng dẫn toàn diện về CRUD, bộ lọc nâng cao, tổng hợp dữ liệu và lập chỉ mục tăng hiệu năng.
· 7 phút đọc

Khám phá cùng AI

Là một cơ sở dữ liệu NoSQL phổ biến, MongoDB cung cấp cách quản lý dữ liệu linh hoạt và có khả năng mở rộng. Hướng dẫn này là điểm khởi đầu để bạn làm chủ Query API trực quan của MongoDB, cho phép bạn tương tác với dữ liệu bằng BSON (Binary JSON) đơn giản, được tối ưu cho hiệu quả và kiểu dữ liệu. 

Xuyên suốt bài viết, chúng ta sẽ điểm qua các thao tác nền tảng tạo, đọc, cập nhật, xóa (CRUD), đi sâu vào truy vấn chính xác với bộ lọc và phép chiếu (projection), khám phá sức mạnh của pipeline tổng hợp để biến đổi dữ liệu, và bàn về lập chỉ mục nhằm tăng hiệu năng. Kết thúc, bạn sẽ có nền tảng vững chắc để truy xuất và thao tác dữ liệu hiệu quả trong các collection của MongoDB.

Khái niệm nền tảng: Các thao tác CRUD

Xương sống của mọi tương tác với cơ sở dữ liệu là CRUD: tạo, đọc, cập nhật và xóa. Hãy cùng xem cách thực hiện từng thao tác trong MongoDB.

Tạo dữ liệu (insert)

Để thêm tài liệu mới vào một collection, bạn dùng các phương thức insert. Tài liệu (document) trong MongoDB là các đối tượng giống JSON.

insertOne()

Phương thức này dùng để thêm một tài liệu đơn vào collection.

// A sample document to insert
const newProduct = {
  name: "Wireless Mouse",
  brand: "Logitech",
  price: 29.99,
  inStock: true
};

// Insert the new document into the 'products' collection
db.products.insertOne(newProduct);

insertMany()

Phương thức này thêm nhiều tài liệu cùng lúc, hiệu quả hơn nhiều so với chèn từng cái một.

// An array of documents to insert
const newProducts = [
  { name: "Mechanical Keyboard", brand: "Corsair", price: 129.99, inStock: false },
  { name: "Gaming Headset", brand: "Razer", price: 99.99, inStock: true },
  { name: "Webcam", brand: "LogiTech", price: 79.99, inStock: true }
];

// Insert the array of documents into the 'products' collection
db.products.insertMany(newProducts);

Đọc dữ liệu (find)

Phương thức find() là công cụ chính để truy xuất tài liệu từ một collection. Khi gọi find(), nó không trả về ngay tất cả tài liệu. Thay vào đó, nó trả về một con trỏ (cursor). Đây là cách hiệu quả để xử lý tập kết quả lớn mà không cần nạp tất cả vào bộ nhớ cùng lúc.

Lệnh find() đơn giản nhất không nhận đối số nào và sẽ trả về một cursor đến mọi tài liệu trong collection.

// Find and return a cursor to all documents in the 'products' collection
db.products.find();

Đây là ví dụ về đối tượng cursor trong mongosh (trình shell của MongoDB) và cách bạn có thể tương tác với nó:

// Find all products and assign the returned cursor to a variable
const productCursor = db.products.find();

// The variable 'productCursor' now holds the cursor object, not the documents themselves.
// To get the documents, you need to iterate over the cursor.

// You can convert the cursor to an array to see all the documents
const allProducts = productCursor.toArray();
console.log(allProducts);

// Or, you can manually iterate through the documents
while (productCursor.hasNext()) {
  const product = productCursor.next();
  console.log(product);
}

Bản thân cursor là một đối tượng với nhiều phương thức như toArray(), hasNext() và next() cho phép bạn truy xuất và làm việc với các tài liệu trong tập kết quả.

Cập nhật dữ liệu (update)

Để sửa đổi tài liệu hiện có, bạn dùng các phương thức update. Các phương thức này nhận hai đối số chính: một bộ lọc để chọn tài liệu cần cập nhật và một toán tử cập nhật để chỉ định thay đổi. Toán tử thường dùng là $set, dùng để gán giá trị cho một trường.

updateOne()

Phương thức này cập nhật tài liệu đầu tiên khớp với bộ lọc.

// Update the 'Wireless Mouse' document to set its price to 39.99
db.products.updateOne(
  { name: "Wireless Mouse" }, // Filter to find the document
  { $set: { price: 39.99 } } // Update operator
);

updateMany()

Phương thức này cập nhật tất cả tài liệu khớp với bộ lọc đã chỉ định.

// Update all 'LogiTech' brand products to be in stock
db.products.updateMany(
  { brand: "LogiTech" }, // Filter to find the documents
  { $set: { inStock: true } } // Update operator
);

Xóa dữ liệu (delete)

Để xóa tài liệu, bạn dùng các phương thức delete. Chúng cũng nhận một bộ lọc để xác định tài liệu cần xóa.

deleteOne()

Phương thức này xóa tài liệu đầu tiên khớp với bộ lọc.

// Delete the 'Webcam' document
db.products.deleteOne({ name: "Webcam" });

deleteMany()

Phương thức này xóa tất cả tài liệu khớp với bộ lọc. Để xóa toàn bộ tài liệu trong một collection, bạn có thể truyền bộ lọc rỗng {}.

// Delete all documents that are not in stock
db.products.deleteMany({ inStock: false });

Truy xuất chính xác: Bộ lọc truy vấn

Bộ lọc truy vấn là cốt lõi sức mạnh của MongoDB. Chúng là đối số đầu tiên trong các phương thức find() và update, cho phép bạn lựa chọn chính xác tài liệu muốn làm việc cùng.

Toán tử so sánh

Các toán tử này cho phép so sánh giá trị của một trường với giá trị chỉ định.

$eq (bằng)

Tìm các tài liệu có trường bằng với một giá trị.

// Find all products that are made by 'Razer'
db.products.find({ brand: { $eq: "Razer" } });
// This can be simplified to:
// db.products.find({ brand: "Razer" });

$gt và $gte (lớn hơn, lớn hơn hoặc bằng)

Tìm các tài liệu có giá trị trường lớn hơn ($gt) hoặc lớn hơn hoặc bằng ($gte) giá trị chỉ định.

// Find all products with a price greater than 100
db.products.find({ price: { $gt: 100 } });

$lt và $lte (nhỏ hơn, nhỏ hơn hoặc bằng)

Tìm các tài liệu có giá trị trường nhỏ hơn ($lt) hoặc nhỏ hơn hoặc bằng ($lte) giá trị chỉ định.

// Find all products with a price less than or equal to 50
db.products.find({ price: { $lte: 50 } });

$ne (khác)

$ne tìm các tài liệu có trường không bằng giá trị chỉ định.

// Find all products that are NOT made by 'LogiTech'
db.products.find({ brand: { $ne: "LogiTech" } });

$in và $nin (nằm trong mảng, không nằm trong mảng)

Tìm các tài liệu có giá trị trường nằm trong ($in) hoặc không nằm trong ($nin) một mảng giá trị chỉ định.

// Find all products from 'Razer' or 'Corsair'
db.products.find({ brand: { $in: ["Razer", "Corsair"] } });

Toán tử logic

Các toán tử logic kết hợp nhiều điều kiện để tạo truy vấn phức tạp.

$and

$and kết hợp các điều kiện truy vấn theo phép AND logic. Tất cả điều kiện phải đúng.

// Find all products with a price greater than 50 AND are in stock
db.products.find({ $and: [{ price: { $gt: 50 } }, { inStock: true }] });
// This can be simplified by just listing the conditions:
// db.products.find({ price: { $gt: 50 }, inStock: true });

$or

Kết hợp các điều kiện theo phép OR logic. Chỉ cần ít nhất một điều kiện đúng.

// Find all products with a brand of 'Corsair' OR a price less than 40
db.products.find({ $or: [{ brand: "Corsair" }, { price: { $lt: 40 } }] });

Toán tử phần tử và đánh giá 

Các toán tử này cung cấp thêm sự linh hoạt khi lọc.

$exists

$exists khớp các tài liệu có chứa một trường cụ thể.

// Find all documents that have an 'inStock' field
db.products.find({ inStock: { $exists: true } });

$regex

Cho phép dùng biểu thức chính quy để khớp mẫu trong các trường chuỗi.

// Find all product names that start with 'Wireless'
db.products.find({ name: { $regex: /^Wireless/ } });

Định hình đầu ra: Projection

Khi thực hiện thao tác find(), bạn có thể chọn chỉ nhận về một tập con các trường trong mỗi tài liệu. Điều này gọi là projection và là đối số thứ hai bạn truyền cho find(). Nó giúp giảm lượng dữ liệu truyền và xử lý.

Bao gồm trường

Bạn có thể chỉ định các trường cần trả về bằng cách đặt giá trị của chúng là 1.

// Find all products and return only the 'name' and 'price' fields
db.products.find({}, { name: 1, price: 1 });

Loại trừ trường

Ngược lại, bạn có thể chỉ định các trường cần ẩn bằng cách đặt giá trị của chúng là 0.

// Find all products and hide the 'inStock' field
db.products.find({}, { inStock: 0 });

Trường _id luôn được trả về theo mặc định trừ khi bạn loại trừ rõ ràng.

Trường _id

Để ẩn trường _id, bạn phải đặt rõ ràng nó là 0.

// Find all products, returning only 'name' and 'price', and hiding '_id'
db.products.find({}, { _id: 0, name: 1, price: 1 });

Biến đổi dữ liệu nâng cao: Aggregation Pipeline

Aggregation là một khung mạnh mẽ để xử lý dữ liệu trong MongoDB. Một pipeline tổng hợp gồm một chuỗi các giai đoạn xử lý tài liệu và truyền kết quả từ giai đoạn này sang giai đoạn kế tiếp.

$match

Giai đoạn $match lọc tài liệu dựa trên bộ lọc truy vấn, tương tự như trong find(). Thường đây là giai đoạn đầu để giảm số lượng tài liệu cần xử lý.

// Find all products with a brand of 'LogiTech'
db.products.aggregate([
  { $match: { brand: "LogiTech" } }
]);

$group

Giai đoạn $group nhóm các tài liệu theo một biểu thức _id chỉ định và có thể thực hiện nhiều phép tích lũy như tính tổng hoặc trung bình của một trường.

// Group products by brand and calculate the total number of products for each brand
db.products.aggregate([
  { $group: { _id: "$brand", totalProducts: { $sum: 1 } } }
]);

$project

Giai đoạn $project định hình lại từng tài liệu trong luồng, tương tự projection trong find(). Bạn có thể thêm trường mới, loại bỏ trường hiện có, và thao tác dữ liệu.

// Project a new field 'nameAndPrice' for each document
db.products.aggregate([
  { $project: { name: 1, price: 1, nameAndPrice: { $concat: ["$name", " (", "$price", ")"] } } }
]);

$sort

Giai đoạn $sort sắp xếp lại tài liệu theo trường chỉ định. Giá trị 1 là tăng dần và -1 là giảm dần.

// Sort all products by price in descending order
db.products.aggregate([
  { $sort: { price: -1 } }
]);

$limit và $skip

Các giai đoạn này dùng cho phân trang. $skip bỏ qua một số lượng tài liệu chỉ định từ đầu pipeline, và $limit giới hạn số tài liệu chuyển sang giai đoạn tiếp theo.

// Skip the first 10 products and then return the next 5
db.products.aggregate([
  { $skip: 10 },
  { $limit: 5 }
]);

Ví dụ pipeline đơn giản

Dưới đây là ví dụ hoàn chỉnh cho thấy cách các giai đoạn này phối hợp với nhau.

// Find the average price of all products that are currently in stock,
// grouped by brand.
db.products.aggregate([
  { $match: { inStock: true } }, // Filter for products in stock
  { $group: { _id: "$brand", averagePrice: { $avg: "$price" } } } // Group and calculate average price
]);

Tăng hiệu năng: Lập chỉ mục

Chỉ mục là các cấu trúc dữ liệu đặc biệt lưu trữ một phần nhỏ, dễ duyệt của dữ liệu trong collection. Chúng cải thiện đáng kể hiệu năng truy vấn bằng cách cho phép MongoDB nhanh chóng tìm và truy xuất tài liệu mà không phải quét toàn bộ collection.

createIndex()

Bạn tạo chỉ mục trên trường mà bạn thường xuyên truy vấn hoặc sắp xếp theo.

// Create a single-field index on the 'brand' field
db.products.createIndex({ brand: 1 });

Giá trị 1 cho biết thứ tự tăng dần cho chỉ mục. Bạn có thể dùng -1 cho thứ tự giảm dần, hữu ích cho các truy vấn sắp xếp theo hướng đó.

Tạo chỉ mục trên trường như brand sẽ giúp các truy vấn lọc theo brand (ví dụ, db.products.find({ brand: "Razer" })) chạy nhanh hơn nhiều, đặc biệt khi collection của bạn tăng trưởng.

Cân nhắc chọn trường để lập chỉ mục

Thông thường, bạn nên tạo chỉ mục trên các trường thường dùng trong truy vấn, chẳng hạn các trường trong bộ lọc find() hoặc giai đoạn $match trong aggregation.

  • Tính phân biệt (Cardinality): Các trường có nhiều giá trị duy nhất (độ phân biệt cao), như địa chỉ email của người dùng, là ứng viên tuyệt vời để lập chỉ mục.
  • Tính chọn lọc của truy vấn: Trường trả về một tập con nhỏ tài liệu cho một truy vấn nhất định cũng là ứng viên sáng giá.
  • Sắp xếp: Nếu bạn thường xuyên sắp xếp kết quả theo một trường cụ thể, tạo chỉ mục trên trường đó sẽ giúp thao tác sắp xếp nhanh hơn nhiều.

Tác động đến truy vấn

Chỉ mục có tác động lớn đến hiệu năng. Một truy vấn có chỉ mục thường nhanh hơn hàng bậc độ lớn so với cùng truy vấn thiếu chỉ mục.

// Conceptual illustration of performance difference

// Without an index on 'brand':
// MongoDB performs a full collection scan, checking every document.
// This is slow, especially with millions of documents.
db.products.find({ brand: "Razer" });

// With an index on 'brand':
// MongoDB uses the index to quickly locate and return only the matching documents.
// This is very fast.
db.products.find({ brand: "Razer" });

Để tối ưu truy vấn hơn nữa, bạn có thể dùng phương thức db.collection.explain() để phân tích hiệu năng truy vấn và xem chỉ mục được sử dụng như thế nào.

Kết luận: Hành trình chinh phục MongoDB

Bạn vừa hoàn thành một khóa học tốc hành về Query API của MongoDB! Giờ bạn đã biết cách thực hiện các thao tác CRUD cốt lõi, dùng bộ lọc mạnh mẽ để truy xuất dữ liệu cụ thể, định hình đầu ra bằng projection và biến đổi dữ liệu với aggregation pipeline. Bạn cũng có hiểu biết cơ bản về lập chỉ mục để cải thiện hiệu năng.

Hướng dẫn này cung cấp nền tảng vững chắc, và cách tốt nhất để làm chủ những khái niệm này là thực hành. Hãy thử nghiệm với các truy vấn khác nhau và khám phá tài liệu đầy đủ để tìm hiểu thêm các toán tử và tính năng nâng cao. Chúc bạn code vui vẻ!

MongoDB Query API FAQs

Sự khác biệt chính giữa find() và aggregate() là gì?

Phương thức find() dùng để truy xuất tài liệu đơn giản kèm lọc và projection tùy chọn. Trong khi đó, aggregate() là một khung mạnh mẽ cho xử lý dữ liệu đa giai đoạn, cho phép bạn biến đổi, nhóm, sắp xếp và định hình lại tài liệu theo cách phức tạp hơn. Nếu find() dùng để đọc tài liệu như vốn có, thì aggregate() giúp rút ra những hiểu biết mới từ dữ liệu của bạn.

Vì sao MongoDB trả về một cursor từ thao tác find() thay vì tài liệu thực tế?

Cursor là cách hiệu quả để xử lý các tập kết quả có thể rất lớn. Bằng cách trả về cursor, MongoDB tránh nạp tất cả tài liệu vào bộ nhớ cùng lúc. Điều này rất quan trọng cho hiệu năng và khả năng mở rộng, đặc biệt khi làm việc với hàng triệu tài liệu. Cursor cho phép bạn lặp qua kết quả khi cần, lấy tài liệu theo từng lô.

Khi nào nên dùng updateOne() so với updateMany()?

Dùng updateOne() khi bạn cần sửa đổi chỉ tài liệu đầu tiên khớp với bộ lọc. Điều này lý tưởng khi bạn kỳ vọng chỉ có một tài liệu khớp, chẳng hạn khi lọc theo định danh duy nhất. Dùng updateMany() khi bạn muốn cập nhật mọi tài liệu đáp ứng tiêu chí lọc.

Chỉ mục cải thiện hiệu năng truy vấn như thế nào?

Chỉ mục là các cấu trúc dữ liệu đặc biệt lưu trữ một phần nhỏ, có tổ chức của dữ liệu trong collection. Chúng tăng tốc truy vấn đáng kể bằng cách cho phép MongoDB nhanh chóng xác định tài liệu mà không phải quét toàn bộ collection. Thay vì kiểm tra từng tài liệu một, cơ sở dữ liệu có thể dùng chỉ mục để nhảy thẳng đến các tài liệu khớp, giúp thao tác nhanh hơn hàng bậc độ lớn.

Tôi có thể kết hợp projection với aggregation không?

Có, projection là phần quan trọng của aggregation pipeline. Giai đoạn $project được dùng để định hình lại tài liệu, thêm trường mới hoặc loại bỏ trường hiện có. Thường dùng sau các giai đoạn như $match hoặc $group để định dạng đầu ra của pipeline, mang lại cho bạn quyền kiểm soát chi tiết cấu trúc cuối cùng của dữ liệu.

Karen Zhang

Tác giả

Karen Zhang

Karen là một Kỹ sư Dữ liệu đam mê xây dựng các nền tảng dữ liệu có khả năng mở rộng. Cô có kinh nghiệm tự động hóa hạ tầng với Terraform và háo hức chia sẻ những gì mình học được qua các bài blog và hướng dẫn. Karen là một người xây dựng cộng đồng và cô tâm huyết với việc kết nối những chuyên gia dữ liệu.

Chủ đề

Liên quan