Aturan bisnis hampir selalu bersyarat: pelanggan dengan stok nol ditandai “habis”, harga diskon berbeda antara minuman dan bahan pokok, dan laporan mingguan ingin barang kritis tampil paling atas. Di bahasa pemrograman biasa, logika seperti itu hidup di if/else — tetapi query SQL harus menghasilkan nilai dalam satu ekspresi. PostgreSQL menjawabnya dengan CASE WHEN ... THEN ... ELSE ... END, konstruksi bersyarat standar SQL yang bisa dipakai di hampir semua tempat yang menerima nilai: daftar SELECT, ORDER BY, WHERE, sampai UPDATE. Chapter ini membedah dua bentuk CASE, menunjukkan posisi-posisi pemakaiannya, dan menutup dengan pola CASE di dalam fungsi agregat — jembatan menuju section Agregasi Data.

Tabel Latihan #

Lima produk berikut cukup untuk mewakili semua skenario: ada stok habis, stok menipis, dan tiga kategori yang berbeda:

sql buat-tabel.sql
CREATE TABLE produk (
    id        integer PRIMARY KEY,
    nama      text NOT NULL,
    kategori  text NOT NULL,
    stok      integer NOT NULL,
    harga     numeric(12,2) NOT NULL
);

INSERT INTO produk (id, nama, kategori, stok, harga) VALUES
    (1, 'Kopi Gayo', 'minuman', 0, 85000.00),
    (2, 'Teh Melati', 'minuman', 4, 35000.00),
    (3, 'Gula Merah', 'bahan pokok', 25, 15000.00),
    (4, 'Beras Pandan Wangi', 'bahan pokok', 12, 72000.00),
    (5, 'Susu Kedelai 1 Liter', 'minuman', 6, 28000.00);

SELECT id, nama, kategori, stok, harga
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 5
 id |         nama         |  kategori   | stok |  harga   
----+----------------------+-------------+------+----------
  1 | Kopi Gayo            | minuman     |    0 | 85000.00
  2 | Teh Melati           | minuman     |    4 | 35000.00
  3 | Gula Merah           | bahan pokok |   25 | 15000.00
  4 | Beras Pandan Wangi   | bahan pokok |   12 | 72000.00
  5 | Susu Kedelai 1 Liter | minuman     |    6 | 28000.00
(5 rows)

Kolom yang akan kita perlakukan adalah stok (0 untuk Kopi Gayo, 4 untuk Teh Melati) dan kategori (minuman versus bahan pokok). Semua contoh di bawah mengambil keputusan dari kedua kolom itu.

Bentuk Pencarian: CASE WHEN Kondisi THEN Hasil #

Bentuk pertama dan paling lentur disebut searched CASE: setiap cabang membawa kondisi lengkapnya sendiri, dan cabang pertama yang bernilai true menang. Struktur umumnya CASE WHEN kondisi THEN hasil ... ELSE hasil_default END:

-- bentuk pencarian (searched CASE): kondisi bebas di tiap cabang
SELECT nama, stok,
       CASE
           WHEN stok = 0 THEN 'habis'
           WHEN stok <= 5 THEN 'menipis'
           ELSE 'aman'
       END AS status_stok
FROM produk
ORDER BY id;

-- tanpa ELSE: baris yang tidak lolos kondisi apa pun menghasilkan NULL
SELECT nama, stok,
       CASE WHEN stok = 0 THEN 'habis' END AS label_habis
FROM produk
ORDER BY id;

-- CASE bisa mengembalikan angka, bukan hanya teks
SELECT nama, harga,
       CASE
           WHEN stok = 0 THEN 0
           WHEN stok <= 5 THEN 10
           ELSE 5
       END AS diskon_persen
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
         nama         | stok | status_stok 
----------------------+------+-------------
 Kopi Gayo            |    0 | habis
 Teh Melati           |    4 | menipis
 Gula Merah           |   25 | aman
 Beras Pandan Wangi   |   12 | aman
 Susu Kedelai 1 Liter |    6 | aman
(5 rows)

         nama         | stok | label_habis 
----------------------+------+-------------
 Kopi Gayo            |    0 | habis
 Teh Melati           |    4 | 
 Gula Merah           |   25 | 
 Beras Pandan Wangi   |   12 | 
 Susu Kedelai 1 Liter |    6 | 
(5 rows)

         nama         |  harga   | diskon_persen 
