Phương thức skip() trong MongoDB
Xem thửTình huống mở đầu
Trang danh sách của cửa hàng linh kiện hiện 3 sản phẩm mỗi trang, sắp theo giá giảm dần. Khách Lan mở trang 1 và thấy monitor, headset, webcam. Trong lúc chị đọc mô tả webcam, nhân viên thêm một máy tính bảng giá 5 triệu đồng. Chị bấm "Trang 2" và thấy webcam một lần nữa, ngay đầu trang, dù chị vừa đọc xong nó ở trang 1.
Hãy đoán trước: sản phẩm bị lặp là do lỗi của server, hay do cách bạn xin trang 2? Cuối bài bạn sẽ có hai cách xin trang 2, một cách bị lặp và một cách không.
Ôn nhanh bài trước: find().limit(0) trả về bao nhiêu document?
Đáp án: tất cả, vì 0 nghĩa là không giới hạn. Bài này đi cùng limit để cắt một trang ở giữa danh sách.
Mục tiêu bài học
Sau bài này bạn có thể:
- Dùng
skip(n)cùngsortvàlimitđể lấy đúng trang thứ n, với công thức skip bằng số trang trừ 1 rồi nhân với kích thước trang. - Giải thích vì sao trang kế tiếp bị lặp hoặc sót khi dữ liệu đổi giữa hai lần bấm.
- Đọc
explainđể thấy chi phí củaskiptăng theo số document bị bỏ qua. - Viết phân trang bằng bộ lọc theo giá trị cuối trang trước, và biết khi nào chỉ cần
skip.
Bức tranh lớn
Xuyên suốt bài, danh sách sản phẩm là một hàng dài thùng xếp theo giá, skip là bước đi qua n thùng đầu mà không nhìn vào bên trong, và limit là chiếc túi chứa đúng n thùng tiếp theo. Dự án xuyên suốt vẫn là kho linh kiện của database shop.
Hình dưới trả lời một câu hỏi: từ số trang, server làm những gì để ra đúng ba sản phẩm?
Đang tải sơ đồ…
Cách đọc hình: đọc từ trên xuống. Ô xanh dương là yêu cầu của người dùng. Hai ô tím là hai việc server làm: tính số cần bỏ qua rồi bước qua chúng. Ô xanh lá là kết quả. Điểm cần để ý là ô tím thứ hai: server vẫn phải đi qua 6 mục, chỉ là không trả chúng về. Màu chỉ là tín hiệu phụ, chữ trong ô mới là nội dung chính.
Chỗ ẩn dụ không còn đúng: người đi qua thùng thật nhìn được số thùng bằng một cái liếc, còn server phải đếm từng mục.
Khái niệm cốt lõi
Bài này có ba khái niệm.
1. skip(n)
cursor.skip(n) bỏ n document đầu của kết quả rồi mới trả về phần còn lại. Số n bằng 0 là không bỏ gì. Nếu n lớn hơn tổng số document thì kết quả rỗng và không báo lỗi. Số thập phân bị cắt phần lẻ. Số âm hoặc chuỗi thì báo lỗi.
2. Một trang là sort, skip, limit
Để lấy trang n với kích thước s, bạn dùng sort(...).skip((n - 1) * s).limit(s). Thứ tự gọi các hàm không quan trọng vì server luôn sắp xếp, bỏ qua, rồi giới hạn (bài trước đã thấy điều này cho sort và limit). Không có sort thì "n document đầu" không xác định nên các trang có thể chồng lên nhau.
3. Chi phí và phân trang theo giá trị cuối trang
skip bỏ qua theo vị trí, nên server vẫn phải đi qua n mục đó. Trang càng sâu càng chậm. Ngoài ra, vị trí dịch chuyển khi dữ liệu đổi, nên skip có thể làm lặp hoặc sót sản phẩm. Cách khác là nhớ giá trị khóa sort của document cuối trang trước, rồi lọc những document đứng sau nó. Cách này gọi là phân trang theo khóa (keyset pagination), và nó không dùng skip.
Ví dụ đầu tiên
Ví dụ nhỏ nhất: trang thứ hai của danh sách theo _id, mỗi trang 3 sản phẩm. Gõ trong mongo shell:
use shop
db.products.find({}, { name: 1 }).sort({ _id: 1 }).skip(3).limit(3)
Dự đoán trước khi xem kết quả: các _id nào hiện ra?
Kết quả [Đã chạy: MongoDB 4.4.19, mongo shell 4.4.19]:
{ "_id" : 4, "name" : "cable" }
{ "_id" : 5, "name" : "headset" }
{ "_id" : 6, "name" : "webcam" }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
find({}, { name: 1 }) | Chọn mọi document, chỉ lấy name cùng _id | Chiếu bớt trường để kết quả gọn |
.sort({ _id: 1 }) | Xếp theo _id tăng dần | Cần một thứ tự cố định thì "bỏ qua 3" mới có nghĩa |
.skip(3) | Bỏ 3 document đầu là _id 1, 2, 3 | Trang 1 đã chiếm ba document này |
.limit(3) | Giữ 3 document tiếp theo: _id 4, 5, 6 | Kích thước trang là 3 |
Bây giờ đến trang của chị Lan, sắp theo giá giảm dần. Trang 1 và trang 2 khi dữ liệu đứng yên:
db.products.find({}, { name: 1, price: 1 }).sort({ price: -1 }).skip(0).limit(3)
{ "_id" : 3, "name" : "monitor", "price" : 3200000 }
{ "_id" : 5, "name" : "headset", "price" : 780000 }
{ "_id" : 6, "name" : "webcam", "price" : 650000 }
db.products.find({}, { name: 1, price: 1 }).sort({ price: -1 }).skip(3).limit(3)
{ "_id" : 7, "name" : "speaker", "price" : 520000 }
{ "_id" : 1, "name" : "keyboard", "price" : 450000 }
{ "_id" : 2, "name" : "mouse", "price" : 220000 }
Hai trang nối nhau đúng: monitor, headset, webcam rồi speaker, keyboard, mouse [Đã chạy: MongoDB 4.4.19]. Giờ thêm tablet 5 triệu giữa hai lần bấm, rồi xin lại trang 2:
var r = db.products.insertOne({ _id: 9, name: "tablet", category: "display", price: 5000000, stock: 4 })
db.products.find({}, { name: 1, price: 1 }).sort({ price: -1 }).skip(3).limit(3)
Dự đoán trước khi xem: webcam có xuất hiện lại không?
Kết quả [Đã chạy: MongoDB 4.4.19]:
{ "_id" : 6, "name" : "webcam", "price" : 650000 }
{ "_id" : 7, "name" : "speaker", "price" : 520000 }
{ "_id" : 1, "name" : "keyboard", "price" : 450000 }
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
insertOne({ _id: 9, name: "tablet", ... price: 5000000 ... }) | Thêm một sản phẩm đắt nhất kho | Mô phỏng nhân viên thêm hàng giữa hai lần bấm trang |
.sort({ price: -1 }).skip(3) | Bỏ qua 3 vị trí đầu, bây giờ là tablet, monitor, headset | Lệnh vẫn như cũ, nhưng ba vị trí đầu đã đổi chủ |
| webcam xuất hiện đầu trang 2 | Webcam đã tụt từ vị trí 3 xuống vị trí 4 | skip đếm theo vị trí chứ không biết chị Lan đã xem gì |
Xem từng bước
Animation dưới đây chạy đúng tình huống trên, rồi thử cách lọc theo giá trị cuối trang. 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: kho 8 sản phẩm, sau đó thêm tablet, MongoDB 4.4.19]:
| Lúc | Lệnh | Sản phẩm trả về |
|---|---|---|
| Trước khi thêm tablet | skip(0).limit(3) | monitor, headset, webcam |
| Sau khi thêm tablet | skip(3).limit(3) | webcam, speaker, keyboard |
| Sau khi thêm tablet | find({ price: { $lt: 650000 } }).sort({ price: -1 }).limit(3) | speaker, keyboard, mouse |
Cách thứ ba dùng giá 650000 của webcam, sản phẩm cuối trang 1:
db.products.find({ price: { $lt: 650000 } }, { name: 1, price: 1 }).sort({ price: -1 }).limit(3)
{ "_id" : 7, "name" : "speaker", "price" : 520000 }
{ "_id" : 1, "name" : "keyboard", "price" : 450000 }
{ "_id" : 2, "name" : "mouse", "price" : 220000 }
Bạn vừa làm được: chỉ ra được vì sao skip gây ra lặp trang, và viết lệnh lấy trang kế tiếp không lặp.
Concept Check
Kho có 8 sản phẩm, mỗi trang 3 sản phẩm, sắp theo _id tăng dần.
Câu 1. Lệnh nào lấy trang 3?
- A.
skip(3).limit(3), vì trang 3 bắt đầu sau 3 document. - B.
skip(6).limit(3), vì trang 3 bắt đầu sau hai trang đầy. - C.
skip(9).limit(3), vì mỗi trang đứng cách nhau 3 document. - D.
skip(2).limit(3), vì trang 3 bắt đầu ở vị trí số 2.
Câu 2. skip(100) trên kho 8 sản phẩm cho kết quả gì?
- A. Báo lỗi, vì không thể bỏ qua nhiều document hơn đang có.
- B. Trả về 8 document, vì skip lớn hơn tổng số thì bị bỏ qua.
- C. Trả về 1 document cuối, vì skip dừng khi hết dữ liệu.
- D. Trả về mảng rỗng và không báo lỗi gì cả.
Câu 3. Chị Lan ở trang 1, sau đó có người thêm sản phẩm đắt nhất kho. Vì sao skip(3) cho trang 2 lặp lại một sản phẩm?
- A. Vì
skipđếm theo vị trí, và vị trí đã dịch xuống một nấc. - B. Vì server đã lưu sản phẩm cuối trang 1 trong bộ nhớ đệm.
- C. Vì
limit(3)luôn lấy lại document cuối của lần trước. - D. Vì
sortđảo ngược thứ tự mỗi khi có document mới.
Đáp án và giải thích:
- Câu 1: B. Trang 1 và 2 chiếm 6 document, nên trang 3 bỏ qua 6. A là trang 2, C vượt quá kho, D hiểu sai công thức. Công thức là (3 - 1) nhân 3.
- Câu 2: D. Đã chạy thật:
[]. A tưởng giống ngôn ngữ khác ném lỗi, B và C bịa ra hành vi bù trừ. - Câu 3: A. Đã chạy thật: webcam xuất hiện hai lần. B, C, D bịa ra cơ chế không có.
skipkhông có trí nhớ giữa hai lệnh.
Làm cùng mình
Cửa hàng cần hàm page(n, size) trả về tên các sản phẩm của trang n theo thứ tự _id, rồi một lệnh lấy trang kế tiếp sau sản phẩm _id 6.
Bước 1. Vẽ ra giấy
Kho có _id 1 đến 8. Với size 3, ghi ra giấy các _id của trang 2 và trang 3, và con số skip của mỗi trang.
Bước 2. Viết hàm
Điền chỗ trống:
function page(n, size) {
return db.products.find({}, { name: 1, _id: 0 })
.sort({ _id: 1 })
// TODO 1: bỏ qua (n - 1) * size document
// TODO 2: giới hạn size document
.toArray().map(function (d) { return d.name }).join(", ")
}
print("page 2:", page(2, 3))
print("page 3:", page(3, 3))
Gợi ý cho chỗ trống thứ nhất: .skip((n - 1) * size). Gợi ý cho chỗ trống thứ hai: .limit(size).
Bước 3. Trang kế tiếp không dùng skip
Điền chỗ trống: lấy 3 sản phẩm có _id lớn hơn lastId.
var lastId = 6
// TODO 3: find với bộ lọc _id lớn hơn lastId, sort theo _id, limit 3
Gợi ý: $gt trong bộ lọc. Đáp án ở mục Lời giải và đáp án. Hãy so với bảng bạn vẽ ở Bước 1.
Thử phá nó đi
Ba lỗi người mới hay gặp với skip [Đã chạy: MongoDB 4.4.19]:
| Code lỗi | Thông báo hoặc kết quả | Nguyên nhân | Cách sửa |
|---|---|---|---|
find().skip(-1) | Lỗi mã 2 (BadValue), thông báo nói giá trị phải không âm | skip chỉ nhận số từ 0 trở lên | Tính lại công thức trang, trang 1 phải cho skip bằng 0 |
find().skip("a") | Lỗi mã 9 (FailedToParse) | skip cần số, không nhận chuỗi | Đổi chuỗi sang số trước khi truyền |
find().skip(100) khi kho có 8 sản phẩm | Mảng rỗng, không có lỗi | Bỏ qua nhiều hơn tổng số thì không còn gì | Kiểm tra số trang tối đa trước khi cho khách bấm |
Chạy thật từng lỗi:
try { db.products.find().skip(-1).toArray() } catch (e) { print(e.code) }
2
try { db.products.find().skip("a").toArray() } catch (e) { print(e.code) }
9
print(JSON.stringify(db.products.find().skip(100).toArray()))
[]
Hiểu lầm cần tránh: coi skip là "trang n" chắc chắn. skip là vị trí chứ không phải danh tính, và nó phải đi cùng một sort ổn định.
Còn một câu hỏi: trang sâu thì chậm đến đâu? Hình dưới đặt hai cách phân trang cạnh nhau.
Đang tải sơ đồ…
Cách đọc hình: đọc từ ô cam xuống hai ô con. Ô tím là cách dùng skip: nhảy được tới trang bất kỳ nhưng chi phí tăng theo n và lệch khi dữ liệu đổi. Ô xanh lá là cách lọc theo khóa cuối: chi phí phẳng và ổn định nhưng chỉ đi được trang kế tiếp, không nhảy tới trang 1000. Không có cách nào đúng cho mọi trường hợp.
Con số thật nằm ở kho 20.000 sản phẩm. Lệnh dưới lấy 10 sản phẩm ở cuối kho theo ba cách:
function cost(label, c) {
var s = c.explain("executionStats").executionStats
print(label, "| returned:", s.nReturned, "| keys examined:", s.totalKeysExamined, "| docs examined:", s.totalDocsExamined)
}
cost("skip only ", db.products.find().skip(19990).limit(10))
cost("sort + skip ", db.products.find().sort({ _id: 1 }).skip(19990).limit(10))
cost("range filter", db.products.find({ _id: { $gt: 19990 } }).sort({ _id: 1 }).limit(10))
Kết quả [Đã chạy: MongoDB 4.4.19]. Số lượng đã đọc là số xác định, còn thời gian chạy thì không in ra vì nó khác nhau mỗi lần:
skip only | returned: 10 | keys examined: 0 | docs examined: 20000
sort + skip | returned: 10 | keys examined: 20000 | docs examined: 10
range filter | returned: 10 | keys examined: 10 | docs examined: 10
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
skip(19990).limit(10) không sort | Quét cả collection, đếm 20000 document rồi bỏ 19990 | Không có sort nên không dùng được chỉ mục |
sort({ _id: 1 }).skip(19990) | Đi qua 20000 khóa chỉ mục _id nhưng chỉ lấy 10 document đầy đủ | Chỉ mục _id giúp tránh đọc document, nhưng vẫn phải đếm hết khóa |
find({ _id: { $gt: 19990 } }) | Nhảy thẳng tới khóa 19991 trong chỉ mục, đọc 10 khóa và 10 document | Bộ lọc dùng chỉ mục để đến đúng chỗ, không đếm từ đầu |
Tự tay làm
Dùng chung kho 8 sản phẩm. 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. Giá trị lạ của skip. Đầu vào: kho 8 sản phẩm theo _id tăng dần. In ba dòng: số document của skip(0), _id của document đầu tiên của skip(2.7), số document của skip(100). Tiêu chí đạt: in 8, 3, 0. Gợi ý: itcount() đếm số document còn lại trong cursor.
Vận dụng
Bài 2. Duyệt cả kho không dùng skip. Đầu vào: kho 8 sản phẩm. Viết vòng lặp lấy từng trang 3 sản phẩm theo _id bằng bộ lọc $gt trên _id của sản phẩm cuối trang trước, in từng trang, dừng khi hết. Tiêu chí đạt: in ba trang, trang cuối chỉ có 2 sản phẩm. Gợi ý: biến last bắt đầu bằng 0, cập nhật sau mỗi trang.
Thử thách (tùy chọn)
Bài 3. Sản phẩm bị sót. Đầu vào: kho 8 sản phẩm sắp theo giá giảm dần, trang 1 là monitor, headset, webcam. Nhân viên xóa headset, rồi khách xin trang 2 bằng skip(3).limit(3). Tiêu chí đạt: nêu đúng sản phẩm bị sót và giải thích bằng vị trí. Gợi ý: viết danh sách đầy đủ sau khi xóa, rồi đánh số vị trí.
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) Công thức skip cho trang n với kích thước trang s là gì?
- (Mục tiêu 1, dự đoán)
find().sort({ _id: 1 }).skip(6).limit(3)trên kho 8 sản phẩm trả về bao nhiêu document? - (Mục tiêu 2, giải thích) Vì sao trang kế tiếp có thể lặp một sản phẩm khi dùng
skip? - (Mục tiêu 3, so sánh) Với
sort({ _id: 1 }), vì saoskip(19990)vẫn chậm dù chỉ lấy 10 document? - (Mục tiêu 4, tìm lỗi) Trang danh sách dùng
skip((n - 1) * 10)nhưng không cósort. Điều gì có thể xảy ra?
Điều cần nhớ
skip(n)bỏ n document đầu, số âm hoặc chuỗi báo lỗi, số thập phân bị cắt phần lẻ, skip quá lớn cho kết quả rỗng.- Trang n là
sort(...).skip((n - 1) * s).limit(s), vàsortphải ổn định. skipđếm theo vị trí nên lặp hoặc sót khi dữ liệu đổi giữa hai lần bấm, và chi phí tăng theo số document bị bỏ.- Phân trang theo khóa bằng bộ lọc
$gthoặc$lttrên giá trị cuối trang giữ chi phí phẳng, nhưng không nhảy được tới trang bất kỳ.
Cheat sheet:
| Muốn | Viết | Kết quả |
|---|---|---|
| Bỏ 3 document đầu | find().sort({ _id: 1 }).skip(3) | Các document từ vị trí thứ 4 trở đi |
| Trang n, kích thước s | .skip((n - 1) * s).limit(s) | Đúng trang n |
| Trang kế tiếp theo khóa | find({ _id: { $gt: last } }).sort({ _id: 1 }).limit(s) | s document sau last |
| Đếm có tính skip | cursor.count(true) | Số document sau skip và limit |
Hình tóm tắt của bài là hai sơ đồ trong bài: các bước của một trang, và hai cách tìm vị trí của trang kế tiếp.
Câu hỏi để dành cho ngày mai: phân trang theo price có nhiều sản phẩm trùng giá thì bộ lọc $lt làm mất sản phẩm. Làm thế nào để vẫn ổn định? Gợi ý nằm ở mục Đào sâu.
Đào sâu
Phần này dành cho bạn tò mò.
- Cơ chế bên trong. Khi
skipđi cùngsorttheo trường có chỉ mục, server đi trên chỉ mục nên chỉ đọc document đầy đủ cho phần được trả về (ở bảng chi phí: 20000 khóa, 10 document). Khi không cósortthì kế hoạch làLIMIT,SKIP,COLLSCANvà phải đọc cả collection. Đây là kết quả đã chạy trên 4.4.19, còn các phiên bản khác [Có thể khác nhau]. - Hiểu lầm nâng cao. Phân trang theo khóa gặp vấn đề khi nhiều document trùng giá trị khóa sort. Cách ổn định là sort bằng
{ price: -1, _id: 1 }và lọc bằng$orgiữa hai điều kiện: giá nhỏ hơn giá cuối, hoặc cùng giá nhưng_idlớn hơn. Ví dụ chạy thật trên 5 document cùng giá 100:
function demo() {
db.products.insertMany([20, 21, 22, 23, 24].map(function (i) { return { _id: i, price: 100 } }))
var p1 = db.products.find().sort({ price: -1, _id: 1 }).limit(2).toArray()
print("page 1:", p1.map(function (d) { return d._id }).join(", "))
var last = p1[1]
var p2 = db.products.find({ $or: [{ price: { $lt: last.price } }, { price: last.price, _id: { $gt: last._id } }] }).sort({ price: -1, _id: 1 }).limit(2).toArray()
print("page 2:", p2.map(function (d) { return d._id }).join(", "))
var wrong = db.products.find({ price: { $lt: last.price } }).limit(2).toArray()
print("wrong:", wrong.length)
}
demo()
Kết quả [Đã chạy: MongoDB 4.4.19]: trang 2 tiếp nối đúng sau trang 1, còn bộ lọc chỉ có $lt tìm được 0 document, tức đánh mất cả ba document còn lại (dòng cuối).
page 1: 20, 21
page 2: 22, 23
wrong: 0
- So sánh với lựa chọn khác. Có thể dùng
cursor.count(true)để biết số document sau khi tínhskipvàlimit, trong khicount()mặc định bỏ qua cả hai. Trên kho 8 sản phẩm, vớiskip(5):
var c = db.products.find().skip(5)
print(c.count(), c.count(true), c.itcount())
Kết quả [Đã chạy]: ba số là 8, 3, 3.
8 3 3
- Giới hạn của bài. Bài này chưa nói về
$skiptrongaggregatevà chưa nói về các cách đánh số trang khi tổng số trang cần hiển thị. - Đọc thêm. MongoDB Manual 4.4, trang
cursor.skip(): https://www.mongodb.com/docs/v4.4/reference/method/cursor.skip/
Bài tiếp theo
Bài sau là hàm ObjectId(): vì sao _id mặc định của MongoDB chứa cả thời gian tạo, và cách tận dụng điều đó để lọc theo khoảng ngày. Mini-dự án nối tiếp: lọc các sản phẩm tạo trong một khoảng thời gian mà không cần trường ngày riêng.
Lời giải và đáp án
Làm cùng mình, Bước 2. Hai dòng cần điền:
.skip((n - 1) * size)
.limit(size)
function page(n, size) {
return db.products.find({}, { name: 1, _id: 0 }).sort({ _id: 1 }).skip((n - 1) * size).limit(size).toArray().map(function (d) { return d.name }).join(", ")
}
print("page 2:", page(2, 3))
print("page 3:", page(3, 3))
page 2: cable, headset, webcam
page 3: speaker, hub
Trang 2 là _id 4, 5, 6 và trang 3 chỉ còn _id 7, 8.
Làm cùng mình, Bước 3.
var lastId = 6
db.products.find({ _id: { $gt: lastId } }, { name: 1 }).sort({ _id: 1 }).limit(3)
{ "_id" : 7, "name" : "speaker" }
{ "_id" : 8, "name" : "hub" }
Bài 1.
print(db.products.find().sort({ _id: 1 }).skip(0).itcount())
print(db.products.find().sort({ _id: 1 }).skip(2.7).limit(1).toArray()[0]._id)
print(db.products.find().sort({ _id: 1 }).skip(100).itcount())
8
3
0
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
skip(0) | Không bỏ gì, còn 8 document | 0 là giá trị trung tính |
skip(2.7).limit(1) | Bỏ 2 document, lấy _id 3 | Phần lẻ bị cắt, không làm tròn |
skip(100).itcount() | Còn 0 document | Bỏ nhiều hơn tổng số thì rỗng, không lỗi |
Bài 2.
function walk() {
var last = 0
for (var i = 1; i <= 4; i++) {
var p = db.products.find({ _id: { $gt: last } }).sort({ _id: 1 }).limit(3).toArray()
if (p.length === 0) break
print("page " + i + ": " + p.map(function (d) { return d.name }).join(", "))
last = p[p.length - 1]._id
}
}
walk()
page 1: keyboard, mouse, monitor
page 2: cable, headset, webcam
page 3: speaker, hub
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
{ _id: { $gt: last } } | Lấy các document đứng sau trang trước | Bám vào giá trị đã thấy, không bám vào vị trí |
if (p.length === 0) break | Dừng khi không còn trang nào | Vòng lặp tối đa 4 lần, nên không chạy vô hạn |
last = p[p.length - 1]._id | Ghi nhớ khóa của document cuối trang | Trang sau bắt đầu ngay sau khóa này |
Bài 3. Danh sách đầy đủ sau khi xóa headset:
print(db.products.find().sort({ price: -1 }).toArray().map(function (d) { return d.name }).join(", "))
monitor, webcam, speaker, keyboard, mouse, hub, cable
Khách đã thấy monitor, headset, webcam. Sau khi xóa, danh sách có monitor ở vị trí 1, webcam vị trí 2, speaker vị trí 3. skip(3) bỏ cả ba, trang 2 bắt đầu từ keyboard. Speaker chưa được xem nhưng bị sót.
var seen = db.products.find().sort({ price: -1 }).limit(3).toArray().map(function (d) { return d.name })
var r = db.products.deleteOne({ name: "headset" })
var page2 = db.products.find().sort({ price: -1 }).skip(3).limit(3).toArray().map(function (d) { return d.name })
print("page 1:", seen.join(", "))
print("page 2:", page2.join(", "))
page 1: monitor, headset, webcam
page 2: keyboard, mouse, hub
Tự kiểm tra.
skip((n - 1) * s), kèmlimit(s).- 2 document (
_id7 và 8), vì kho chỉ có 8 document. - Vì
skipđếm theo vị trí, và thêm hay xóa document giữa hai lần bấm làm vị trí dịch chuyển. - Vì server vẫn phải đi qua 19990 khóa chỉ mục trước khi trả về 10 document.
- Không có thứ tự nào được bảo đảm, nên các trang có thể chồng lên nhau hoặc sót document.