ORDER BY, LIMIT, dan DISTINCT

Mengurutkan hasil, membatasi baris untuk paginasi, dan menghilangkan duplikasi dengan DISTINCT.

Daftar isi

Topik ini membahas tiga klausa yang mengatur bentuk hasil query, yaitu ORDER BY untuk mengurutkan baris, LIMIT untuk membatasi jumlah baris, dan DISTINCT untuk menghilangkan duplikasi. Ketiganya dipakai bersama pada kebutuhan yang sama, misalnya paginasi dan pembuatan daftar ringkas. Basis data latihan dibangun ulang di awal topik agar setiap keluaran dapat diulang dari keadaan data yang sama.

Menyiapkan basis data latihan#

Basis data perpustakaan dibangun ulang dari berkas skrip yang sama dengan topik lain pada modul ini, yaitu 8 baris anggota, 10 baris buku, dan 12 baris peminjaman. Skrip tersebut memulai dari DROP DATABASE dan CREATE DATABASE, sehingga eksekusi ulangnya selalu menghasilkan data yang persis sama.

sql data-perpustakaan.sql
-- tidak-dijalankan
-- Berkas latihan jalur MySQL: membangun ulang basis data perpustakaan dari nol.
-- Jalankan dengan: mysql -u root < data-perpustakaan.sql

DROP DATABASE IF EXISTS perpustakaan;
CREATE DATABASE perpustakaan;
USE perpustakaan;

CREATE TABLE anggota (
  id INT PRIMARY KEY,
  nama VARCHAR(60) NOT NULL,
  kota VARCHAR(40) NOT NULL,
  kelas VARCHAR(10) NOT NULL
);

CREATE TABLE buku (
  id INT PRIMARY KEY,
  judul VARCHAR(120) NOT NULL,
  penulis VARCHAR(60) NOT NULL,
  tahun INT NOT NULL,
  stok INT NOT NULL
);

CREATE TABLE peminjaman (
  id INT PRIMARY KEY,
  anggota_id INT NOT NULL,
  buku_id INT NOT NULL,
  tanggal_pinjam DATE NOT NULL,
  tanggal_kembali DATE NULL
);

INSERT INTO anggota (id, nama, kota, kelas) VALUES
  (1, 'Ali Rahman',     'Bandung',  '10A'),
  (2, 'Budi Santoso',   'Bandung',  '10A'),
  (3, 'Citra Lestari',  'Jakarta',  '11B'),
  (4, 'Dewi Anggraini', 'Depok',    '10A'),
  (5, 'Eka Putri',      'Bogor',    '10A'),
  (6, 'Fajar Nugroho',  'Bandung',  '11B'),
  (7, 'Gita Wulandari', 'Jakarta',  '10A'),
  (8, 'Hadi Pratama',   'Semarang', '11B');

INSERT INTO buku (id, judul, penulis, tahun, stok) VALUES
  (1,  'Dasar Basis Data Relasional', 'Ratna Kusuma',   2019, 4),
  (2,  'Algoritma dan Struktur Data', 'Bambang Wijaya', 2021, 2),
  (3,  'Belajar Python dari Nol',     'Ratna Kusuma',   2022, 5),
  (4,  'Jaringan Komputer Dasar',     'Hendra Gunawan', 2018, 1),
  (5,  'Statistika untuk Pemula',     'Sari Wulandari', 2020, 3),
  (6,  'Sejarah Maritim Nusantara',   'Ahmad Fauzi',    2017, 2),
  (7,  'Matematika Diskrit',          'Sari Wulandari', 2021, 2),
  (8,  'Pemrograman Web Sisi Server', 'Bambang Wijaya', 2023, 6),
  (9,  'Fisika Mekanika Dasar',       'Dewi Kartika',   2016, 3),
  (10, 'Sastra Indonesia Modern',     'Ahmad Fauzi',    2019, 4);

INSERT INTO peminjaman (id, anggota_id, buku_id, tanggal_pinjam, tanggal_kembali) VALUES
  (1,  1, 3,  '2025-01-06', '2025-01-13'),
  (2,  3, 2,  '2025-01-08', NULL),
  (3,  2, 1,  '2025-01-10', '2025-01-17'),
  (4,  5, 8,  '2025-01-15', NULL),
  (5,  7, 5,  '2025-01-20', '2025-01-27'),
  (6,  4, 6,  '2025-01-22', '2025-02-03'),
  (7,  6, 3,  '2025-02-05', NULL),
  (8,  8, 10, '2025-02-10', '2025-02-18'),
  (9,  1, 7,  '2025-02-14', NULL),
  (10, 3, 4,  '2025-02-20', '2025-02-25'),
  (11, 2, 9,  '2025-02-24', '2025-03-03'),
  (12, 5, 2,  '2025-03-10', NULL);
