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:

sql buat-tabel.sql
-- 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:

text
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:

text
 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:

text
 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:

text
 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:

text
 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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;