TL;DR: Cách sửa nhanh
Lỗi này xảy ra vì $size rất khắt khe. Nó yêu cầu một mảng, nhưng lại tìm thấy giá trị null hoặc một trường bị thiếu. Để sửa lỗi ngay lập tức, hãy bọc trường của bạn bằng $ifNull để cung cấp một mảng rỗng [] làm phương án dự phòng.
// Cách gây lỗi:
{ $project: { count: { $size: "$tags" } } }
// Cách an toàn:
{
$project: {
count: {
$size: { $ifNull: [ "$tags", [] ] }
}
}
}
Tại sao Pipeline của bạn bị lỗi
Sự linh hoạt của MongoDB là một con dao hai lưỡi. Vì không có schema chặt chẽ, một document có thể có mảng tags trong khi document tiếp theo lại thiếu hoàn toàn. Khi $size gặp trường bị thiếu đó, nó không chỉ trả về 0 mà sẽ làm hỏng toàn bộ aggregation.
Ngay cả khi bạn có 1.000.000 document hoàn hảo, chỉ cần một bản ghi bị thiếu trường cũng sẽ khiến truy vấn thất bại. Điều này thường xảy ra vì ba lý do:
- Các trường bị thiếu: Khóa đơn giản là không có trong document.
- Giá trị Null: Trường có tồn tại nhưng được đặt là
nullmột cách rõ ràng bởi ứng dụng hoặc script import. - Sai lệch kiểu dữ liệu: Một trường chứa một chuỗi (như
"none") hoặc một đối tượng thay vì một mảng như mong đợi.
Các giải pháp đã qua thử nghiệm thực tế
Cách 1: Sử dụng $ifNull (Cách sửa tiêu chuẩn)
Nếu bạn chủ yếu đối mặt với các trường bị thiếu hoặc null, $ifNull là lựa chọn tốt nhất. Nó đóng vai trò như một lưới an toàn. Nếu trường bị thiếu, nó sẽ chuyển một mảng rỗng cho $size, sau đó trả về giá trị đếm là 0.
db.posts.aggregate([
{
$project: {
title: 1,
comment_count: {
$size: { $ifNull: [ "$comments", [] ] }
}
}
}
])
Cách 2: Sử dụng $isArray (Cách tiếp cận toàn diện)
Dữ liệu thực tế thường khá lộn xộn. Bạn có thể gặp document mà comments là kiểu boolean hoặc chuỗi. $ifNull sẽ không bắt được những kiểu này. Để an toàn tuyệt đối, hãy dùng $isArray để xác thực dữ liệu trước khi đo lường.
db.posts.aggregate([
{
$addFields: {
comment_count: {
$cond: {
if: { $isArray: "$comments" },
then: { $size: "$comments" },
else: 0
}
}
}
}
])
Logic điều kiện này đảm bảo pipeline của bạn vẫn hoạt động bất kể kiểu dữ liệu nào đang tồn tại trong collection.
Cách 3: Lọc sớm với $match (Lựa chọn tối ưu hiệu suất)
Đôi khi bạn không muốn xử lý các document "lỗi" ngay từ đầu. Bằng cách lọc các mảng ngay khi bắt đầu pipeline, bạn sẽ giảm bớt khối lượng công việc cho các giai đoạn sau. Cách này thường nhanh hơn vì nó tận dụng được index.
db.posts.aggregate([
{
$match: {
comments: { $exists: true, $type: "array" }
}
},
{
$project: {
comment_count: { $size: "$comments" }
}
}
])
Xác minh giải pháp
Đừng đoán mò—hãy kiểm tra. Tạo một collection nhỏ với dữ liệu "rác" để xác nhận aggregation của bạn xử lý được mọi trường hợp biên.
db.test_collection.insertMany([
{ _id: 1, items: ["táo", "chuối"] }, // Mảng hợp lệ
{ _id: 2 }, // Trường bị thiếu
{ _id: 3, items: null }, // Trường null
{ _id: 4, items: "không-phải-mảng" } // Chuỗi (sử dụng Cách 2 cho trường hợp này!)
]);
// Chạy xác minh
db.test_collection.aggregate([
{
$project: {
count: {
$cond: [
{ $isArray: "$items" },
{ $size: "$items" },
0
]
}
}
}
]);
Một giải pháp thành công sẽ trả về kết quả đếm là 2, 0, 0 và 0 mà không có bất kỳ thông báo lỗi nào.