bash
# membangun ulang basis data latihan dari berkas skrip
mysql -u root < data-perpustakaan.sql

Perintah di atas selesai tanpa keluaran apa pun, dan basis data latihan siap dibaca.

Urutan baris tanpa ORDER BY#

Tanpa ORDER BY, MySQL tidak memberi jaminan apa pun mengenai urutan baris pada hasil query. Server bebas mengembalikan baris dalam urutan apa pun yang paling murah menurut rencana eksekusinya, yaitu langkah yang dipilih server untuk menjalankan query.

bash
# membaca tabel tanpa klausa pengurutan
mysql -t -u root perpustakaan -e "SELECT judul, tahun FROM buku;"
text Keluaran
+-----------------------------+-------+
| judul                       | tahun |
+-----------------------------+-------+
| Dasar Basis Data Relasional |  2019 |
| Algoritma dan Struktur Data |  2021 |
| Belajar Python dari Nol     |  2022 |
| Jaringan Komputer Dasar     |  2018 |
| Statistika untuk Pemula     |  2020 |
| Sejarah Maritim Nusantara   |  2017 |
| Matematika Diskrit          |  2021 |
| Pemrograman Web Sisi Server |  2023 |
| Fisika Mekanika Dasar       |  2016 |
| Sastra Indonesia Modern     |  2019 |
+-----------------------------+-------+

Keluaran di atas tampak terurut menurut kolom id. Urutan itu muncul karena InnoDB membaca tabel kecil ini melalui primary key-nya, dan sifat tersebut bukan jaminan server. Tabel yang bertambah besar atau query yang dibaca melalui index lain dapat menghasilkan urutan yang berbeda, sehingga urutan yang dibutuhkan selalu dinyatakan secara eksplisit.

Mengurutkan hasil dengan ORDER BY#

Klausa ORDER BY menyatakan kolom pembanding beserta arah pengurutannya, yaitu ASC untuk menaik dan DESC untuk menurun. Tanpa kata arah, pengurutan bawaan berlangsung menaik.

sql Sintaks ORDER BY
-- tidak-dijalankan
SELECT kolom_1, kolom_2
FROM nama_tabel
ORDER BY kolom_1 ASC, kolom_2 DESC;
bash
# mengurutkan buku menurut tahun, lalu judul sebagai pemecah seri
mysql -t -u root perpustakaan -e "SELECT judul, tahun FROM buku ORDER BY tahun ASC, judul ASC;"
text Keluaran
+-----------------------------+-------+
| judul                       | tahun |
+-----------------------------+-------+
| Fisika Mekanika Dasar       |  2016 |
| Sejarah Maritim Nusantara   |  2017 |
| Jaringan Komputer Dasar     |  2018 |
| Dasar Basis Data Relasional |  2019 |
| Sastra Indonesia Modern     |  2019 |
| Statistika untuk Pemula     |  2020 |
| Algoritma dan Struktur Data |  2021 |
| Matematika Diskrit          |  2021 |
| Belajar Python dari Nol     |  2022 |
| Pemrograman Web Sisi Server |  2023 |
+-----------------------------+-------+

Kunci pengurutan boleh lebih dari satu. Kolom pertama menentukan urutan utama, sedangkan kolom berikutnya memecah seri antarbaris yang nilainya sama. Dua buku terbitan 2019 dan dua buku terbitan 2021 pada keluaran di atas diurutkan menurut judul karena judul menjadi kunci kedua pada kedua kelompok tahun itu.

bash
# mengurutkan buku dari stok terbanyak, dipecah menurut judul
mysql -t -u root perpustakaan -e "SELECT judul, penulis, stok FROM buku ORDER BY stok DESC, judul ASC;"
text Keluaran
+-----------------------------+----------------+------+
| judul                       | penulis        | stok |
+-----------------------------+----------------+------+
| Pemrograman Web Sisi Server | Bambang Wijaya |    6 |
| Belajar Python dari Nol     | Ratna Kusuma   |    5 |
| Dasar Basis Data Relasional | Ratna Kusuma   |    4 |
| Sastra Indonesia Modern     | Ahmad Fauzi    |    4 |
| Fisika Mekanika Dasar       | Dewi Kartika   |    3 |
| Statistika untuk Pemula     | Sari Wulandari |    3 |
| Algoritma dan Struktur Data | Bambang Wijaya |    2 |
| Matematika Diskrit          | Sari Wulandari |    2 |
| Sejarah Maritim Nusantara   | Ahmad Fauzi    |    2 |
| Jaringan Komputer Dasar     | Hendra Gunawan |    1 |
+-----------------------------+----------------+------+

