Giới hạn số dòng
Bạn sẽ học được
- Dùng
LIMITđể lấy đúng số dòng mong muốn. - Phân trang bằng
OFFSETvà hiểu vì sao phải sắp xếp trước. - Viết mẫu top-N ổn định khi có giá trị bằng nhau.
- Nhận ra vì sao
LIMITkhông cóORDER BYtrả kết quả ngẫu nhiên. - Biết giới hạn của
OFFSETlớn và cách phân trang nhanh hơn.
Khái niệm cốt lõi
LIMIT n cắt kết quả còn tối đa n dòng. Nó chạy sau ORDER BY, nên kết hợp hai mệnh đề này cho ra "n dòng đầu theo một thứ tự xác định":
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 3;Đọc theo thứ tự thực thi: lấy toàn bộ sản phẩm, sắp giá giảm dần, rồi giữ ba dòng đầu. Kết quả là Nồi chiên không dầu, Bàn phím cơ, Tai nghe Bluetooth.
LIMIT mà không có ORDER BY là một cái bẫy:
SELECT name FROM products LIMIT 3;Câu này trả ba dòng, nhưng không có gì đảm bảo là ba dòng nào. PostgreSQL có thể trả ba dòng đầu của bảng, ba dòng cuối, hoặc ba dòng bất kỳ tùy kế hoạch thực thi, chỉ mục, và thứ tự vật lý trên đĩa. Chạy hôm nay khác hôm sau là chuyện bình thường. Nếu kết quả cần ổn định — và gần như luôn cần — hãy thêm ORDER BY.
OFFSET m bỏ qua m dòng đầu sau khi sắp xếp, rồi LIMIT n lấy n dòng tiếp theo. Đây là cơ chế phân trang cơ bản:
-- Trang 1
SELECT name, price FROM products ORDER BY price, id LIMIT 3 OFFSET 0;
-- Trang 2
SELECT name, price FROM products ORDER BY price, id LIMIT 3 OFFSET 3;
-- Trang 3
SELECT name, price FROM products ORDER BY price, id LIMIT 3 OFFSET 6;Ba trang lần lượt chứa các dòng 1–3, 4–6, 7–9. OFFSET 0 tương đương không có OFFSET. Điều kiện tiên quyết để phân trang đúng là thứ tự phải ổn định giữa các lần gọi. Nếu hai dòng bằng nhau ở mọi cột sắp xếp, chúng có thể đổi chỗ giữa trang 1 và trang 2, khiến một dòng xuất hiện hai lần và một dòng biến mất. Thêm cột duy nhất như id vào ORDER BY để chặn điều này.
Khi có nhiều dòng cùng giá trị ở biên, "top 5" trở nên mơ hồ. Nếu sáu sản phẩm cùng giá và bạn lấy năm, sản phẩm nào bị loại? SQL không trả lời câu hỏi đó — nó chỉ trả năm dòng theo thứ tự nội bộ. Trong báo cáo thật, hãy nêu rõ quy tắc phụ (ví dụ "năm sản phẩm đắt nhất, nếu bằng giá thì ưu tiên id nhỏ hơn") và viết đúng quy tắc đó trong ORDER BY.
Các biến thể cú pháp:
| Cách viết | Ghi chú |
|---|---|
LIMIT 5 | Phổ biến ở PostgreSQL, MySQL, SQLite |
FETCH FIRST 5 ROWS ONLY | Chuẩn SQL, PostgreSQL hỗ trợ |
LIMIT 5 OFFSET 10 | Bỏ 10 dòng, lấy 5 |
LIMIT ALL | Không giới hạn, giống bỏ LIMIT |
LIMIT 0 | Trả 0 dòng, hữu ích khi chỉ cần kiểm tra cú pháp |
Số âm không hợp lệ: LIMIT -1 báo lỗi LIMIT must not be negative. Khác với một số hệ quản trị khác, PostgreSQL không dùng -1 để biểu thị "không giới hạn".
Về hiệu năng, OFFSET lớn là vấn đề. OFFSET 100000 buộc cơ sở dữ liệu đọc và bỏ qua 100.000 dòng trước khi trả kết quả. Với bảng lớn, trang càng về sau càng chậm. Cách phân trang nhanh hơn là keyset pagination: dùng giá trị của dòng cuối trang trước làm mốc:
SELECT name, price
FROM products
WHERE price < 390000
ORDER BY price DESC
LIMIT 3;Truy vấn này dùng được chỉ mục trên price và không phải quét các dòng bị bỏ. Với bộ dữ liệu nhỏ trong khóa học, OFFSET hoàn toàn ổn; nhưng biết keyset sẽ hữu ích khi làm việc với bảng thật.
Ví dụ chạy được
Ba sản phẩm đắt nhất:
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 3;| name | price |
|---|---|
| Nồi chiên không dầu | 1690000 |
| Bàn phím cơ | 1250000 |
| Tai nghe Bluetooth | 890000 |
Ba sản phẩm rẻ nhất:
SELECT name, price
FROM products
ORDER BY price
LIMIT 3;Sách SQL thực chiến (180000), Áo thun cotton (250000), Đèn bàn LED (290000).
Trang thứ hai của danh sách tăng dần, mỗi trang ba dòng:
SELECT name, price
FROM products
ORDER BY price
LIMIT 3 OFFSET 3;Ba dòng: Bình giữ nhiệt (320000), Giá kê laptop (390000), Chuột không dây (450000). Đây là các dòng 4, 5, 6 của danh sách chín sản phẩm. Trang thứ ba với OFFSET 6 chỉ còn ba dòng cuối: Tai nghe, Bàn phím, Nồi chiên.
Phân trang trên bảng đơn:
SELECT id, order_date
FROM orders
ORDER BY order_date
LIMIT 5 OFFSET 5;Năm dòng: đơn 6 (15/03), 7 (01/04), 8 (12/04), 9 (06/05), 10 (01/06). Trang đầu (OFFSET 0) là đơn 1, 2, 3, 4, 5. Trang cuối (OFFSET 15) còn đúng một dòng là đơn 16 (11/11). Tổng ba trang đầu là 15 dòng; trang bốn có 1 dòng — đây là lý do giao diện phân trang cần biết tổng số dòng, hoặc đơn giản là dừng khi trang trả ít hơn LIMIT.
Kiểm tra LIMIT không có ORDER BY:
SELECT id FROM orders LIMIT 5;Kết quả thường là 1, 2, 3, 4, 5 khi bảng còn mới, nhưng đừng dựa vào đó. Sau khi có ai xoá và chèn lại dòng, thứ tự vật lý thay đổi và kết quả có thể khác. Đây là lý do các bài kiểm tra tự động luôn yêu cầu ORDER BY khi so sánh kết quả.
Một ví dụ về tie-breaker:
SELECT name, price
FROM products
ORDER BY price DESC, id
LIMIT 5;Hai cột sắp xếp bảo đảm kết quả xác định. Với dữ liệu hiện tại, id không thay đổi gì vì không có giá trùng; nhưng nếu thêm sản phẩm cùng giá, quy tắc đã rõ ràng và kết quả ổn định.
Thực hành
- Lấy ba sản phẩm đắt nhất và ba sản phẩm rẻ nhất. So sánh hai danh sách; chúng phải rời nhau hoàn toàn.
- Viết truy vấn phân trang đơn hàng, mỗi trang bốn dòng, sắp theo
order_date. Liệt kêidcủa từng trang và kiểm tra không dòng nào lặp lại. - Chạy
SELECT id FROM orders LIMIT 3;hai lần. Kết quả có thể giống nhau; giải thích vì sao điều đó không chứng minh thứ tự ổn định. - Lấy năm đơn mới nhất bằng
ORDER BY order_date DESC LIMIT 5và thêmid DESClàm tie-breaker. Xác nhận kết quả không đổi trên dữ liệu hiện tại, nhưng hiểu vì sao câu lệnh an toàn hơn.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
LIMIT trả dòng không mong muốn | Thiếu ORDER BY | Sắp xếp trước khi giới hạn |
syntax error at or near "LIMIT" | Đặt LIMIT trước ORDER BY | ORDER BY đứng trước LIMIT |
| Dòng bị lặp giữa hai trang | Thứ tự không ổn định | Thêm cột duy nhất vào ORDER BY |
LIMIT must not be negative | Dùng số âm | Dùng LIMIT ALL nếu muốn bỏ giới hạn |
| Trang cuối chậm bất thường | OFFSET quá lớn | Dùng keyset pagination |
Lỗi "dòng bị lặp giữa hai trang" khó thấy vì nó chỉ xuất hiện khi dữ liệu có giá trị trùng. Cách phòng tránh: luôn thêm id vào cuối ORDER BY khi dùng OFFSET. Một cột duy nhất chỉ tốn vài byte nhưng loại bỏ hoàn toàn lớp lỗi này.
Tóm tắt
LIMIT ncắt sauORDER BY; thiếuORDER BYthì kết quả không xác định.OFFSET mbỏmdòng đầu, dùng để phân trang.- Luôn thêm cột duy nhất làm tie-breaker khi phân trang hoặc top-N.
FETCH FIRST n ROWS ONLYlà cú pháp chuẩn tương đươngLIMIT.OFFSETlớn chậm; keyset pagination nhanh hơn trên bảng lớn.
Dùng ← → để chuyển bài