----------------------+----------+---------------
 Kopi Gayo            | 85000.00 |             0
 Teh Melati           | 35000.00 |            10
 Gula Merah           | 15000.00 |             5
 Beras Pandan Wangi   | 72000.00 |             5
 Susu Kedelai 1 Liter | 28000.00 |             5
(5 rows)

Mari kita bedah perilaku-perilaku kuncinya:

  1. Cabang dievaluasi berurutan dari atas: Kopi Gayo dengan stok = 0 berhenti di cabang pertama dan berlabel 'habis'; Teh Melati dengan stok = 4 lolos cabang pertama (kondisi salah) lalu tertangkap cabang kedua 'menipis'. Urutan cabang adalah urutan prioritas — tukar dua cabang pertama dan hasilnya berubah.
  2. Tanpa ELSE, hasilnya NULL: query kedua hanya memberi label pada stok nol; keempat produk lainnya menampilkan kolom label_habis yang kosong. Perilaku ini kembali ke hukum NULL dari chapter Fungsi NULL — jika NULL tidak diinginkan, selalu tulis cabang ELSE, atau bungkus dengan COALESCE.
  3. Hasil cabang boleh angka: kolom diskon_persen mengembalikan 0, 10, atau 5 — nilai yang langsung bisa dihitung, misalnya harga * (100 - diskon_persen) / 100.0. Satu aturan tegas: seluruh cabang harus menghasilkan tipe yang kompatibel — campuran 'habis' dan 10 dalam satu CASE akan ditolak server.

Bentuk Sederhana: CASE Ekspresi WHEN Nilai #

Bentuk kedua, simple CASE, membandingkan satu ekspresi terhadap daftar nilai — lebih ringkas ketika kondisinya memang perbandingan kesamaan:

-- bentuk sederhana (simple CASE): satu ekspresi dibandingkan ke daftar nilai
SELECT nama, kategori,
       CASE kategori
           WHEN 'minuman' THEN 'rak dingin'
           WHEN 'bahan pokok' THEN 'rak utama'
           ELSE 'rak umum'
       END AS lokasi_rak
FROM produk
ORDER BY id;

-- NULLIF adalah CASE yang dibekukan menjadi fungsi
SELECT NULLIF('sama', 'sama') AS nullif_langsung,
       CASE WHEN 'sama' = 'sama' THEN NULL ELSE 'sama' END AS case_padanan;

SELECT NULLIF('beda', 'sama') AS nullif_beda,
       CASE WHEN 'beda' = 'sama' THEN NULL ELSE 'beda' END AS case_beda;

Output yang Dihasilkan:

text
         nama         |  kategori   | lokasi_rak 
----------------------+-------------+------------
 Kopi Gayo            | minuman     | rak dingin
 Teh Melati           | minuman     | rak dingin
 Gula Merah           | bahan pokok | rak utama
 Beras Pandan Wangi   | bahan pokok | rak utama
 Susu Kedelai 1 Liter | minuman     | rak dingin
(5 rows)

 nullif_langsung | case_padanan 
-----------------+--------------
                 | 
(1 row)

 nullif_beda | case_beda 
-------------+-----------
 beda        | beda
(1 row)

Perbedaan kedua bentuk terletak pada posisi ekspresi: pada bentuk sederhana, kategori ditulis sekali setelah kata CASE, lalu tiap cabang hanya menyebut nilai pembandingnya — WHEN 'minuman' berarti kategori = 'minuman'. Gunakan bentuk pencarian saat kondisi beragam (stok = 0, stok <= 5), dan bentuk sederhana saat semua kondisi adalah kesamaan terhadap satu kolom. Dua query berikutnya membuktikan hubungan kekerabatan yang menarik: NULLIF('sama', 'sama') menghasilkan nilai yang persis sama dengan CASE WHEN 'sama' = 'sama' THEN NULL ELSE 'sama' END — NULLIF memang CASE dua cabang yang dibekukan menjadi fungsi. Sebaliknya, fungsi GREATEST dan LEAST yang dibahas di chapter Fungsi NULL bisa dipandang sebagai CASE pilihan nilai ekstrem: GREATEST(a, b) setara memilih a jika a >= b, selain b.

CASE di ORDER BY, WHERE, dan UPDATE #

Kekuatan CASE yang sesungguhnya muncul ketika ia keluar dari daftar SELECT. Tiga posisi berikut adalah yang paling sering dipakai di dunia nyata:

