DHDataHọc
Lý thuyếtBài 15/306 phút đọc+10 XP

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 OFFSET và 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 LIMIT không có ORDER BY trả kết quả ngẫu nhiên.
  • Biết giới hạn của OFFSET lớ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":

code
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:

code
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:

code
-- 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ếtGhi chú
LIMIT 5Phổ biến ở PostgreSQL, MySQL, SQLite
FETCH FIRST 5 ROWS ONLYChuẩn SQL, PostgreSQL hỗ trợ
LIMIT 5 OFFSET 10Bỏ 10 dòng, lấy 5
LIMIT ALLKhông giới hạn, giống bỏ LIMIT
LIMIT 0Trả 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:

code
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:

code
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 3;
nameprice
Nồi chiên không dầu1690000
Bàn phím cơ1250000
Tai nghe Bluetooth890000

Ba sản phẩm rẻ nhất:

code
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:

code
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:

code
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:

code
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:

code
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

  1. 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.
  2. 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ê id của từng trang và kiểm tra không dòng nào lặp lại.
  3. 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.
  4. Lấy năm đơn mới nhất bằng ORDER BY order_date DESC LIMIT 5 và thêm id DESC là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ứngNguyên nhânCách sửa
LIMIT trả dòng không mong muốnThiếu ORDER BYSắp xếp trước khi giới hạn
syntax error at or near "LIMIT"Đặt LIMIT trước ORDER BYORDER BY đứng trước LIMIT
Dòng bị lặp giữa hai trangThứ tự không ổn địnhThêm cột duy nhất vào ORDER BY
LIMIT must not be negativeDùng số âmDùng LIMIT ALL nếu muốn bỏ giới hạn
Trang cuối chậm bất thườngOFFSET quá lớnDù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 n cắt sau ORDER BY; thiếu ORDER BY thì kết quả không xác định.
  • OFFSET m bỏ m dò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 ONLY là cú pháp chuẩn tương đương LIMIT.
  • OFFSET lớn chậm; keyset pagination nhanh hơn trên bảng lớn.

Dùng ← → để chuyển bài