Hasil query yang lolos penyaringan masih tampil dalam urutan apa adanya — MySQL tidak menjamin urutan baris tanpa perintah eksplisit. Laporan harga termurah, daftar produk paling laris, atau halaman katalog semuanya menuntut urutan yang bisa kita tentukan sendiri. Klausa ORDER BY adalah alat MySQL untuk mengatur urutan baris hasil: naik atau turun, satu kolom atau beberapa kolom, bahkan hasil ekspresi hitung. Pada chapter ini kita akan berlatih seluruh variasi itu, membuktikan dua perilaku khas — urutan teks yang tidak peduli huruf besar/kecil serta posisi NULL yang selalu menempati ujung — lalu melihat aturan kerja sama ORDER BY dengan GROUP BY.
Menyiapkan Tabel Latihan #
Kita pakai kembali pola data section ini: tabel produk toko alat tulis dan tabel pelanggan dengan kolom telepon yang boleh NULL:
DROP DATABASE IF EXISTS belajar_filter;
CREATE DATABASE belajar_filter;
USE belajar_filter;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE produk (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
kategori VARCHAR(50) NOT NULL,
harga DECIMAL(10,2) NOT NULL,
stok INT NOT NULL DEFAULT 0
);
INSERT INTO produk (nama, kategori, harga, stok) VALUES
('Buku Tulis Sidu 38', 'Alat Tulis', 4500.00, 120),
('Pensil 2B', 'Alat Tulis', 3000.00, 200),
('Penghapus Putih', 'Alat Tulis', 2500.00, 150),
('Pulpen Gel Hitam', 'Alat Tulis', 5000.00, 80),
('Spidol Permanen', 'Alat Tulis', 10000.00, 45),
('Kertas HVS A4', 'Kertas', 27500.00, 20),
('Kertas Origami', 'Kertas', 12000.00, 60),
('Amplop Coklat', 'Kertas', 800.00, 300),
('Stipo 12 Gram', 'Perekat', 6000.00, 0),
('Selotip Bening', 'Perekat', 8000.00, 55);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0CREATE TABLE pelanggan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
kota VARCHAR(50) NOT NULL,
telepon VARCHAR(20) NULL
);
INSERT INTO pelanggan (nama, kota, telepon) VALUES
('Budi Santoso', 'Bandung', '0812-5000-1001'),
('Siti Rahayu', 'Jakarta', '0813-5000-1002'),
('Andi Wijaya', 'Surabaya', NULL),
('Dewi Lestari', 'Bandung', '0815-5000-1004'),
('Rian Hidayat', 'Yogyakarta', NULL),
('Maya Putri', 'Medan', '0817-5000-1006'),
('Eko Prasetyo', 'Semarang', NULL),
('Fitri Handayani', 'Bandung', '0819-5000-1008');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0Mengurutkan Satu Kolom #
Bentuk paling sederhana: ORDER BY nama_kolom mengurutkan baris menaik (ascending). Kata kunci ASC boleh ditulis eksplisit, tetapi arah menaik adalah nilai baku sehingga ORDER BY harga dan ORDER BY harga ASC identik. Query berikut mengurutkan produk dari harga termurah:
SELECT nama, harga
FROM produk
ORDER BY harga ASC;Output yang Dihasilkan:
+--------------------+----------+
| nama | harga |
+--------------------+----------+
| Amplop Coklat | 800.00 |
| Penghapus Putih | 2500.00 |
| Pensil 2B | 3000.00 |
| Buku Tulis Sidu 38 | 4500.00 |
| Pulpen Gel Hitam | 5000.00 |
| Stipo 12 Gram | 6000.00 |
| Selotip Bening | 8000.00 |
| Spidol Permanen | 10000.00 |
| Kertas Origami | 12000.00 |
| Kertas HVS A4 | 27500.00 |
+--------------------+----------+
10 rows in set (0.00 sec)Amplop Coklat seharga 800 tampil pertama dan Kertas HVS A4 seharga 27500 tampil terakhir. Menambahkan kata kunci DESC membalik arah menjadi menurun (descending) — bentuk yang paling sering dipakai untuk laporan termahal-dulu:
SELECT nama, harga
FROM produk
ORDER BY harga DESC;Output yang Dihasilkan:
+--------------------+----------+
| nama | harga |
+--------------------+----------+
| Kertas HVS A4 | 27500.00 |
| Kertas Origami | 12000.00 |
| Spidol Permanen | 10000.00 |
| Selotip Bening | 8000.00 |
| Stipo 12 Gram | 6000.00 |
| Pulpen Gel Hitam | 5000.00 |
| Buku Tulis Sidu 38 | 4500.00 |
| Pensil 2B | 3000.00 |
| Penghapus Putih | 2500.00 |
| Amplop Coklat | 800.00 |
+--------------------+----------+
10 rows in set (0.00 sec)Urutannya kebalikan penuh dari hasil sebelumnya. Kedua query membaca data yang sama; satu-satunya perubahan adalah arah pengurutan.
Mengurutkan Beberapa Kolom #
Daftar kolom setelah ORDER BY diproses berurutan: kolom pertama menjadi kunci utama, kolom kedua hanya dipakai untuk memecah hasil yang nilainya sama, dan seterusnya. Pola favorit laporan adalah ORDER BY kategori ASC, harga DESC — produk dikelompokkan per kategori, lalu di dalam tiap kategori diurutkan dari termahal:
SELECT nama, kategori, harga
FROM produk
ORDER BY kategori ASC, harga DESC;Output yang Dihasilkan:
+--------------------+------------+----------+
| nama | kategori | harga |
+--------------------+------------+----------+
| Spidol Permanen | Alat Tulis | 10000.00 |
| Pulpen Gel Hitam | Alat Tulis | 5000.00 |
| Buku Tulis Sidu 38 | Alat Tulis | 4500.00 |
| Pensil 2B | Alat Tulis | 3000.00 |
| Penghapus Putih | Alat Tulis | 2500.00 |
| Kertas HVS A4 | Kertas | 27500.00 |
| Kertas Origami | Kertas | 12000.00 |
| Amplop Coklat | Kertas | 800.00 |
| Selotip Bening | Perekat | 8000.00 |
| Stipo 12 Gram | Perekat | 6000.00 |
+--------------------+------------+----------+
10 rows in set (0.00 sec)Mari kita bedah hasilnya per kelompok. Pada kategori Alat Tulis tampil Spidol Permanen (10000), Pulpen Gel Hitam (5000), Buku Tulis Sidu 38 (4500), Pensil 2B (3000), lalu Penghapus Putih (2500) — lima produk terurut harga menurun. Pada kategori Kertas, Kertas HVS A4 (27500) mendahului Kertas Origami (12000) dan Amplop Coklat (800). Arah tiap kolom diatur sendiri-sendiri: ASC pada kategori mengelompokkan secara alfabetis, DESC pada harga mengurutkan termahal-dulu di dalam tiap kelompok. Bila seluruh kolom cukup satu arah yang sama, cukup tulis ORDER BY kategori, harga atau ORDER BY kategori DESC, harga DESC.
Mengurutkan Berdasarkan Alias dan Ekspresi #
ORDER BY tidak terbatas pada kolom tabel. Kita bisa mengurutkan berdasarkan alias yang didefinisikan di daftar SELECT maupun berdasarkan ekspresi hitung apa pun. Contoh klasiknya: nilai persediaan per produk — hasil harga * stok — diurutkan dari yang terbesar:
SELECT nama, harga, stok, harga * stok AS nilai_persediaan
FROM produk
ORDER BY nilai_persediaan DESC;Output yang Dihasilkan:
+--------------------+----------+------+------------------+
| nama | harga | stok | nilai_persediaan |
+--------------------+----------+------+------------------+
| Kertas Origami | 12000.00 | 60 | 720000.00 |
| Pensil 2B | 3000.00 | 200 | 600000.00 |
| Kertas HVS A4 | 27500.00 | 20 | 550000.00 |
| Buku Tulis Sidu 38 | 4500.00 | 120 | 540000.00 |
| Spidol Permanen | 10000.00 | 45 | 450000.00 |
| Selotip Bening | 8000.00 | 55 | 440000.00 |
| Pulpen Gel Hitam | 5000.00 | 80 | 400000.00 |
| Penghapus Putih | 2500.00 | 150 | 375000.00 |
| Amplop Coklat | 800.00 | 300 | 240000.00 |
| Stipo 12 Gram | 6000.00 | 0 | 0.00 |
+--------------------+----------+------+------------------+
10 rows in set (0.00 sec)Kertas Origami menempati puncak: 60 lembar stok dikali 12000 menghasilkan persediaan 720000, mengungguli Pensil 2B (600000) dan Kertas HVS A4 (550000). Stipo 12 Gram jatuh ke posisi terakhir karena stoknya nol membuat hasil perkaliannya nol. Perhatikan bahwa nilai_persediaan adalah alias — MySQL mengevaluasi daftar SELECT sebelum ORDER BY, sehingga alias boleh dipakai sebagai kunci urutan; inilah satu-satunya klausa selain HAVING yang mengizinkannya (perbandingan di WHERE justru menolak alias).
Ekspresi juga bisa ditulis langsung di ORDER BY tanpa melewati alias. Kedua query berikut menghasilkan urutan yang sama persis:
SELECT nama, harga, stok
FROM produk
ORDER BY harga * stok DESC;Output yang Dihasilkan:
+--------------------+----------+------+
| nama | harga | stok |
+--------------------+----------+------+
| Kertas Origami | 12000.00 | 60 |
| Pensil 2B | 3000.00 | 200 |
| Kertas HVS A4 | 27500.00 | 20 |
| Buku Tulis Sidu 38 | 4500.00 | 120 |
| Spidol Permanen | 10000.00 | 45 |
| Selotip Bening | 8000.00 | 55 |
| Pulpen Gel Hitam | 5000.00 | 80 |
| Penghapus Putih | 2500.00 | 150 |
| Amplop Coklat | 800.00 | 300 |
| Stipo 12 Gram | 6000.00 | 0 |
+--------------------+----------+------+
10 rows in set (0.00 sec)Bentuk tanpa alias berguna ketika nilai hasil hitung tidak perlu ikut ditampilkan — urutan tetap dipandu ekspresi, sedangkan kolom hasil hanya berisi data mentah yang lebih ringkas.
Urutan Teks: Huruf Besar dan Kecil #
Pengurutan kolom teks memakai collation kolom tersebut. Collation baku MySQL 8 (utf8mb4_0900_ai_ci) memiliki akhiran _ci — case-insensitive — sehingga huruf besar dan kecil dianggap setara saat membandingkan. Mari kita buktikan dengan perbandingan langsung antar string:
SELECT 'apel' < 'Jeruk' AS apel_dulu, 'A' = 'a' AS huruf_sama;Output yang Dihasilkan:
+-----------+------------+
| apel_dulu | huruf_sama |
+-----------+------------+
| 1 | 1 |
+-----------+------------+
1 row in set (0.00 sec)Hasil apel_dulu bernilai 1 (benar): ‘apel’ dianggap mendahului ‘Jeruk’ karena huruf a mendahului huruf j — fakta bahwa ‘apel’ ditulis huruf kecil dan ‘Jeruk’ diawali huruf besar tidak berpengaruh. Kolom huruf_sama juga bernilai 1 karena 'A' dan 'a' dianggap sama.
Sekarang kita lihat efeknya pada pengurutan data nyata. Kita tambahkan sementara satu produk yang namanya ditulis semua huruf kecil:
INSERT INTO produk (nama, kategori, harga, stok) VALUES
('pensil warna 12', 'Alat Tulis', 9000.00, 35);
SELECT nama, kategori
FROM produk
ORDER BY nama;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
+--------------------+------------+
| nama | kategori |
+--------------------+------------+
| Amplop Coklat | Kertas |
| Buku Tulis Sidu 38 | Alat Tulis |
| Kertas HVS A4 | Kertas |
| Kertas Origami | Kertas |
| Penghapus Putih | Alat Tulis |
| Pensil 2B | Alat Tulis |
| pensil warna 12 | Alat Tulis |
| Pulpen Gel Hitam | Alat Tulis |
| Selotip Bening | Perekat |
| Spidol Permanen | Alat Tulis |
| Stipo 12 Gram | Perekat |
+--------------------+------------+
11 rows in set (0.00 sec)Produk ‘pensil warna 12’ tidak melompat ke ujung baris-baris berawalan huruf P kapital; produk tersebut nyempil di antara Pensil 2B dan Pulpen Gel Hitam sesuai urutan alfabetis murni — P-e-n-g lebih kecil daripada P-e-n-s, yang kemudian lebih kecil daripada P-u-l. Urutan teks mengikuti nilai hurufnya, bukan penampilan huruf besarnya.
Posisi NULL dalam Urutan #
Bagaimana ORDER BY memperlakukan NULL yang sudah kita kenal di chapter sebelumnya? MySQL menempatkan NULL paling awal saat mengurutkan menaik dan paling akhir saat menurun. Kita buktikan pada kolom telepon pelanggan:
SELECT id, nama, telepon
FROM pelanggan
ORDER BY telepon ASC;Output yang Dihasilkan:
+----+-----------------+----------------+
| id | nama | telepon |
+----+-----------------+----------------+
| 3 | Andi Wijaya | NULL |
| 5 | Rian Hidayat | NULL |
| 7 | Eko Prasetyo | NULL |
| 1 | Budi Santoso | 0812-5000-1001 |
| 2 | Siti Rahayu | 0813-5000-1002 |
| 4 | Dewi Lestari | 0815-5000-1004 |
| 6 | Maya Putri | 0817-5000-1006 |
| 8 | Fitri Handayani | 0819-5000-1008 |
+----+-----------------+----------------+
8 rows in set (0.00 sec)Tiga pelanggan dengan telepon NULL — Andi, Rian, Eko — tampil duluan, disusul lima pelanggan yang nomornya terurut menaik. Balik arahnya, dan NULL pindah ke ujung sebaliknya:
SELECT id, nama, telepon
FROM pelanggan
ORDER BY telepon DESC;Output yang Dihasilkan:
+----+-----------------+----------------+
| id | nama | telepon |
+----+-----------------+----------------+
| 8 | Fitri Handayani | 0819-5000-1008 |
| 6 | Maya Putri | 0817-5000-1006 |
| 4 | Dewi Lestari | 0815-5000-1004 |
| 2 | Siti Rahayu | 0813-5000-1002 |
| 1 | Budi Santoso | 0812-5000-1001 |
| 3 | Andi Wijaya | NULL |
| 5 | Rian Hidayat | NULL |
| 7 | Eko Prasetyo | NULL |
+----+-----------------+----------------+
8 rows in set (0.00 sec)NULL diperlakukan sebagai nilai paling kecil dalam pengurutan MySQL — lebih kecil dari nilai apa pun — sehingga arah menaik menempatkan baris NULL di awal dan arah menurun menempatkannya di akhir. Perilaku ini tidak sama di semua database; beberapa menyediakan opsi NULLS FIRST/NULLS LAST untuk mengaturnya, sedangkan MySQL tidak. Bila posisi NULL perlu dikendalikan, trik yang lazim adalah mengurutkan ekspresi penanda terlebih dulu, misalnya ORDER BY telepon IS NULL, telepon — baris bernilai NULL (penanda 1) akan terdorong setelah baris yang terisi (penanda 0).
ORDER BY Bersama GROUP BY #
ORDER BY juga bekerja pada query ber-GROUP BY — peringkat jumlah produk per kategori adalah contohnya. Fungsi agregat COUNT(*) akan dibahas tuntas di Section Agregasi; di sini kita cukup memakainya untuk menghitung anggota tiap kelompok:
SELECT kategori, COUNT(*) AS jumlah_produk
FROM produk
GROUP BY kategori
ORDER BY kategori;Output yang Dihasilkan:
+------------+---------------+
| kategori | jumlah_produk |
+------------+---------------+
| Alat Tulis | 6 |
| Kertas | 3 |
| Perekat | 2 |
+------------+---------------+
3 rows in set (0.00 sec)Enam produk Alat Tulis, tiga produk Kertas, dan dua produk Perekat tampil terurut menurut kategori — kolom yang sama dengan kolom GROUP BY. Mengurutkan kolom pengelompokan adalah pola yang aman karena nilainya tunggal per kelompok, jadi tidak ada keambiguan urutan. Mengurutkan alias agregat juga sah, misalnya ORDER BY jumlah_produk DESC untuk menampilkan kategori terbesar lebih dulu.
Jebakannya muncul ketika ORDER BY menyebut kolom biasa yang bukan kolom pengelompokan dan bukan bagian agregat. Mode baku MySQL 8.4 (ONLY_FULL_GROUP_BY) menolaknya karena satu kelompok memuat banyak nilai kolom yang berbeda — nilai mana yang dipakai untuk mengurutkan tidak terdefinisi:
-- @error
SELECT kategori, COUNT(*) FROM produk GROUP BY kategori ORDER BY stok;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1055 (42000): Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'belajar_filter.produk.stok' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
ByeServer menolak query dengan galat ERROR 1055: kolom stok tidak ada di klausa GROUP BY dan bukan kolom agregat. Solusinya selalu eksplisit: masukkan kolom ke GROUP BY, bungkus dengan fungsi agregat seperti MIN(stok), atau hapus kolom tersebut dari ORDER BY.
Membersihkan Database Latihan #
Latihan pengurutan selesai. Kita jatuhkan database latihan bersama kedua tabelnya:
DROP DATABASE belajar_filter;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)Referensi #
- SELECT Statement — MySQL 8.4 Reference Manual
- Character Sets and Collations — MySQL 8.4 Reference Manual
- Server SQL Modes — MySQL 8.4 Reference Manual
Urutan hasil kini sepenuhnya dalam kendali kita: satu kolom, beberapa kolom, alias, hingga ekspresi hitung. Pertanyaan berikutnya praktis sekaligus lazim: bagaimana menampilkan hanya sebagian hasil — sepuluh produk teratas, halaman katalog per tiga baris, lima pelanggan pertama dari daftar terurut? Chapter berikutnya membahas LIMIT dan OFFSET, pasangan klausa yang memotong hasil query menjadi ukuran yang kita tentukan.