Sử dụng aggregate() với hai stage $group để tính tổng điểm nhóm theo cả id và Name, xử lý trường hợp ID trùng nhưng Name khác nhau.
Tạo collection dữ liệu điểm
> db.demo627.insertOne({id:101, "Name":"Chris", "Marks":54});
> db.demo627.insertOne({id:102, "Name":"Bob", "Marks":74});
> db.demo627.insertOne({id:101, "Name":"Chris", "Marks":87});
> db.demo627.insertOne({id:102, "Name":"Mike", "Marks":70});
Hiển thị dữ liệu gốc
> db.demo627.find();
Kết quả (4 document, id 101 và 102 mỗi cái 2 document):
id:101 Chris 54 | id:102 Bob 74 | id:101 Chris 87 | id:102 Mike 70
Pipeline aggregate 2 cấp $group
> db.demo627.aggregate([
{
"$group": {
"_id": { "id": "$id", "Name": "$Name" },
"Marks": { "$sum": "$Marks" }
}
},
{
"$group": {
"_id": { "id": "$_id.id", "Name": "$_id.Name" },
"TotalMarks": { "$sum": "$Marks" }
}
}
], { "allowDiskUse": true });
Kết quả:
{
"_id" : { "id" : 101, "Name" : "Chris" }, "TotalMarks" : 141
}
{
"_id" : { "id" : 102, "Name" : "Bob" }, "TotalMarks" : 74
}
{
"_id" : { "id" : 102, "Name" : "Mike" }, "TotalMarks" : 70
}
Giải thích:
- $group đầu tiên: Gom theo cặp (id, Name) → tính tổng Marks cho mỗi cặp. Kết quả trung gian: (101,Chris)=141, (102,Bob)=74, (102,Mike)=70
- $group thứ hai: Gom lại theo (id, Name) từ kết quả trung gian → tổng Marks (trong trường hợp này mỗi cặp chỉ có 1 record trung gian nên kết quả không đổi)
Tại sao cần 2 $group? Pattern này dùng khi cần tổng hợp đa cấp, ví dụ: trước gom theo (id, Name, Subject), sau gom theo (id, Name) để tính tổng tất cả môn. Ví dụ này đơn giản hóa để demo cú pháp.
allowDiskUse: true cho phép dùng disk tạm khi dữ liệu lớn vượt giới hạn bộ nhớ (100MB).