Laporan manajemen jarang berhenti pada total dan rata-rata; mereka menanyakan pencilan: transaksi terbesar minggu ini, produk termurah di katalog, tanggal penjualan pertama kali. Dua fungsi agregasi kembar menjawabnya: MIN mengembalikan nilai terkecil dan MAX mengembalikan nilai terbesar dari sekumpulan baris. Keduanya bekerja bukan hanya pada angka, melainkan juga pada tanggal dan teks — dengan aturan perbandingan yang mengikuti tipe data dan collation kolom. Pada chapter ini kita akan menguji ketiga jenis tipe itu, membuktikan bahwa MIN dan MAX mengabaikan NULL, lalu belajar satu pola subquery sederhana untuk menjawab pertanyaan yang paling sering diajukan setelah tahu nilai ekstremnya: baris mana yang memuat nilai itu?
Menyiapkan Tabel Latihan #
Kita pakai tabel penjualan dua belas baris yang sudah akrab, dengan kolom pelanggan yang boleh NULL pada lima baris:
DROP DATABASE IF EXISTS belajar_agregasi;
CREATE DATABASE belajar_agregasi;
USE belajar_agregasi;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE penjualan (
id INT AUTO_INCREMENT PRIMARY KEY,
tanggal DATE NOT NULL,
kota VARCHAR(50) NOT NULL,
produk VARCHAR(50) NOT NULL,
jumlah INT NOT NULL,
harga DECIMAL(10,2) NOT NULL,
pelanggan VARCHAR(50) NULL
);
INSERT INTO penjualan (tanggal, kota, produk, jumlah, harga, pelanggan) VALUES
('2026-01-05', 'Jakarta', 'Kopi Gayo', 2, 55000.00, 'Budi'),
('2026-01-05', 'Bandung', 'Teh Melati', 3, 25000.00, 'Siti'),
('2026-01-06', 'Jakarta', 'Teh Melati', 1, 25000.00, NULL),
('2026-01-07', 'Surabaya', 'Kopi Toraja', 5, 62000.00, 'Andi'),
('2026-01-08', 'Bandung', 'Gula Aren', 4, 15000.00, NULL),
('2026-01-09', 'Jakarta', 'Gula Aren', 2, 15000.00, 'Rina'),
('2026-01-10', 'Yogyakarta', 'Kopi Gayo', 3, 55000.00, NULL),
('2026-01-11', 'Surabaya', 'Teh Melati', 2, 25000.00, 'Budi'),
('2026-01-12', 'Yogyakarta', 'Kopi Toraja', 1, 62000.00, NULL),
('2026-01-13', 'Jakarta', 'Kopi Toraja', 4, 62000.00, 'Siti'),
('2026-01-14', 'Bandung', 'Kopi Gayo', 1, 55000.00, NULL),
('2026-01-15', 'Surabaya', 'Gula Aren', 6, 15000.00, 'Rina');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 12 rows affected (0.00 sec)
Records: 12 Duplicates: 0 Warnings: 0Rentang nilainya kita desain lebar namun mudah diingat: jumlah berkisar dari 1 sampai 6, harga dari 15000.00 sampai 62000.00, dan tanggal dari awal hingga pertengahan Januari 2026. Nilai pelanggan terisi pada tujuh baris dengan empat nama berbeda, sehingga kita juga bisa mengamati perilaku MIN/MAX pada data teks yang tidak lengkap.
Nilai Terkecil dan Terbesar untuk Angka #
Mari mulai dari kolom angka:
SELECT MIN(jumlah) AS jumlah_terkecil,
MAX(jumlah) AS jumlah_terbesar,
MIN(harga) AS harga_termurah,
MAX(harga) AS harga_termahal
FROM penjualan;Output yang Dihasilkan:
+-----------------+-----------------+----------------+----------------+
| jumlah_terkecil | jumlah_terbesar | harga_termurah | harga_termahal |
+-----------------+-----------------+----------------+----------------+
| 1 | 6 | 15000.00 | 62000.00 |
+-----------------+-----------------+----------------+----------------+
1 row in set (0.00 sec)Hasilnya 1, 6, 15000.00, dan 62000.00 — masing-masing satu nilai tunggal meskipun tabel berisi dua belas baris. Perhatikan bahwa tipe hasil mengikuti kolomnya: agregasi kolom INT menghasilkan bilangan bulat, agregasi kolom DECIMAL(10,2) menghasilkan nilai dua desimal. Berbeda dari AVG yang selalu menambah skala empat digit, MIN dan MAX tidak pernah mengubah nilai — keduanya hanya memilih satu nilai yang memang sudah tersimpan, sehingga formatnya identik dengan isi kolom aslinya.
Ekstrem pada Tanggal #
Untuk kolom tanggal, perbandingan berjalan kronologis — tanggal lebih awal dianggap “lebih kecil”:
SELECT MIN(tanggal) AS transaksi_pertama,
MAX(tanggal) AS transaksi_terakhir
FROM penjualan;Output yang Dihasilkan:
+-------------------+--------------------+
| transaksi_pertama | transaksi_terakhir |
+-------------------+--------------------+
| 2026-01-05 | 2026-01-15 |
+-------------------+--------------------+
1 row in set (0.00 sec)Hasilnya 2026-01-05 dan 2026-01-15, tepat tanggal baris pertama dan terakhir yang kita sisipkan. Pola MAX(tanggal) adalah cara standar menjawab “kapan terakhir kali terjadi transaksi?” pada tabel riwayat — jauh lebih ringkas daripada mengurutkan seluruh tabel lalu membaca baris terakhir. Perilaku ini juga berlaku untuk tipe waktu lain seperti DATETIME dan TIMESTAMP, karena seluruh tipe tanggal-waktu dibandingkan secara kronologis.
Ekstrem pada Teks dan Peran Collation #
MIN dan MAX juga bekerja pada kolom teks. Perbandingannya mengikuti collation kolom:
SELECT MIN(kota) AS kota_awal,
MAX(kota) AS kota_akhir,
MIN(produk) AS produk_awal,
MAX(produk) AS produk_akhir
FROM penjualan;Output yang Dihasilkan:
+-----------+------------+-------------+--------------+
| kota_awal | kota_akhir | produk_awal | produk_akhir |
+-----------+------------+-------------+--------------+
| Bandung | Yogyakarta | Gula Aren | Teh Melati |
+-----------+------------+-------------+--------------+
1 row in set (0.00 sec)Hasilnya Bandung dan Yogyakarta untuk kota, serta Gula Aren dan Teh Melati untuk produk — persis seperti urutan abjad. Perbandingan string berjalan karakter demi karakter mengikuti collation kolom, dan collation bawaan server kita (utf8mb4_0900_ai_ci) bersifat case-insensitive: 'bandung' dan 'Bandung' dianggap sama besar sehingga urutan hasil tidak tergantung besar-kecil huruf. Akibatnya, bila dua nilai hanya berbeda kapitalisasi, MIN/MAX mengembalikan salah satu sesuai urutan internal collation — detail yang jarang terasa, namun penting diketahui saat data teks dicampur kapitalisasinya.
NULL Diabaikan, Hasil Kosong Menghasilkan NULL #
Fungsi MIN dan MAX mengabaikan NULL sama seperti SUM dan AVG. Kita buktikan pada kolom pelanggan yang berisi lima nilai NULL:
SELECT MIN(pelanggan) AS pelanggan_awal,
MAX(pelanggan) AS pelanggan_akhir
FROM penjualan;Output yang Dihasilkan:
+----------------+-----------------+
| pelanggan_awal | pelanggan_akhir |
+----------------+-----------------+
| Andi | Siti |
+----------------+-----------------+
1 row in set (0.00 sec)Hasilnya Andi dan Siti — diambil dari tujuh nama yang tersedia, tanpa NULL ikut dipertimbangkan sebagai nilai terkecil maupun terbesar. Sebaliknya, ketika tidak ada baris yang cocok sama sekali, hasilnya NULL, mengikuti perilaku SUM:
SELECT MIN(jumlah) AS jumlah_terkecil_semarang
FROM penjualan
WHERE kota = 'Semarang';Output yang Dihasilkan:
+--------------------------+
| jumlah_terkecil_semarang |
+--------------------------+
| NULL |
+--------------------------+
1 row in set (0.00 sec)Query kota Semarang menghasilkan NULL karena tidak ada satu baris pun yang lolos filter. Perilaku ini konsisten dengan semantik fungsi agregasi non-COUNT: himpunan kosong tidak memiliki nilai terkecil, sehingga jawabannya “tidak ada jawaban”. Sama seperti pada SUM, fungsi COALESCE(MIN(...), ...) siap dipakai bila aplikasi Anda menghendaki nilai pengganti.
Menemukan Baris yang Memuat Nilai Ekstrem #
Mengetahui harga termahal 62000.00 biasanya langsung memunculkan pertanyaan lanjutan: transaksi mana saja yang harganya setinggi itu? MAX saja tidak menjawabnya karena hasilnya satu nilai, bukan baris. Pola standarnya adalah memfilter baris dengan nilai yang sama dengan hasil agregat — dan di sinilah kita berkenalan singkat dengan subquery:
SELECT id, tanggal, kota, produk, jumlah, harga
FROM penjualan
WHERE harga = (SELECT MAX(harga) FROM penjualan)
ORDER BY id;Output yang Dihasilkan:
+----+------------+------------+-------------+--------+----------+
| id | tanggal | kota | produk | jumlah | harga |
+----+------------+------------+-------------+--------+----------+
| 4 | 2026-01-07 | Surabaya | Kopi Toraja | 5 | 62000.00 |
| 9 | 2026-01-12 | Yogyakarta | Kopi Toraja | 1 | 62000.00 |
| 10 | 2026-01-13 | Jakarta | Kopi Toraja | 4 | 62000.00 |
+----+------------+------------+-------------+--------+----------+
3 rows in set (0.00 sec)Tiga baris Kopi Toraja dengan harga 62000.00 muncul. Cara membacanya: MySQL menjalankan query dalam kurung terlebih dahulu — (SELECT MAX(harga) FROM penjualan) menghasilkan satu nilai 62000.00 — lalu nilai tersebut menggantikan subquery pada kondisi WHERE harga = ... milik query luar. Karena harga termahal dimiliki tiga baris, ketiganya tampil; pola ini tidak mengasumsikan nilai ekstrem hanya ada pada satu baris. Kita baru mencicipi subquery di sini; Section Subquery akan membedah teknik ini sampai tuntas nanti.
Membersihkan Database Latihan #
Blok latihan terakhir kita seperti biasa:
DROP DATABASE belajar_agregasi;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
Lima fungsi agregasi dasar kini lengkap kita kuasai: COUNT, SUM, AVG, MIN, dan MAX. Namun semuanya masih merangkum seluruh tabel menjadi satu baris hasil, padahal laporan nyata hampir selalu bertingkat: total per kota, rata-rata per produk, jumlah per bulan. Chapter berikutnya membahas GROUP BY — mekanisme yang memecah tabel menjadi kelompok-kelompok lalu menerapkan agregasi pada tiap kelompok.