Arah boleh berbeda pada setiap kunci. Keluaran di atas menurun menurut stok, tetapi tetap menaik menurut judul untuk memecah seri stok yang sama. Contoh berikut memakai tiga kunci sekaligus dan menunjukkan bahwa penulisan arah boleh ditinggalkan bila arah menaik yang diinginkan.

bash
# mengurutkan anggota menurut kota, kelas, lalu nama
mysql -t -u root perpustakaan -e "SELECT nama, kota, kelas FROM anggota ORDER BY kota, kelas, nama;"
text Keluaran
+----------------+----------+-------+
| nama           | kota     | kelas |
+----------------+----------+-------+
| Ali Rahman     | Bandung  | 10A   |
| Budi Santoso   | Bandung  | 10A   |
| Fajar Nugroho  | Bandung  | 11B   |
| Eka Putri      | Bogor    | 10A   |
| Dewi Anggraini | Depok    | 10A   |
| Gita Wulandari | Jakarta  | 10A   |
| Citra Lestari  | Jakarta  | 11B   |
| Hadi Pratama   | Semarang | 11B   |
+----------------+----------+-------+

Membatasi baris dengan LIMIT#

Klausa LIMIT membatasi jumlah baris yang dikembalikan, dan bentuk LIMIT ... OFFSET ... melewati sejumlah baris terlebih dahulu. Keduanya menjadi dasar paginasi, yaitu pembagian hasil besar menjadi halaman-halaman kecil.

sql Sintaks LIMIT
-- tidak-dijalankan
SELECT kolom_1, kolom_2
FROM nama_tabel
ORDER BY kolom_1
LIMIT jumlah_baris OFFSET posisi_awal;
bash
# tiga buku terlama menurut urutan tahun
mysql -t -u root perpustakaan -e "SELECT judul, tahun FROM buku ORDER BY tahun ASC, judul ASC LIMIT 3;"
text Keluaran
+---------------------------+-------+
| judul                     | tahun |
+---------------------------+-------+
| Fisika Mekanika Dasar     |  2016 |
| Sejarah Maritim Nusantara |  2017 |
| Jaringan Komputer Dasar   |  2018 |
+---------------------------+-------+

LIMIT bekerja setelah pengurutan selesai, sehingga tiga baris yang tampil benar-benar tiga baris teratas menurut kuncinya. Contoh berikut membagi 8 anggota menjadi halaman berukuran 3 baris, dengan halaman ke-n memakai OFFSET (n-1) * 3.

bash
# halaman pertama: tiga baris pertama
mysql -t -u root perpustakaan -e "SELECT id, nama, kota FROM anggota ORDER BY id LIMIT 3;"
text Keluaran
+----+---------------+---------+
| id | nama          | kota    |
+----+---------------+---------+
|  1 | Ali Rahman    | Bandung |
|  2 | Budi Santoso  | Bandung |
|  3 | Citra Lestari | Jakarta |
+----+---------------+---------+
bash
# halaman kedua: lewati 3 baris, ambil 3 berikutnya
mysql -t -u root perpustakaan -e "SELECT id, nama, kota FROM anggota ORDER BY id LIMIT 3 OFFSET 3;"
text Keluaran
+----+----------------+---------+
| id | nama           | kota    |
+----+----------------+---------+
|  4 | Dewi Anggraini | Depok   |
|  5 | Eka Putri      | Bogor   |
|  6 | Fajar Nugroho  | Bandung |
+----+----------------+---------+
bash
# halaman ketiga: lewati 6 baris, sisanya hanya 2
mysql -t -u root perpustakaan -e "SELECT id, nama, kota FROM anggota ORDER BY id LIMIT 3 OFFSET 6;"
text Keluaran
+----+----------------+----------+
| id | nama           | kota     |
+----+----------------+----------+
|  7 | Gita Wulandari | Jakarta  |
|  8 | Hadi Pratama   | Semarang |
+----+----------------+----------+

Halaman terakhir berisi dua baris karena sisa datanya tinggal dua. MySQL juga menyediakan bentuk pendek yang memadatkan kedua nilai ke dalam satu LIMIT.

sql Bentuk pendek LIMIT posisi, jumlah
-- tidak-dijalankan
SELECT kolom_1, kolom_2
FROM nama_tabel
ORDER BY kolom_1
LIMIT 3, 3;

