Query ORDER BY yang rapi tetap bisa mengembalikan sejuta baris — dan pengguna aplikasi Anda tidak akan menggulir semuanya. Dua kebutuhan muncul bersamaan di hampir setiap aplikasi: menampilkan hanya paling atas (tiga produk termahal, lima pesanan terbaru) dan menampilkan hasil per halaman seperti katalog online. PostgreSQL menjawab keduanya dengan klausa LIMIT (potong jumlah baris) dan OFFSET (lompati baris awal) — pasangan yang menyatu dengan ORDER BY dan menjadi tulang punggung pagination sederhana. Chapter ini membahas keduanya dari query top-N sampai pola pagination lengkap, termasuk jebakan performa ketika offset dibuat sangat dalam.
Menyiapkan Tabel Latihan #
Kita pakai tabel produk dengan sepuluh baris — cukup banyak untuk empat halaman berisi tiga baris, tapi tetap ringkas untuk dibaca:
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
kategori text NOT NULL,
harga numeric(10, 2) NOT NULL,
stok integer NOT NULL
);
INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(3, 'Kertas HVS A4', 'kertas', 55000, 80),
(4, 'Stapler Besar', 'alat tulis', 24000, 15),
(5, 'Amplop Coklat', 'kertas', 1500, 300),
(6, 'Penghapus', 'alat tulis', 2000, 150),
(7, 'Pensil 2B', 'alat tulis', 2500, 180),
(8, 'Map Plastik', 'alat tulis', 8500, 45),
(9, 'Kertas Folio', 'kertas', 48000, 60),
(10, 'Lakban Bening', 'alat tulis', 9000, 90);Output yang Dihasilkan:
CREATE TABLE
INSERT 0 10Klausa LIMIT dan OFFSET ditulis setelah ORDER BY — urutan klausanya bukan gaya: keduanya bekerja pada hasil yang sudah terurut, sehingga tanpa ORDER BY, klausa ini memotong baris dalam urutan yang tidak terdefinisi.
LIMIT: Memotong Jumlah Baris #
Bentuk paling produktif dari LIMIT adalah query top-N: urutkan dulu, lalu ambil N baris pertama. Dua bentuk tepi juga perlu dikenal — LIMIT 0 dan LIMIT ALL:
-- Top-3 produk termahal: resep dasar query "top-N".
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3;
-- LIMIT 0 mengembalikan nol baris: berguna untuk menguji struktur query
-- (kolom sah atau tidak) tanpa memindahkan data.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 0;
-- LIMIT ALL berarti "tanpa batas" — ekuivalen dengan tidak menulis LIMIT.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT ALL;Output yang Dihasilkan:
id | nama | harga
----+---------------+----------
3 | Kertas HVS A4 | 55000.00
9 | Kertas Folio | 48000.00
4 | Stapler Besar | 24000.00
(3 rows)
id | nama | harga
----+------+-------
(0 rows)
id | nama | harga
----+---------------+----------
3 | Kertas HVS A4 | 55000.00
9 | Kertas Folio | 48000.00
4 | Stapler Besar | 24000.00
10 | Lakban Bening | 9000.00
8 | Map Plastik | 8500.00
1 | Buku Tulis | 4500.00
2 | Pulpen Hitam | 3000.00
7 | Pensil 2B | 2500.00
6 | Penghapus | 2000.00
5 | Amplop Coklat | 1500.00
(10 rows)Query pertama memotong hasil menjadi tiga produk termahal (Kertas HVS A4, Kertas Folio, Stapler Besar) — persis resep “produk unggulan” di halaman depan toko. LIMIT 0 mengembalikan (0 rows) namun tetap memvalidasi query: nama kolom yang salah tetap memicu error, sehingga bentuk ini dipakai aplikasi untuk preflight check. LIMIT ALL mengembalikan seluruh sepuluh baris; kata kunci ini jarang ditulis karena efeknya sama dengan menghapus LIMIT, tetapi Anda akan menemuinya di query yang dibangun secara dinamis — aplikasi menempelkan LIMIT ALL ketika halaman “tampilkan semua” dipilih.
OFFSET: Melompati Baris Awal #
OFFSET n membuang n baris pertama dari hasil terurut sebelum baris mulai dikirim ke klien. Klausa ini boleh berdiri sendiri tanpa LIMIT, dan nilai offset yang melebihi jumlah baris tidak menghasilkan error:
-- OFFSET melompati baris hasil sebelum mulai mengembalikan.
-- Urutan tetap ditentukan ORDER BY; OFFSET menghitung posisi di urutan itu.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 3;
-- OFFSET tanpa LIMIT: semua baris setelah yang dilompati tetap dikembalikan.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
OFFSET 8;
-- OFFSET lebih besar dari jumlah baris tidak error — hasilnya hanya kosong.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
OFFSET 99;Output yang Dihasilkan:
id | nama | harga
----+---------------+---------
10 | Lakban Bening | 9000.00
8 | Map Plastik | 8500.00
1 | Buku Tulis | 4500.00
(3 rows)
id | nama | harga
----+---------------+---------
6 | Penghapus | 2000.00
5 | Amplop Coklat | 1500.00
(2 rows)
id | nama | harga
----+------+-------
(0 rows)Query pertama melompati tiga produk teratas lalu mengambil tiga berikutnya — perhatikan bahwa hasilnya dimulai dari Lakban Bening, tepat baris keempat urutan. Query kedua memperlihatkan OFFSET tanpa LIMIT: dua produk termurah terakhir dikembalikan setelah delapan baris dilompati. Query ketiga membuktikan OFFSET 99 pada tabel berisi sepuluh baris menghasilkan (0 rows) secara diam — tidak ada error, sehingga halaman pagination yang kelewat batas hanya tampil kosong.
Pola Pagination: ORDER BY + LIMIT + OFFSET #
Pagination menggabungkan ketiga klausa dengan satu formula: OFFSET = (nomor_halaman - 1) × ukuran_halaman. Dengan sepuluh produk dan tiga baris per halaman, kita memperoleh empat halaman (halaman terakhir hanya berisi satu baris):
-- Halaman 1: tiga produk termahal (OFFSET 0 berarti tidak melompati apa pun).
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 0;
-- Halaman 2: lompati 3 baris pertama, ambil 3 berikutnya.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 3;
-- Halaman 3: lompati 6 baris pertama.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 6;
-- Halaman 4 (terakhir): hanya 1 baris tersisa dari 10 total.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 9;Output yang Dihasilkan:
id | nama | harga
----+---------------+----------
3 | Kertas HVS A4 | 55000.00
9 | Kertas Folio | 48000.00
4 | Stapler Besar | 24000.00
(3 rows)
id | nama | harga
----+---------------+---------
10 | Lakban Bening | 9000.00
8 | Map Plastik | 8500.00
1 | Buku Tulis | 4500.00
(3 rows)
id | nama | harga
----+--------------+---------
2 | Pulpen Hitam | 3000.00
7 | Pensil 2B | 2500.00
6 | Penghapus | 2000.00
(3 rows)
id | nama | harga
----+---------------+---------
5 | Amplop Coklat | 1500.00
(1 row)Mari kita bedah pola di balik keempat query tersebut:
ORDER BY harga DESC, iddipasang identik di keempat halaman. Pagination tanpaORDER BYyang menentukan urutan penuh akan mengembalikan baris yang tumpang-tindih atau terlewat antar halaman — halaman 2 bisa mengulang baris halaman 1 hanya karena urutan hasilnya berubah. Pemutus seriidmemastikan tidak ada dua baris yang bertukar posisi.LIMIT 3adalah ukuran halaman yang tetap;OFFSET 0, 3, 6, 9bergerak kelipatan tiga. Aplikasi menghitung offset ini dari nomor halaman yang diminta pengguna: halaman 1 → offset 0, halaman 2 → offset 3, dan seterusnya.- Halaman 4 hanya berisi satu baris —
LIMIT 3adalah batas maksimum, bukan janji jumlah. Aplikasi perlu menangani halaman pendek seperti ini, misalnya menyembunyikan tombol “berikutnya”.
OFFSET Tidak Boleh Negatif #
Nilai LIMIT dan OFFSET harus bilangan bulat bukan negatif. Query aplikasi yang menghitung offset dari input pengguna — misalnya offset = (halaman - 1) × ukuran — bisa menghasilkan angka negatif bila input tidak divalidasi, dan PostgreSQL menolaknya dengan error:
-- LIMIT hanya menerima bilangan bulat bukan negatif.
-- Query berikut mencoba meminta -1 baris, yang tidak punya makna.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT -1;Output yang Dihasilkan:
psql:limit-negatif.sql:6: ERROR: LIMIT must not be negativeError LIMIT must not be negative muncul saat parse, sebelum query dieksekusi. Pesan yang sama berlaku untuk OFFSET negatif (OFFSET must not be negative), sehingga aplikasi web sebaiknya memvalidasi dan menjepit nomor halaman ke nilai minimum 1 sebelum menghitung offset.
Jebakan OFFSET Dalam #
Pola LIMIT 3 OFFSET 9 pada sepuluh baris tidak terasa apa-apa. Masalahnya muncul pada skala nyata: untuk mengirim halaman ke-10.000 (offset 30.000, tiga baris per halaman), server harus menyusun dan membuang 30.000 baris terurut lebih dulu, lalu mengirim tiga baris saja — biaya yang tumbuh linear terhadap nomor halaman. Semakin dalam pengguna menggulir, semakin lambat halamannya; halaman ke-100 bekerja 100 kali lebih keras dari halaman pertama.
Untuk tabel berukuran besar, teknik yang lebih efisien adalah keyset pagination (kadang disebut seek method): simpan nilai sort baris terakhir halaman sebelumnya, lalu filter WHERE harga < nilai_terakhir ORDER BY harga DESC LIMIT 3 — server melompat langsung ke posisi itu lewat index, tanpa memindai semua baris di depannya. Pola ini membutuhkan pemahaman index dan rencana eksekusi, jadi pembahasannya menunggu Section Indexes dan Query Optimization; untuk data berukuran ratusan hingga ribuan baris, kombinasi LIMIT + OFFSET yang Anda kuasai di chapter ini sudah lebih dari cukup.
LIMIT dan OFFSET adalah sintaks khas PostgreSQL (dan turunannya MySQL) yang akhirnya diadopsi standar SQL dalam bentuk lain. Chapter penutup section ini memperkenalkan FETCH FIRST — padanan standar SQL:2008 yang lebih ekspresif, lengkap dengan opsi WITH TIES yang tidak dimiliki LIMIT.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkasCREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
kategori text NOT NULL,
harga numeric(10, 2) NOT NULL,
stok integer NOT NULL
);
INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
(1, 'Buku Tulis', 'alat tulis', 4500, 120),
(2, 'Pulpen Hitam', 'alat tulis', 3000, 200),
(3, 'Kertas HVS A4', 'kertas', 55000, 80),
(4, 'Stapler Besar', 'alat tulis', 24000, 15),
(5, 'Amplop Coklat', 'kertas', 1500, 300),
(6, 'Penghapus', 'alat tulis', 2000, 150),
(7, 'Pensil 2B', 'alat tulis', 2500, 180),
(8, 'Map Plastik', 'alat tulis', 8500, 45),
(9, 'Kertas Folio', 'kertas', 48000, 60),
(10, 'Lakban Bening', 'alat tulis', 9000, 90);
limit-dasar.sql
Unduh berkas-- Top-3 produk termahal: resep dasar query "top-N".
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3;
-- LIMIT 0 mengembalikan nol baris: berguna untuk menguji struktur query
-- (kolom sah atau tidak) tanpa memindahkan data.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 0;
-- LIMIT ALL berarti "tanpa batas" — ekuivalen dengan tidak menulis LIMIT.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT ALL;
limit-negatif.sql
Unduh berkas-- LIMIT hanya menerima bilangan bulat bukan negatif.
-- Query berikut mencoba meminta -1 baris, yang tidak punya makna.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT -1;
limit-offset.sql
Unduh berkas-- OFFSET melompati baris hasil sebelum mulai mengembalikan.
-- Urutan tetap ditentukan ORDER BY; OFFSET menghitung posisi di urutan itu.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 3;
-- OFFSET tanpa LIMIT: semua baris setelah yang dilompati tetap dikembalikan.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
OFFSET 8;
-- OFFSET lebih besar dari jumlah baris tidak error — hasilnya hanya kosong.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
OFFSET 99;
pagination.sql
Unduh berkas-- Halaman 1: tiga produk termahal (OFFSET 0 berarti tidak melompati apa pun).
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 0;
-- Halaman 2: lompati 3 baris pertama, ambil 3 berikutnya.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 3;
-- Halaman 3: lompati 6 baris pertama.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 6;
-- Halaman 4 (terakhir): hanya 1 baris tersisa dari 10 total.
SELECT id, nama, harga
FROM produk
ORDER BY harga DESC, id
LIMIT 3 OFFSET 9;