Laporan hampir selalu berpasangan dengan ekstrem: harga termurah dan termahal, transaksi pertama dan terakhir, produk yang paling dulu terjual abjad. Fungsi min dan max menjawabnya dengan satu baris query — dan karena keduanya bekerja pada semua tipe data yang bisa diurutkan, jangkauannya jauh melampaui angka: teks diurutkan leksikografis, tanggal memilih paling awal dan paling akhir. Pasangan ini juga paling toleran terhadap NULL dan himpunan kosong, tetapi tetap ada satu kebiasaan yang harus diantisipasi: keduanya mengembalikan NULL, bukan error, saat tidak ada nilai sama sekali. Kita uji semuanya pada tabel penjualan yang sudah akrab dari chapter COUNT
.
Tabel Latihan: penjualan #
Tabel domain yang sama untuk seluruh section ini — 12 transaksi Toko Maju Jaya, Januari–Februari 2026, dengan diskon kosong pada 6 baris dan kategori kosong pada 2 baris:
-- Tabel latihan utama Section Agregasi Data:
-- transaksi penjualan Toko Maju Jaya, Januari-Februari 2026.
CREATE TABLE penjualan (
id integer PRIMARY KEY,
tanggal date NOT NULL,
penjual text NOT NULL,
kategori text, -- NULL = transaksi belum dikategorikan
produk text NOT NULL,
jumlah integer NOT NULL, -- unit terjual
harga numeric(10,2) NOT NULL, -- harga satuan, rupiah
diskon numeric(5,2) -- NULL = tanpa diskon
);
INSERT INTO penjualan (id, tanggal, penjual, kategori, produk, jumlah, harga, diskon) VALUES
( 1, DATE '2026-01-05', 'Budi', 'Kopi', 'Kopi Gayo', 10, 85000.00, 5.00),
( 2, DATE '2026-01-08', 'Siti', 'Teh', 'Teh Melati', 25, 35000.00, NULL),
( 3, DATE '2026-01-12', 'Andi', 'Gula', 'Gula Merah', 40, 15000.00, NULL),
( 4, DATE '2026-01-15', 'Budi', 'Kopi', 'Kopi Toraja', 8, 90000.00, 10.00),
( 5, DATE '2026-01-20', 'Dewi', NULL, 'Kopi Gayo', 12, 85000.00, NULL),
( 6, DATE '2026-01-22', 'Siti', 'Teh', 'Teh Hijau', 15, 40000.00, 7.50),
( 7, DATE '2026-01-25', 'Andi', 'Gula', 'Gula Merah', 30, 15000.00, 2.50),
( 8, DATE '2026-02-02', 'Budi', NULL, 'Teh Melati', 20, 35000.00, NULL),
( 9, DATE '2026-02-05', 'Dewi', 'Kopi', 'Kopi Gayo', 18, 85000.00, 8.00),
(10, DATE '2026-02-10', 'Siti', 'Teh', 'Teh Melati', 22, 35000.00, NULL),
(11, DATE '2026-02-14', 'Andi', 'Gula', 'Gula Merah', 35, 15000.00, NULL),
(12, DATE '2026-02-18', 'Budi', 'Kopi', 'Kopi Toraja', 6, 90000.00, 12.00);
SELECT id, tanggal, penjual, kategori, produk, jumlah, harga, diskon
FROM penjualan
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 12
id | tanggal | penjual | kategori | produk | jumlah | harga | diskon
----+------------+---------+----------+-------------+--------+----------+--------
1 | 2026-01-05 | Budi | Kopi | Kopi Gayo | 10 | 85000.00 | 5.00
2 | 2026-01-08 | Siti | Teh | Teh Melati | 25 | 35000.00 |
3 | 2026-01-12 | Andi | Gula | Gula Merah | 40 | 15000.00 |
4 | 2026-01-15 | Budi | Kopi | Kopi Toraja | 8 | 90000.00 | 10.00
5 | 2026-01-20 | Dewi | | Kopi Gayo | 12 | 85000.00 |
6 | 2026-01-22 | Siti | Teh | Teh Hijau | 15 | 40000.00 | 7.50
7 | 2026-01-25 | Andi | Gula | Gula Merah | 30 | 15000.00 | 2.50
8 | 2026-02-02 | Budi | | Teh Melati | 20 | 35000.00 |
9 | 2026-02-05 | Dewi | Kopi | Kopi Gayo | 18 | 85000.00 | 8.00
10 | 2026-02-10 | Siti | Teh | Teh Melati | 22 | 35000.00 |
11 | 2026-02-14 | Andi | Gula | Gula Merah | 35 | 15000.00 |
12 | 2026-02-18 | Budi | Kopi | Kopi Toraja | 6 | 90000.00 | 12.00
(12 rows)Terkecil dan Terbesar pada Angka dan Ekspresi #
Bentuk dasarnya dua kata: min(kolom) dan max(kolom). Sama seperti sum, argumennya boleh berupa ekspresi yang dihitung per baris lebih dulu:
-- MIN dan MAX pada kolom angka: harga termurah dan termahal.
SELECT min(harga) AS harga_termurah,
max(harga) AS harga_termahal
FROM penjualan;
-- Argumen boleh ekspresi: nilai transaksi terkecil dan terbesar
-- dari perkalian jumlah x harga per baris.
SELECT min(jumlah * harga) AS nilai_transaksi_terkecil,
max(jumlah * harga) AS nilai_transaksi_terbesar
FROM penjualan;Output yang Dihasilkan:
harga_termurah | harga_termahal
----------------+----------------
15000.00 | 90000.00
(1 row)
nilai_transaksi_terkecil | nilai_transaksi_terbesar
--------------------------+--------------------------
450000.00 | 1530000.00
(1 row)Query pertama memetakan rentang harga satuan: termurah 15000.00 (Gula Merah), termahal 90000.00 (Kopi Toraja). Query kedua menghitung ekspresi jumlah × harga per baris dulu — menghasilkan nilai transaksi — lalu mengambil ekstreminya: terkecil 450000.00 (30 unit Gula Merah milik Andi) dan terbesar 1530000.00 (18 unit Kopi Gayo milik Dewi). Perhatikan bahwa transaksi bernilai terbesar bukan transaksi dengan harga termahal: kuantitas ikut menentukan, dan itulah guna ekspresi sebagai argumen.
Teks dan Tanggal Ikut Diurutkan #
min dan max tidak terbatas pada angka — keduanya bekerja pada semua tipe yang bisa diurutkan dengan ORDER BY, termasuk teks dan tanggal:
-- MIN dan MAX pada teks: urutannya leksikografis (urutan kolasi database),
-- sama seperti urutan ORDER BY pada kolom teks.
SELECT min(produk) AS produk_awal,
max(produk) AS produk_akhir
FROM penjualan;
-- MIN dan MAX pada tanggal: transaksi pertama dan terakhir.
SELECT min(tanggal) AS transaksi_pertama,
max(tanggal) AS transaksi_terakhir
FROM penjualan;
-- MIN dan MAX bekerja pada semua tipe yang bisa diurutkan;
-- penjual pun bisa dicari yang "terkecil" dan "terbesar" menurut urutan teks.
SELECT min(penjual) AS penjual_awal,
max(penjual) AS penjual_akhir
FROM penjualan;Output yang Dihasilkan:
produk_awal | produk_akhir
-------------+--------------
Gula Merah | Teh Melati
(1 row)
transaksi_pertama | transaksi_terakhir
-------------------+--------------------
2026-01-05 | 2026-02-18
(1 row)
penjual_awal | penjual_akhir
--------------+---------------
Andi | Siti
(1 row)Pada teks, min(produk) menghasilkan Gula Merah dan max(produk) menghasilkan Teh Melati — hasil yang identik dengan baris pertama dan terakhir bila kita menjalankan ORDER BY produk. Urutannya leksikografis dan mengikuti collation database; dengan locale C, urutan ditentukan byte demi byte, huruf kapital dan digit mendahului huruf kecil. Pada tanggal, min(tanggal) memilih tanggal paling awal (2026-01-05, transaksi perdana Budi) dan max(tanggal) tanggal paling akhir (2026-02-18). Pola min/max tanggal adalah cara standar menjawab “sejak kapan” dan “terakhir kapan” — riwayat harga, aktivitas pelanggan terakhir, hingga batas periode laporan.
NULL Dilewati, Himpunan Kosong Menghasilkan NULL #
Dua kebiasaan terakhir pasangan ini sejalan dengan sum: NULL diabaikan, dan ketiadaan nilai menghasilkan NULL:
-- MIN dan MAX mengabaikan NULL: diskon terkecil dan terbesar
-- diambil dari 6 baris yang terisi saja.
SELECT count(diskon) AS diskon_terisi,
min(diskon) AS diskon_terkecil,
max(diskon) AS diskon_terbesar
FROM penjualan;
-- Himpunan kosong menghasilkan NULL, bukan error dan bukan 0:
-- tidak ada transaksi setelah Februari 2026.
SELECT min(tanggal) AS transaksi_pertama,
max(tanggal) AS transaksi_terakhir
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Bandingkan dengan count() pada filter yang sama: 0, bukan NULL.
SELECT count(*) AS transaksi
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';Output yang Dihasilkan:
diskon_terisi | diskon_terkecil | diskon_terbesar
---------------+-----------------+-----------------
6 | 2.50 | 12.00
(1 row)
transaksi_pertama | transaksi_terakhir
-------------------+--------------------
|
(1 row)
transaksi
-----------
0
(1 row)Query pertama membuktikan NULL diabaikan: diskon terkecil 2.50 dan terbesar 12.00 diambil dari 6 baris terisi — enam baris tanpa diskon tidak pernah dianggap nol. Query kedua memperlihatkan perilaku himpunan kosong: kedua kolom kosong NULL karena tidak ada satu pun tanggal untuk dibandingkan. Ini berbeda dari count yang tetap mengembalikan 0 — pola tiga perilaku yang kini lengkap gambarnya: count → 0, sum/avg/min/max → NULL.
Termurah per Kategori: Preview GROUP BY #
Kekuatan sebenarnya baru terasa ketika min/max dipasangkan dengan GROUP BY — membagi tabel per kelompok lalu mengambil ekstrem tiap kelompok:
-- Preview GROUP BY: harga termurah dan termahal per kategori.
-- Perhatikan baris kategori kosong — dua transaksi belum dikategorikan.
SELECT kategori,
count(*) AS transaksi,
min(harga) AS termurah,
max(harga) AS termahal
FROM penjualan
GROUP BY kategori
ORDER BY kategori;Output yang Dihasilkan:
kategori | transaksi | termurah | termahal
----------+-----------+----------+----------
Gula | 3 | 15000.00 | 15000.00
Kopi | 4 | 85000.00 | 90000.00
Teh | 3 | 35000.00 | 40000.00
| 2 | 35000.00 | 85000.00
(4 rows)Empat baris hasil meringkas rentang harga tiap kategori: Gula stabil di 15000.00, Kopi bergerak antara 85000.00 dan 90000.00, Teh antara 35000.00 dan 40000.00. Perhatikan baris keempat — kategori yang kosong. NULL pada kolom pengelompokan tidak diabaikan seperti NULL pada agregat, melainkan membentuk kelompok tersendiri; dua transaksi yang belum dikategorikan berkumpul di situ dengan rentang 35000.00 sampai 85000.00. Perilaku menarik ini dibedah tuntas di chapter berikutnya — sekaligus seluruh mekanisme di balik GROUP BY: GROUP BY
.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkas-- Tabel latihan utama Section Agregasi Data:
-- transaksi penjualan Toko Maju Jaya, Januari-Februari 2026.
CREATE TABLE penjualan (
id integer PRIMARY KEY,
tanggal date NOT NULL,
penjual text NOT NULL,
kategori text, -- NULL = transaksi belum dikategorikan
produk text NOT NULL,
jumlah integer NOT NULL, -- unit terjual
harga numeric(10,2) NOT NULL, -- harga satuan, rupiah
diskon numeric(5,2) -- NULL = tanpa diskon
);
INSERT INTO penjualan (id, tanggal, penjual, kategori, produk, jumlah, harga, diskon) VALUES
( 1, DATE '2026-01-05', 'Budi', 'Kopi', 'Kopi Gayo', 10, 85000.00, 5.00),
( 2, DATE '2026-01-08', 'Siti', 'Teh', 'Teh Melati', 25, 35000.00, NULL),
( 3, DATE '2026-01-12', 'Andi', 'Gula', 'Gula Merah', 40, 15000.00, NULL),
( 4, DATE '2026-01-15', 'Budi', 'Kopi', 'Kopi Toraja', 8, 90000.00, 10.00),
( 5, DATE '2026-01-20', 'Dewi', NULL, 'Kopi Gayo', 12, 85000.00, NULL),
( 6, DATE '2026-01-22', 'Siti', 'Teh', 'Teh Hijau', 15, 40000.00, 7.50),
( 7, DATE '2026-01-25', 'Andi', 'Gula', 'Gula Merah', 30, 15000.00, 2.50),
( 8, DATE '2026-02-02', 'Budi', NULL, 'Teh Melati', 20, 35000.00, NULL),
( 9, DATE '2026-02-05', 'Dewi', 'Kopi', 'Kopi Gayo', 18, 85000.00, 8.00),
(10, DATE '2026-02-10', 'Siti', 'Teh', 'Teh Melati', 22, 35000.00, NULL),
(11, DATE '2026-02-14', 'Andi', 'Gula', 'Gula Merah', 35, 15000.00, NULL),
(12, DATE '2026-02-18', 'Budi', 'Kopi', 'Kopi Toraja', 6, 90000.00, 12.00);
SELECT id, tanggal, penjual, kategori, produk, jumlah, harga, diskon
FROM penjualan
ORDER BY id;
min-max-angka.sql
Unduh berkas-- MIN dan MAX pada kolom angka: harga termurah dan termahal.
SELECT min(harga) AS harga_termurah,
max(harga) AS harga_termahal
FROM penjualan;
-- Argumen boleh ekspresi: nilai transaksi terkecil dan terbesar
-- dari perkalian jumlah x harga per baris.
SELECT min(jumlah * harga) AS nilai_transaksi_terkecil,
max(jumlah * harga) AS nilai_transaksi_terbesar
FROM penjualan;
min-max-null.sql
Unduh berkas-- MIN dan MAX mengabaikan NULL: diskon terkecil dan terbesar
-- diambil dari 6 baris yang terisi saja.
SELECT count(diskon) AS diskon_terisi,
min(diskon) AS diskon_terkecil,
max(diskon) AS diskon_terbesar
FROM penjualan;
-- Himpunan kosong menghasilkan NULL, bukan error dan bukan 0:
-- tidak ada transaksi setelah Februari 2026.
SELECT min(tanggal) AS transaksi_pertama,
max(tanggal) AS transaksi_terakhir
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Bandingkan dengan count() pada filter yang sama: 0, bukan NULL.
SELECT count(*) AS transaksi
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
min-max-per-kategori.sql
Unduh berkas-- Preview GROUP BY: harga termurah dan termahal per kategori.
-- Perhatikan baris kategori kosong — dua transaksi belum dikategorikan.
SELECT kategori,
count(*) AS transaksi,
min(harga) AS termurah,
max(harga) AS termahal
FROM penjualan
GROUP BY kategori
ORDER BY kategori;
min-max-teks-tanggal.sql
Unduh berkas-- MIN dan MAX pada teks: urutannya leksikografis (urutan kolasi database),
-- sama seperti urutan ORDER BY pada kolom teks.
SELECT min(produk) AS produk_awal,
max(produk) AS produk_akhir
FROM penjualan;
-- MIN dan MAX pada tanggal: transaksi pertama dan terakhir.
SELECT min(tanggal) AS transaksi_pertama,
max(tanggal) AS transaksi_terakhir
FROM penjualan;
-- MIN dan MAX bekerja pada semua tipe yang bisa diurutkan;
-- penjual pun bisa dicari yang "terkecil" dan "terbesar" menurut urutan teks.
SELECT min(penjual) AS penjual_awal,
max(penjual) AS penjual_akhir
FROM penjualan;