-- CASE di ORDER BY: urutan khusus yang bukan urutan alfabetis
SELECT nama, kategori
FROM produk
ORDER BY CASE kategori
             WHEN 'bahan pokok' THEN 1
             WHEN 'minuman' THEN 2
             ELSE 3
         END,
         nama;

-- CASE di WHERE: saring baris berdasarkan label hasil CASE
SELECT nama, stok,
       CASE WHEN stok <= 5 THEN 'perlu restok' ELSE 'cukup' END AS status_restok
FROM produk
WHERE CASE WHEN stok <= 5 THEN 'perlu restok' ELSE 'cukup' END = 'perlu restok'
ORDER BY nama;

-- CASE di UPDATE: aturan harga berbeda per kategori dalam satu pernyataan
UPDATE produk
SET harga = CASE kategori
                WHEN 'minuman' THEN round(harga * 0.90, 2)
                ELSE round(harga * 0.95, 2)
            END;

SELECT nama, kategori, harga
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
         nama         |  kategori   
----------------------+-------------
 Beras Pandan Wangi   | bahan pokok
 Gula Merah           | bahan pokok
 Kopi Gayo            | minuman
 Susu Kedelai 1 Liter | minuman
 Teh Melati           | minuman
(5 rows)

    nama    | stok | status_restok 
------------+------+---------------
 Kopi Gayo  |    0 | perlu restok
 Teh Melati |    4 | perlu restok
(2 rows)

UPDATE 5
         nama         |  kategori   |  harga   
----------------------+-------------+----------
 Kopi Gayo            | minuman     | 76500.00
 Teh Melati           | minuman     | 31500.00
 Gula Merah           | bahan pokok | 14250.00
 Beras Pandan Wangi   | bahan pokok | 68400.00
 Susu Kedelai 1 Liter | minuman     | 25200.00
(5 rows)
  1. ORDER BY CASE: bahan pokok diberi kunci 1, minuman 2 — daftar hasil terurut sesuai prioritas bisnis, bukan abjad. Tie-break nama di belakangnya menjaga urutan tetap stabil di dalam tiap kelompok.
  2. WHERE CASE ... = 'perlu restok': secara teknis sah — CASE menghasilkan teks, teks dibandingkan, baris tersaring. Secara gaya, ini biasanya keputusan yang buruk: kondisi stok <= 5 ditulis langsung di WHERE akan jauh lebih jelas dan lebih mudah dioptimalkan server. Pemakaian yang sah muncul saat kondisi disusun dinamis dari data lain.
  3. UPDATE ... SET harga = CASE ... END: inilah pemakaian CASE yang paling menghemat kerja — satu pernyataan UPDATE menurunkan harga minuman 10% (85000.00 menjadi 76500.00) dan selain minuman 5% (72000.00 menjadi 68400.00). Tanpa CASE, Anda perlu dua UPDATE dengan dua WHERE yang saling melengkapi — dan dua kali pekerjaan pembacaan tabel.

CASE di Dalam Agregat: Pratinjau #

Satu posisi lagi menutup chapter ini dan sekaligus membuka pintu ke section berikutnya: CASE di dalam fungsi agregat. Pola count(CASE WHEN kondisi THEN 1 END) menghitung baris per kondisi dalam satu kali pembacaan tabel:

-- pola pivot mini: CASE di dalam agregat menghitung per kategori sekaligus
SELECT count(*) AS total_produk,
       count(CASE WHEN stok = 0 THEN 1 END) AS produk_habis,
       count(CASE WHEN stok > 0 AND stok <= 5 THEN 1 END) AS produk_menipis,
       count(CASE WHEN kategori = 'minuman' THEN 1 END) AS produk_minuman
FROM produk;

Output yang Dihasilkan:

text
 total_produk | produk_habis | produk_menipis | produk_minuman 
--------------+--------------+----------------+----------------
            5 |            1 |              1 |              3
(1 row)

Satu baris hasil menjawab empat pertanyaan sekaligus: lima produk total, satu habis (Kopi Gayo), satu menipis (Teh Melati), dan tiga minuman. Trik di balik pola ini adalah kolusi dua sifat yang sudah Anda kuasai: cabang CASE tanpa ELSE menghasilkan NULL untuk baris yang tidak lolos, dan count(ekspresi) hanya menghitung nilai non-NULL — sehingga baris yang tidak relevan otomatis hilang dari hitungan. Perluasan pola ini ke SUM(CASE WHEN ...), GROUP BY, dan laporan pivot-baris menjadi fokus utama section Agregasi Data , yang dimulai dari chapter COUNT .

