Stage $project trong MongoDB Aggregation Pipeline dùng để **chọn, đổi tên, tính toán, restructure** trường đầu ra. Nó tương tự projection trong find() nhưng mạnh hơn rất nhiều: cho phép biểu thức, điều kiện, mảng, object lồng.
1. Tạo dữ liệu: MarksInformation có cấu trúc khác nhau (Marks1-3 vs Score1-2)
> db.demo439.insertOne({ "Name": "Chris", "MarksInformation": { "Marks1": 67, "Marks2": 45, "Marks3": 78 } });
> db.demo439.insertOne({ "Name": "David", "MarksInformation": { "Marks1": 50, "Marks2": 57, "Marks3": 68 } });
> db.demo439.insertOne({ "Name": "Bob", "MarksInformation": { "Score1": 65, "Score2": 71 } });
2. $project: Chuẩn hóa output thành Marks1, Marks2, Marks3 (fallback Score -> Marks)
> db.demo439.aggregate([
{
$project: {
"Name": 1,
"Marks1": {
$cond: [
{ $eq: [{ $ifNull: ["$MarksInformation.Marks1", 0] }, 0] },
{ $ifNull: ["$MarksInformation.Score1", 0] },
"$MarksInformation.Marks1"
]
},
"Marks2": {
$cond: [
{ $eq: [{ $ifNull: ["$MarksInformation.Marks2", 0] }, 0] },
{ $ifNull: ["$MarksInformation.Score2", 0] },
"$MarksInformation.Marks2"
]
},
"Marks3": {
$cond: [
{ $eq: [{ $ifNull: ["$MarksInformation.Marks3", 0] }, 0] },
{ $ifNull: ["$MarksInformation.Score3", 0] },
"$MarksInformation.Marks3"
]
}
}
}
]);
3. Kết quả: Cấu trúc thống nhất Marks1, Marks2, Marks3
{ "_id": ObjectId("..."), "Name": "Chris", "Marks1": 67, "Marks2": 45, "Marks3": 78 }
{ "_id": ObjectId("..."), "Name": "David", "Marks1": 50, "Marks2": 57, "Marks3": 68 }
{ "_id": ObjectId("..."), "Name": "Bob", "Marks1": 65, "Marks2": 71, "Marks3": 0 }
Giải thích logic fallback:
$ifNull: [field, default]: Trả về field nếu không null/missing, ngược lại default.- Kiểm tra
Marks1có tồn tại và ≠ 0 không ($eq: [..., 0]). Nếu thiếu/bằng 0 -> fallback sangScore1. - Bob không có Marks3, Score3 -> fallback 0.
Cách hiện đại (MongoDB 5.0+): Dù $coalesce
Marks1: { $coalesce: ["$MarksInformation.Marks1", "$MarksInformation.Score1", 0] }
$coalesce trả về giá trị đầu tiên non-null trong danh sách - ngắn gọn, dễ đọc hơn $cond + $ifNull lồng nhau.