Pada bentuk pendek, angka pertama adalah posisi awal dan angka kedua adalah jumlah baris, sehingga LIMIT 3, 3 sama dengan LIMIT 3 OFFSET 3. Urutan argumennya terbalik dari bentuk OFFSET, dan pertukaran dua angka tersebut sering menjadi penyebab paginasi yang keliru.

Menghilangkan duplikasi dengan DISTINCT#

Kata kunci DISTINCT menghilangkan baris hasil yang duplikat, yaitu baris yang bernilai sama pada seluruh kolom yang dipilih. Klausa ini berguna untuk menjawab pertanyaan berupa daftar nilai yang unik.

bash
# daftar kota asal anggota tanpa duplikasi
mysql -t -u root perpustakaan -e "SELECT DISTINCT kota FROM anggota ORDER BY kota;"
text Keluaran
+----------+
| kota     |
+----------+
| Bandung  |
| Bogor    |
| Depok    |
| Jakarta  |
| Semarang |
+----------+

Delapan baris anggota tersaring menjadi lima kota yang unik. DISTINCT dinilai atas kombinasi seluruh kolom yang dipilih, bukan atas satu kolom saja.

bash
# kombinasi kota dan kelas yang unik
mysql -t -u root perpustakaan -e "SELECT DISTINCT kota, kelas FROM anggota ORDER BY kota, kelas;"
text Keluaran
+----------+-------+
| kota     | kelas |
+----------+-------+
| Bandung  | 10A   |
| Bandung  | 11B   |
| Bogor    | 10A   |
| Depok    | 10A   |
| Jakarta  | 10A   |
| Jakarta  | 11B   |
| Semarang | 11B   |
+----------+-------+

Pasangan Bandung dan 10A dimiliki dua anggota, yaitu Ali Rahman dan Budi Santoso, tetapi hanya tampil satu kali pada hasil. Contoh berikut memakai DISTINCT untuk menjawab siapa saja yang pernah meminjam buku, tanpa mempedulikan banyaknya pinjaman.

bash
# anggota yang pernah meminjam, tanpa duplikasi
mysql -t -u root perpustakaan -e "SELECT DISTINCT anggota_id FROM peminjaman ORDER BY anggota_id;"
text Keluaran
+------------+
| anggota_id |
+------------+
|          1 |
|          2 |
|          3 |
|          4 |
|          5 |
|          6 |
|          7 |
|          8 |
+------------+

Dua belas baris peminjaman tersaring menjadi delapan peminjam yang unik. Agar hasilnya mudah dibaca, DISTINCT biasanya dipasangkan dengan ORDER BY, dan aturannya ketat, yaitu kolom pengurutan wajib termasuk dalam daftar kolom yang dipilih.

Kesalahan umum#

Mengurutkan DISTINCT menurut kolom yang tidak dipilih. Pengurutan pada query DISTINCT hanya boleh merujuk kolom yang ada di daftar SELECT.

bash
# percobaan yang salah: mengurutkan menurut kolom di luar daftar SELECT
mysql -t -u root perpustakaan -e "SELECT DISTINCT kota FROM anggota ORDER BY nama;"
text Keluaran
ERROR 3065 (HY000) at line 1: Expression #1 of ORDER BY clause is not in SELECT list, references column 'perpustakaan.anggota.nama' which is not in SELECT list; this is incompatible with DISTINCT

Server menolak query tersebut karena mengurutkan menurut nama akan memaksa satu baris hasil untuk memilih salah satu dari banyak nama. Perbaikannya adalah mengurutkan menurut kota yang memang dipilih, atau memasukkan nama ke dalam daftar SELECT.

Salah menghitung OFFSET. Halaman ke-n dengan ukuran k baris memakai OFFSET (n-1) * k. Kesalahan yang sering terjadi adalah memakai n * k, sehingga baris pertama setiap halaman terlewat dan satu baris terakhir hilang. Menghitung ulang rumus offset setiap kali halaman dibangun mencegah kekeliruan ini.

Ringkasan#

  • Tanpa ORDER BY, urutan baris hasil tidak dijamin, sehingga urutan selalu dinyatakan dengan ORDER BY dan arah ASC atau DESC.
  • Kunci pengurutan boleh lebih dari satu, dan kunci berikutnya memecah seri antarbaris yang nilainya sama.
  • LIMIT membatasi jumlah baris dan OFFSET melewatinya, sehingga keduanya menjadi dasar paginasi yang membutuhkan ORDER BY yang stabil.
  • DISTINCT menghilangkan duplikasi atas kombinasi seluruh kolom yang dipilih, dan kolom pengurutannya wajib berada dalam daftar SELECT.

Referensi#