Sebelum melangkah ke sana, ada satu keluarga fungsi yang belum sempat kita jamah dalam section ini padahal dipakai di hampir setiap laporan: count, sum, avg, min, max. Chapter berikutnya memberikan tur kilat sebelum pembahasan mendalamnya di section berikutnya: Fungsi Agregat .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
CREATE TABLE produk (
    id        integer PRIMARY KEY,
    nama      text NOT NULL,
    kategori  text NOT NULL,
    stok      integer NOT NULL,
    harga     numeric(12,2) NOT NULL
);

INSERT INTO produk (id, nama, kategori, stok, harga) VALUES
    (1, 'Kopi Gayo', 'minuman', 0, 85000.00),
    (2, 'Teh Melati', 'minuman', 4, 35000.00),
    (3, 'Gula Merah', 'bahan pokok', 25, 15000.00),
    (4, 'Beras Pandan Wangi', 'bahan pokok', 12, 72000.00),
    (5, 'Susu Kedelai 1 Liter', 'minuman', 6, 28000.00);

SELECT id, nama, kategori, stok, harga
FROM produk
ORDER BY id;

case-agregat.sql

Unduh berkas
sql
-- pola pivot mini: CASE di dalam agregat menghitung per kategori sekaligus
SELECT count(*) AS total_produk,
       count(CASE WHEN stok = 0 THEN 1 END) AS produk_habis,
       count(CASE WHEN stok > 0 AND stok <= 5 THEN 1 END) AS produk_menipis,
       count(CASE WHEN kategori = 'minuman' THEN 1 END) AS produk_minuman
FROM produk;

case-di-klausa.sql

Unduh berkas
sql
-- CASE di ORDER BY: urutan khusus yang bukan urutan alfabetis
SELECT nama, kategori
FROM produk
ORDER BY CASE kategori
             WHEN 'bahan pokok' THEN 1
             WHEN 'minuman' THEN 2
             ELSE 3
         END,
         nama;

-- CASE di WHERE: saring baris berdasarkan label hasil CASE
SELECT nama, stok,
       CASE WHEN stok <= 5 THEN 'perlu restok' ELSE 'cukup' END AS status_restok
FROM produk
WHERE CASE WHEN stok <= 5 THEN 'perlu restok' ELSE 'cukup' END = 'perlu restok'
ORDER BY nama;

-- CASE di UPDATE: aturan harga berbeda per kategori dalam satu pernyataan
UPDATE produk
SET harga = CASE kategori
                WHEN 'minuman' THEN round(harga * 0.90, 2)
                ELSE round(harga * 0.95, 2)
            END;

SELECT nama, kategori, harga
FROM produk
ORDER BY id;

case-pencarian.sql

Unduh berkas
sql
-- bentuk pencarian (searched CASE): kondisi bebas di tiap cabang
SELECT nama, stok,
       CASE
           WHEN stok = 0 THEN 'habis'
           WHEN stok <= 5 THEN 'menipis'
           ELSE 'aman'
       END AS status_stok
FROM produk
ORDER BY id;

-- tanpa ELSE: baris yang tidak lolos kondisi apa pun menghasilkan NULL
SELECT nama, stok,
       CASE WHEN stok = 0 THEN 'habis' END AS label_habis
FROM produk
ORDER BY id;

-- CASE bisa mengembalikan angka, bukan hanya teks
SELECT nama, harga,
       CASE
           WHEN stok = 0 THEN 0
           WHEN stok <= 5 THEN 10
           ELSE 5
       END AS diskon_persen
FROM produk
ORDER BY id;

case-sederhana.sql

Unduh berkas
sql
-- bentuk sederhana (simple CASE): satu ekspresi dibandingkan ke daftar nilai
SELECT nama, kategori,
       CASE kategori
           WHEN 'minuman' THEN 'rak dingin'
           WHEN 'bahan pokok' THEN 'rak utama'
           ELSE 'rak umum'
       END AS lokasi_rak
FROM produk
ORDER BY id;

-- NULLIF adalah CASE yang dibekukan menjadi fungsi
SELECT NULLIF('sama', 'sama') AS nullif_langsung,
       CASE WHEN 'sama' = 'sama' THEN NULL ELSE 'sama' END AS case_padanan;

SELECT NULLIF('beda', 'sama') AS nullif_beda,
       CASE WHEN 'beda' = 'sama' THEN NULL ELSE 'beda' END AS case_beda;