Toán tử $inc trong MongoDB
Xem thửTình huống mở đầu
Trang chi tiết đơn hàng đếm số lần mở trang bằng field views. Đơn 1 đang có views là 10. Một buổi tối có hai request đến gần như cùng lúc. Mỗi request chạy đoạn code quen thuộc: đọc views về, cộng 1 trong ứng dụng, rồi ghi lại bằng $set. Sáng hôm sau bạn đối chiếu log: có đúng hai lượt mở trang, nhưng views chỉ tăng từ 10 lên 11.
Hãy đoán trước khi đọc tiếp: lượt xem thứ hai biến mất ở bước nào, và có cách nào cộng mà không cần đọc giá trị cũ không? Cuối bài bạn sẽ có lệnh đó.
Ôn nhanh bài trước: $min: { low: 700000 } trên field low đang là null cho kết quả gì?
Đáp án: low vẫn là null, vì null nhỏ hơn mọi số nên $min không bao giờ thắng được. Bài này cũng làm việc với field null, và kết quả còn nặng hơn: không phải bị bỏ qua mà là lỗi.
Mục tiêu bài học
Sau bài này bạn có thể:
- Viết
$incđể cộng hoặc trừ một hay nhiều field trong cùng một lệnh. - Dự đoán kết quả khi field thiếu, null, hay không phải số.
- Giải thích vì sao
$incan toàn hơn đọc về client, cộng rồi$set. - Dự đoán kiểu số của kết quả (int, long, double, decimal) khi cộng hai kiểu khác nhau hoặc khi tràn.
Bức tranh lớn
Quay lại sổ đơn hàng orders và bút cộng của chương: $inc không hỏi giá trị cũ là bao nhiêu, nó cộng thêm một số ngay trên phiếu, giống như bạn đặt thêm vào ngăn kéo thay vì đếm lại cả ngăn. Dự án xuyên suốt vẫn là sổ đơn hàng với năm đơn. Đơn 4 không có views, đơn 5 có views là null:
db.orders.insertMany([
{ _id: 1, item: "keyboard", qty: NumberInt(2), price: NumberInt(450000), status: "new", views: NumberInt(10), createdAt: ISODate("2026-03-01T08:00:00Z") },
{ _id: 2, item: "mouse", qty: NumberInt(3), price: NumberInt(220000), status: "paid", views: NumberInt(4), createdAt: ISODate("2026-03-02T09:30:00Z") },
{ _id: 3, item: "monitor", qty: NumberInt(1), price: NumberInt(3200000), status: "shipped", views: NumberInt(0), createdAt: ISODate("2026-03-05T14:00:00Z") },
{ _id: 4, item: "cable", qty: NumberInt(10), price: NumberInt(35000), status: "new", createdAt: ISODate("2026-03-06T10:00:00Z") },
{ _id: 5, item: "headset", qty: NumberInt(1), price: NumberInt(780000), status: "new", views: null, createdAt: ISODate("2026-03-07T11:00:00Z") }
])
Hình dưới trả lời một câu hỏi: server làm gì khi gặp $inc?
Đang tải sơ đồ…
Cách đọc hình: đọc từ ô xanh dương xuống. Field chưa có thì lệnh tạo nó với đúng giá trị n (ô tím), giống như cộng vào 0. Field đã có thì server kiểm tra giá trị đang lưu có phải số không. Là số thì cộng (ô xanh lá), không phải số thì báo lỗi code 14 và không ghi gì (ô đỏ). Null cũng là một giá trị không phải số.
Chỗ ẩn dụ không còn đúng: ngăn kéo thật có thể để trống, còn field null trong MongoDB là một giá trị có thật, nên $inc coi nó là sai kiểu.
Khái niệm cốt lõi
Bài này có ba khái niệm.
1. Cộng ngay trong server
{ $inc: { field: n } } cộng n vào giá trị đang lưu. n âm thì trừ. Một lệnh có thể cộng nhiều field với nhiều số khác nhau. Server đọc, cộng và ghi trong một bước atomic trên document, nên hai request cùng gọi $inc không bao giờ dẫm lên nhau.
2. Thiếu thì tạo, sai kiểu thì lỗi
Field chưa có: server tạo field với giá trị n (như cộng vào 0). Field đang là số: cộng bình thường. Field đang là null, chuỗi, ngày hay bất kỳ kiểu không phải số: lỗi code 14. Đối số n cũng phải là số; "1" hay null bị từ chối.
3. Kiểu của kết quả
Kết quả mang kiểu rộng hơn trong hai toán hạng: int cộng int ra int, nhưng tràn int thì nâng lên long; int cộng double ra double; có decimal thì ra decimal. Số gõ trực tiếp trong mongo shell luôn là double, nên { $inc: { views: 1 } } biến field int thành double. Dùng NumberInt(1) để giữ int.
Ví dụ đầu tiên
Ca đơn giản nhất: thêm một lượt xem cho đơn 1.
db.orders.updateOne({ _id: 1 }, { $inc: { views: 1 } })
db.orders.findOne({ _id: 1 }, { views: 1 })
Dự đoán trước khi xem kết quả: views bao nhiêu?
Kết quả [Đã chạy: MongoDB 4.4.19, mongo shell 4.4.19]:
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 1, "views" : 11 }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
{ _id: 1 } | Chọn đơn 1 | Lọc theo _id đảm bảo đúng một document |
{ $inc: { views: 1 } } | Cộng 1 vào views, từ 10 lên 11 | Không cần đọc 10 về client trước |
"modifiedCount" : 1 | Một document đổi | Cộng một số khác 0 luôn làm document đổi |
findOne(..., { views: 1 }) | Đọc lại để kiểm tra | views bây giờ là 11 |
Một lệnh cộng nhiều field, có cả số âm:
db.orders.updateOne({ _id: 2 }, { $inc: { qty: 2, price: -10000, views: 1 } }) // số dương cộng, số âm trừ, mỗi field một phép
db.orders.findOne({ _id: 2 }, { qty: 1, price: 1, views: 1 })
Kết quả [Đã chạy: MongoDB 4.4.19]:
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 2, "qty" : 5, "price" : 210000, "views" : 5 }
Đơn 2 từ 3 lên 5 cho qty, từ 220000 xuống 210000 cho price, và từ 4 lên 5 cho views. Số âm trừ, số dương cộng, mỗi field một phép.
Bây giờ đến tình huống mở đầu, mô phỏng bằng tay hai request xen kẽ nhau:
function readViews() { return db.orders.findOne({ _id: 1 }).views }
var seenByA = readViews() // request A đọc 10
var seenByB = readViews() // request B đọc 10, trước khi A kịp ghi
db.orders.updateOne({ _id: 1 }, { $set: { views: seenByA + 1 } }) // A ghi 11
db.orders.updateOne({ _id: 1 }, { $set: { views: seenByB + 1 } }) // B cũng ghi 11
print("views:", readViews())
Kết quả [Đã chạy: MongoDB 4.4.19, hai request được mô phỏng xen kẽ trong một script]:
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }
views: 11
Cả hai request đều đọc 10 trước khi ai ghi, nên cả hai đều ghi 11. Dùng $inc thì không có bước đọc nào để lỗi thời:
db.orders.updateOne({ _id: 1 }, { $inc: { views: 1 } }) // A
db.orders.updateOne({ _id: 1 }, { $inc: { views: 1 } }) // B
print("views:", db.orders.findOne({ _id: 1 }).views)
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
views: 12
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
readViews() hai lần trước khi ghi | Mô phỏng hai request cùng đọc giá trị cũ | Đây là khoảng hở của đọc rồi ghi lại |
$set: { views: seenByB + 1 } | Ghi 11 dựa trên giá trị đã cũ | $set không biết A vừa ghi |
$inc: { views: 1 } hai lần | Cộng 1 hai lần nối tiếp trên giá trị hiện tại | Việc cộng nằm trong server, nên kết quả luôn là 12 |
Hai request trong ví dụ là mô phỏng tuần tự; việc hai request thật chạy trùng thời điểm thì [Có thể khác nhau] theo thời gian chạy. Nhưng $inc cho kết quả đúng ở mọi thứ tự đến.
Hai trường hợp field không phải là số. Đơn 4 không có views:
db.orders.updateOne({ _id: 4 }, { $inc: { views: 1 } })
db.orders.findOne({ _id: 4 }, { item: 1, views: 1 })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 4, "item" : "cable", "views" : 1 }
Field được tạo với giá trị 1. Đơn 5 có views là null:
try { db.orders.updateOne({ _id: 5 }, { $inc: { views: 1 } }) } catch (e) { print("code " + e.code + ": " + e.message) }
db.orders.findOne({ _id: 5 }, { views: 1 })
code 14: Cannot apply $inc to a value of non-numeric type. {_id: 5.0} has the field 'views' of non-numeric type null
{ "_id" : 5, "views" : null }
Lệnh bị từ chối và đơn 5 không đổi. Cộng vào null là lỗi chứ không phải bỏ qua.
Xem từng bước
Animation dưới đây chạy lại tình huống mở đầu với hai request, trước bằng đọc rồi $set, sau bằng $inc. Hãy trả lời câu dự đoán trước khi bấm bước đầu tiên.
Đang tải Animation…
Bảng trace bằng chữ [Đã chạy: MongoDB 4.4.19, hai request được mô phỏng xen kẽ]:
| Bước | Đọc rồi $set | $inc |
|---|---|---|
| Hai request cùng đọc | Cả hai thấy 10 | Không đọc |
| Request A | Ghi 11 | Cộng 1, views là 11 |
| Request B | Ghi 11 (đè A) | Cộng 1, views là 12 |
| Kết quả | views là 11, mất một lượt | views là 12, đủ cả hai |
Bạn vừa làm được: giải thích vì sao đọc rồi ghi lại làm mất lượt đếm, và dùng $inc để cộng trực tiếp trong server.
Concept Check
Dùng sổ năm đơn ở trên, mỗi câu chạy trên dữ liệu mới nạp.
Câu 1. updateOne({ _id: 1 }, { $inc: { views: -3 } }) khi views đang là 10 cho kết quả nào?
- A.
viewslà 7, vì số âm được trừ. - B.
viewslà 13, vì$incchỉ cộng. - C. Lỗi, vì
$inckhông nhận số âm. - D.
viewslà 10, vì số âm bị bỏ qua.
Câu 2. updateOne({ _id: 4 }, { $inc: { views: 1 } }) khi đơn 4 chưa có views cho kết quả nào?
- A. Lỗi, vì không có giá trị để cộng.
- B. Không đổi gì, vì không có field.
- C. Tạo
viewsvới giá trị 1. - D. Tạo
viewslà null.
Câu 3. updateOne({ _id: 5 }, { $inc: { views: 1 } }) khi views của đơn 5 là null cho kết quả nào?
- A.
viewsthành 1, vì null coi như 0. - B. Lỗi code 14, vì null không phải số.
- C. Không đổi gì và không báo lỗi.
- D.
viewsvẫn là null,modifiedCountlà 1.
Đáp án và giải thích:
- Câu 1: A. Chạy thật cho 7. B, C và D bịa ra quy tắc chỉ cộng hoặc bỏ qua số âm, trong khi số âm là cách chính thức để trừ.
- Câu 2: C. Chạy thật cho
viewsbằng 1. A giả định cộng vào field thiếu là sai, B nhầm với$settrên field không tồn tại, D bịa ra giá trị null mặc định. - Câu 3: B. Chạy thật cho lỗi code 14 nói rõ field
viewscó kiểu non-numeric type null. A và C đoán null được coi như 0 hoặc bị bỏ qua; D bịa ra kết quả mâu thuẫn, vì lệnh lỗi thì không ghi gì.
Làm cùng mình
Cửa hàng nhận thêm 2 mouse vào đơn 2, giảm giá 10000 trên giá mỗi chiếc, và ghi thêm một lượt xem. Đơn 2 đang có qty 3, price 220000, views 4.
Bước 1. Vẽ ra giấy
Vẽ phiếu đơn 2 với ba field. Dự đoán giá trị mới của từng field sau lệnh bạn sắp viết.
Bước 2. Viết lệnh
db.orders.updateOne(
{ _id: 2 },
{
$inc: {
// TODO 1: qty tăng thêm 2
// TODO 2: price giảm đi 10000
// TODO 3: views tăng thêm 1
}
}
)
db.orders.findOne({ _id: 2 }, { item: 1, qty: 1, price: 1, views: 1 })
Dự đoán trước: modifiedCount là bao nhiêu khi có ba field đổi?
Bước 3. Chạy ba phép cộng khác nhau
Trên đơn 1 (views 10), chạy lần lượt NumberInt(-3), NumberInt(0) và 1.5, sau mỗi lệnh in modifiedCount và views. Dự đoán trước: lệnh nào cho modifiedCount bằng 0?
Bước 4. Vẽ lại ra giấy
Vẽ lại giá trị views sau ba phép cộng. Ghi một câu: vì sao cộng 0 không làm document đổi.
Thử phá nó đi
Sáu lỗi người mới hay gặp [Đã chạy: MongoDB 4.4.19]:
| Code lỗi | Kết quả thật | Nguyên nhân | Cách sửa |
|---|---|---|---|
{ $inc: { views: 1 } } trên views null | code 14: Cannot apply $inc to a value of non-numeric type. {_id: 5.0} has the field 'views' of non-numeric type null | Null không phải số | Gieo giá trị đầu bằng filter views: null và $set: { views: 0 } |
{ $inc: { views: "1" } } | code 14: Cannot increment with non-numeric argument: {views: "1"} | Đối số là chuỗi | Gửi số, đổi kiểu ở ứng dụng trước |
{ $inc: { views: null } } | code 14: Cannot increment with non-numeric argument: {views: null} | Đối số null | Bỏ field khỏi $inc khi không có số để cộng |
{ $inc: { status: 1 } } trên chuỗi | code 14: Cannot apply $inc to a value of non-numeric type. {_id: 1.0} has the field 'status' of non-numeric type string | Field đang chứa chuỗi | Dùng đúng field số |
{ $inc: { createdAt: 1 } } trên ngày | code 14: Cannot apply $inc to a value of non-numeric type. {_id: 1.0} has the field 'createdAt' of non-numeric type date | Ngày không phải số | Tính ngày bằng pipeline update |
{ $inc: { views: 1 }, $set: { views: 0 } } | code 40: Updating the path 'views' would create a conflict at 'views' | Hai toán tử cùng đường dẫn | Chỉ một toán tử cho mỗi field |
Cách đọc kết quả: lỗi có dạng code N: thông báo. Đây là các lệnh bị server từ chối nên document không đổi gì.
Một bẫy không báo lỗi: dùng 1 trong shell. Số 1 gõ tay là double, nên { $inc: { views: 1 } } trên đơn 4 cho m1 mod1 1 : double, kiểu double, còn NumberInt(1) cho m1 mod1 1 : int, kiểu int. Chuỗi kết quả m1 mod1 nghĩa là matchedCount 1 và modifiedCount 1.
Tự tay làm
Dùng chung sổ năm đơn. Mỗi bài nêu đầu vào, kết quả mong đợi và tiêu chí đạt.
Cơ bản
Bài 1. Điền bảng dự đoán. Đơn 1 có views là 10. Với mỗi lệnh riêng lẻ trên dữ liệu mới nạp, điền views sau lệnh và modifiedCount:
| Lệnh | views sau | modifiedCount |
|---|---|---|
{ $inc: { views: NumberInt(-3) } } | ? | ? |
{ $inc: { views: NumberInt(0) } } | ? | ? |
{ $inc: { views: 1.5 } } | ? | ? |
Tiêu chí đạt: đúng cả sáu ô. Gợi ý: cộng 0 cùng kiểu không đổi giá trị nên cũng không đổi document.
Vận dụng
Bài 2. Đặt thêm hàng. Đầu vào: đơn 2. Viết một updateOne dùng $inc tăng qty thêm 2, giảm price 10000 và tăng views thêm 1. Tiêu chí đạt: qty 5, price 210000, views 5, và đúng một lệnh. Gợi ý: nhiều field trong cùng một $inc.
Thử thách (tùy chọn)
Bài 3. Đếm an toàn cho cả sổ. Đầu vào: cả năm đơn, trong đó đơn 4 thiếu views và đơn 5 có views null. Viết hai lệnh: lệnh đầu đặt views bằng 0 cho mọi đơn có views là null hoặc thiếu, lệnh sau cộng 1 vào views của cả năm đơn. Tiêu chí đạt: views cuối cùng của năm đơn là 11, 5, 1, 1, 1 và lệnh sau có modifiedCount bằng 5. Gợi ý: filter { views: null } khớp cả field null lẫn field thiếu.
Tự kiểm tra
Trả lời bằng lời của bạn, không nhìn lại bài.
- (Mục tiêu 1, nhớ lại)
$inclàm gì với số âm, và với field thiếu? - (Mục tiêu 2, dự đoán)
viewsđang là null.$incvới 1 cho kết quả gì? - (Mục tiêu 3, giải thích) Vì sao hai request cùng đọc rồi
$setcó thể làm mất một lượt đếm, còn$incthì không? - (Mục tiêu 4, so sánh)
viewsđang là int 10,{ $inc: { views: 1 } }gõ trong shell cho kiểu nào, vàNumberInt(1)thì sao? - (Mục tiêu 2, tìm lỗi)
{ $inc: { views: "1" } }bị lỗi gì, và sửa thế nào?
Điều cần nhớ
$inccộngnvào field ngay trong server, số âm thì trừ, nhiều field trong một lệnh; cộng 0 cùng kiểu không đổi document.- Field thiếu thì được tạo với giá trị
n; field null hoặc không phải số thì lỗi code 14; đối số cũng phải là số. $incan toàn cho bộ đếm vì server cộng nối tiếp, khác với đọc về client rồi$set.- Kiểu kết quả theo kiểu rộng hơn: int tràn thành long, int cộng double ra double; số gõ trong shell là double.
Cheat sheet [Đã chạy: mỗi dòng là một lần chạy thật trên dữ liệu mới nạp; m1 mod1 là matchedCount 1, modifiedCount 1]:
| Muốn | Viết | Kết quả |
|---|---|---|
| Cộng | { $inc: { views: 5 } } trên đơn 1 | m1 mod1 15 : double |
| Trừ | { $inc: { views: -3 } } trên đơn 1 | m1 mod1 7 : double |
| Cộng 0 cùng kiểu | { $inc: { views: NumberInt(0) } } trên đơn 1 | m1 mod0 10 : int |
| Field thiếu | { $inc: { views: NumberInt(1) } } trên đơn 4 | m1 mod1 1 : int |
| Upsert document mới | { $inc: { views: 1 } } với upsert: true | m0 mod0 up 1 : double |
Hình tóm tắt của bài là sơ đồ ở mục Bức tranh lớn.
Câu hỏi để dành cho bài sau: muốn giảm giá cả sổ đi 10 phần trăm thì cộng một số âm có đủ không, hay cần một toán tử khác?
Đào sâu
Phần này dành cho bạn tò mò.
- Cơ chế bên trong: tràn int. Cộng hai int mà kết quả vượt giới hạn int thì kết quả nâng lên long [Đã chạy: MongoDB 4.4.19]:
function show(id) { printjsononeline(db.orders.aggregate([{ $match: { _id: id } }, { $project: { views: 1, type: { $type: "$views" } } }]).toArray()[0]) }
db.orders.updateOne({ _id: 3 }, { $inc: { views: NumberInt(2147483647) } }) // 0 cộng giá trị lớn nhất của int
show(3)
db.orders.updateOne({ _id: 3 }, { $inc: { views: NumberInt(1) } }) // vượt giới hạn int
show(3)
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 3, "views" : 2147483647, "type" : "int" }
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 3, "views" : NumberLong("2147483648"), "type" : "long" }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
$inc: { views: NumberInt(2147483647) } | Cộng 2147483647, giá trị lớn nhất của int | Đơn 3 có views 0 nên kết quả vừa khít |
$inc: { views: NumberInt(1) } | Cộng thêm 1, vượt giới hạn int | Server nâng kết quả lên long chứ không quay vòng |
{ $type: "$views" } | Trả tên kiểu BSON của views | Cho thấy int đổi thành long |
Một case khác: int 10 cộng NumberInt(2147483647) cho m1 mod1 NumberLong("2147483657") : long. Long cộng int ra long (m1 mod1 NumberLong(9) : long với NumberLong(5)), và decimal làm kết quả thành decimal (m1 mod1 NumberDecimal("10.5") : decimal).
- Cộng 0 và kiểu. Cộng 0 chỉ giữ nguyên document khi kiểu không đổi [Đã chạy: MongoDB 4.4.19]:
{ $inc: { views: NumberInt(0) } }trênviewsint chom1 mod0 10 : int, còn{ $inc: { views: 0 } }gõ trần chom1 mod1 10 : doublevì0là double nênviewsđổi từ int sang double. - Số thực.
$inccộng double thì mang sai số như mọi double [Đã chạy: MongoDB 4.4.19]:
db.orders.updateOne({ _id: 1 }, { $set: { score: 0.1 } })
db.orders.updateOne({ _id: 1 }, { $inc: { score: 0.2 } })
print("score:", db.orders.findOne({ _id: 1 }).score)
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
score: 0.30000000000000004
Với tiền hoặc số cần chính xác, lưu decimal [Đã chạy: MongoDB 4.4.19]:
db.orders.updateOne({ _id: 1 }, { $set: { score: NumberDecimal("0.1") } })
db.orders.updateOne({ _id: 1 }, { $inc: { score: NumberDecimal("0.2") } })
print("score:", tojson(db.orders.findOne({ _id: 1 }).score))
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
score: NumberDecimal("0.3")
- Trả về giá trị sau khi cộng. Muốn cộng và biết ngay giá trị mới (ví dụ để cấp số thứ tự), dùng
findOneAndUpdatevớireturnNewDocument: true[Đã chạy: MongoDB 4.4.19]:
var doc = db.orders.findOneAndUpdate({ _id: 1 }, { $inc: { views: 1 } }, { returnNewDocument: true })
print("new views:", doc.views)
new views: 11
- Cộng từ field khác.
$incchỉ cộng một hằng số. Muốnviewscộng thêmqtycủa chính document, dùng update dạng pipeline (từ MongoDB 4.2) [Đã chạy: MongoDB 4.4.19]:
db.orders.updateOne({ _id: 1 }, [{ $set: { views: { $add: ["$views", "$qty"] } } }])
db.orders.findOne({ _id: 1 }, { views: 1, qty: 1 })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 1, "qty" : 2, "views" : 12 }
- Giới hạn của bài. Bài này chưa nói đến
$inccho phần tử trong mảng bằng$[]hayarrayFilters, và chưa nói đến chiến lược bộ đếm có tải rất cao (phân mảnh bộ đếm thành nhiều document). Tính chịu lỗi khi hai request thật chạy trùng thời điểm [Suy luận] dựa trên tài liệu chính thức: ghi trên một document là atomic. - Đọc thêm. MongoDB Manual 4.4,
$inc(update): https://www.mongodb.com/docs/v4.4/reference/operator/update/inc/ và Atomicity and Transactions: https://www.mongodb.com/docs/v4.4/core/write-operations-atomicity/
Bài tiếp theo
Bài sau là $mul: nhân field với một số ngay trong server, thứ bạn cần để giảm giá hàng loạt. Mini-dự án nối tiếp: giảm 10 phần trăm cho các đơn đang ở trạng thái new.
Lời giải và đáp án
Làm cùng mình, Bước 2. Ba dòng cần điền:
qty: 2,
price: -10000,
views: 1
db.orders.updateOne({ _id: 2 }, { $inc: { qty: 2, price: -10000, views: 1 } })
db.orders.findOne({ _id: 2 }, { item: 1, qty: 1, price: 1, views: 1 })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
{ "_id" : 2, "item" : "mouse", "qty" : 5, "price" : 210000, "views" : 5 }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
qty: 2 | Cộng 2 vào qty, từ 3 lên 5 | Số dương cộng thêm |
price: -10000 | Cộng -10000 vào price, từ 220000 xuống 210000 | $inc không có phép trừ riêng, trừ là cộng số âm |
views: 1 | Cộng 1 vào views, từ 4 lên 5 | Cùng một lệnh, ba field |
modifiedCount là 1, vì đơn vị đếm là document chứ không phải field.
Bước 3.
function show(label, res) { print(label, "modified:", res.modifiedCount, "views:", db.orders.findOne({ _id: 1 }).views) }
show("minus 3", db.orders.updateOne({ _id: 1 }, { $inc: { views: NumberInt(-3) } }))
show("zero", db.orders.updateOne({ _id: 1 }, { $inc: { views: NumberInt(0) } }))
show("1.5", db.orders.updateOne({ _id: 1 }, { $inc: { views: 1.5 } }))
minus 3 modified: 1 views: 7
zero modified: 0 views: 7
1.5 modified: 1 views: 8.5
Cộng -3 đưa 10 về 7, cộng NumberInt(0) giữ 7 và modifiedCount là 0, cộng 1.5 đưa 7 lên 8.5. Cộng 0 cùng kiểu không làm giá trị đổi nên document không đổi.
Bài 1. NumberInt(-3): views là 7, modifiedCount 1. NumberInt(0): views vẫn là 10, modifiedCount 0. 1.5: views là 11.5, modifiedCount 1. Nếu bạn viết 0 trần trong shell thì đó là double, và modifiedCount lại là 1 vì kiểu của views đổi từ int sang double (xem Đào sâu).
Bài 2. Lời giải là đoạn ở Bước 2: qty 5, price 210000, views 5.
Bài 3.
var fixed = db.orders.updateMany({ views: null }, { $set: { views: NumberInt(0) } }) // views: null khớp cả null lẫn field thiếu
print("fixed:", fixed.modifiedCount)
var res = db.orders.updateMany({}, { $inc: { views: 1 } })
print("matched:", res.matchedCount, "modified:", res.modifiedCount)
db.orders.find({}, { views: 1 }).forEach(printjsononeline)
fixed: 2
matched: 5 modified: 5
{ "_id" : 1, "views" : 11 }
{ "_id" : 2, "views" : 5 }
{ "_id" : 3, "views" : 1 }
{ "_id" : 4, "views" : 1 }
{ "_id" : 5, "views" : 1 }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
updateMany({ views: null }, { $set: { views: NumberInt(0) } }) | Đặt views bằng 0 cho đơn 4 và 5 | Filter so với null khớp cả field null lẫn field thiếu |
updateMany({}, { $inc: { views: 1 } }) | Cộng 1 vào cả năm đơn | Không đơn nào còn null, nên không có lỗi code 14 |
Đơn 4 và đơn 5 sau bước đầu đều có views là 0, nên bước sau cho 1 ở cả hai.
Tự kiểm tra.
- Số âm thì trừ; field thiếu thì được tạo với giá trị bằng số cộng thêm.
- Lỗi code 14, vì null không phải số, và document không đổi.
- Đọc rồi
$setcho phép hai request cùng dựa trên một giá trị cũ, nên lượt cộng của request này bị ghi đè;$inccộng nối tiếp trong server trên giá trị hiện tại. - Shell gõ
1là double nên kết quả là double;NumberInt(1)giữ kiểu int. - Đối số là chuỗi nên server từ chối với lỗi code 14; sửa bằng cách gửi số
1.