PIVOT SQL: Ubah Baris Jadi Kolom buat Laporan
TL;DR
PIVOT di SQL adalah teknik mengubah nilai baris jadi nama kolom, biasanya buat bikin laporan matriks kayak penjualan per kota per bulan. Cara paling portabel adalah CASE WHEN di dalam agregat, dan itu jalan di hampir semua database. SQL Server punya operator PIVOT khusus, sementara PostgreSQL pakai fungsi crosstab dari extension tablefunc.
PIVOT di SQL adalah teknik ngubah nilai baris jadi nama kolom. Data kamu bentuknya panjang ke bawah, laporan yang diminta bentuknya lebar ke samping.
Kasus klasiknya: bos minta tabel penjualan dengan baris per kota dan kolom Jan sampai Des. Data kamu bentuknya satu baris per transaksi.
Ada tiga cara ngerjainnya, dan aku bakal jelasin ketiganya. Tapi satu di antaranya yang paling sering aku pakai — karena jalan di mana aja.
Apa itu PIVOT di SQL?
PIVOT adalah operasi yang muter data dari bentuk panjang (banyak baris, sedikit kolom) jadi bentuk lebar (sedikit baris, banyak kolom). Nilai unik dari satu kolom berubah jadi nama-nama kolom baru, dan angka agregatnya ngisi sel-selnya.
Contoh. Data mentahnya kayak gini:
| kota | bulan | omzet |
|---|---|---|
| Jakarta | 1 | 12.000.000 |
| Jakarta | 2 | 14.500.000 |
| Bandung | 1 | 8.200.000 |
| Bandung | 2 | 7.900.000 |
Setelah di-pivot:
| kota | jan | feb |
|---|---|---|
| Jakarta | 12.000.000 | 14.500.000 |
| Bandung | 8.200.000 | 7.900.000 |
Angkanya sama. Bentuknya beda. Dan bentuk kedua yang orang mau lihat di laporan.
Gimana cara bikin PIVOT pakai CASE WHEN?
Ini cara yang paling aku rekomendasiin. Jalan di PostgreSQL, MySQL, SQL Server, SQLite, BigQuery — semuanya.
Polanya: SUM(CASE WHEN kondisi THEN nilai ELSE 0 END) buat tiap kolom yang mau kamu bikin.
SELECT
kota,
SUM(CASE WHEN EXTRACT(MONTH FROM tanggal) = 1 THEN total_harga ELSE 0 END) AS jan,
SUM(CASE WHEN EXTRACT(MONTH FROM tanggal) = 2 THEN total_harga ELSE 0 END) AS feb,
SUM(CASE WHEN EXTRACT(MONTH FROM tanggal) = 3 THEN total_harga ELSE 0 END) AS mar,
SUM(total_harga) AS total
FROM toko_berkah
WHERE EXTRACT(YEAR FROM tanggal) = 2026
GROUP BY kota
ORDER BY total DESC;
Cara bacanya per kolom. Kolom jan cuma ngejumlahin baris yang bulannya 1. Baris bulan lain dikasih 0, jadi nggak ikut kejumlah.
GROUP BY yang nentuin baris laporannya. CASE WHEN yang nentuin kolomnya. Dua-duanya kerja bareng.
Kolom total di ujung itu bonus: SUM tanpa kondisi, jadi dia jumlahin semuanya.
Kalau CASE WHEN masih asing buat kamu, ada penjelasan lengkapnya di halaman fungsi CASE WHEN.
Variasi: hitung transaksi, bukan omzet
Ganti SUM jadi COUNT, dan kamu dapat jumlah transaksi per bulan:
SELECT
kota,
COUNT(CASE WHEN EXTRACT(MONTH FROM tanggal) = 1 THEN 1 END) AS trx_jan,
COUNT(CASE WHEN EXTRACT(MONTH FROM tanggal) = 2 THEN 1 END) AS trx_feb
FROM toko_berkah
GROUP BY kota;
Perhatiin: di COUNT, aku nggak pakai ELSE 0. Soalnya COUNT ngitung nilai yang nggak NULL, jadi tanpa ELSE, baris yang nggak cocok jadi NULL dan nggak ikut kehitung. Kalau kamu tulis ELSE 0, semua baris kehitung dan hasilnya salah.
Ini jebakan yang sering bikin angka melenceng.
Gimana cara pakai operator PIVOT di SQL Server?
SQL Server punya sintaks khusus. Lebih pendek, tapi cuma jalan di SQL Server dan Oracle.
SELECT kota, [1] AS jan, [2] AS feb, [3] AS mar
FROM (
SELECT kota, MONTH(tanggal) AS bln, total_harga
FROM toko_berkah
) AS src
PIVOT (
SUM(total_harga) FOR bln IN ([1], [2], [3])
) AS pvt;
Tiga bagian yang perlu kamu ngerti. Subquery src nyiapin data yang bakal di-pivot — dan cuma kolom yang kamu SELECT di sini yang bakal muncul.
Klausa PIVOT nentuin agregatnya (SUM) dan kolom mana yang nilainya jadi nama kolom (bln).
Bagian IN ([1], [2], [3]) nentuin nilai mana yang jadi kolom. Kamu harus sebut satu-satu — dan ini kelemahannya.
Jebakan yang sering bikin bingung: kolom yang nggak kamu sebut di subquery tapi ada di tabel asli bakal ikut jadi grup diam-diam. Kalau hasilnya lebih banyak baris dari yang kamu harapin, cek subquery-nya. Detailnya di dokumentasi PIVOT SQL Server.
Gimana cara PIVOT di PostgreSQL?
PostgreSQL nggak punya operator PIVOT. Dia punya crosstab, tapi itu ada di extension tablefunc yang harus di-install dulu:
CREATE EXTENSION IF NOT EXISTS tablefunc;
SELECT *
FROM crosstab(
'SELECT kota, EXTRACT(MONTH FROM tanggal)::int, SUM(total_harga)
FROM toko_berkah
GROUP BY 1, 2
ORDER BY 1, 2',
'SELECT generate_series(1, 3)'
) AS ct(kota text, jan numeric, feb numeric, mar numeric);
Jujur aja: aku jarang pakai ini. Sintaksnya ribet, query-nya ditulis sebagai string (jadi nggak dicek editor), dan kamu tetap harus sebut nama kolomnya di bagian AS ct(...).
Buat kebanyakan kasus di PostgreSQL, CASE WHEN lebih gampang dibaca dan di-maintain.
Contoh kasus: laporan bulanan toko_berkah
Ini query yang beneran dipakai buat laporan bulanan toko_berkah. Baris per kota, kolom per kuartal, plus persentase kontribusi.
WITH pivot_kuartal AS (
SELECT
kota,
SUM(CASE WHEN EXTRACT(QUARTER FROM tanggal) = 1 THEN total_harga ELSE 0 END) AS q1,
SUM(CASE WHEN EXTRACT(QUARTER FROM tanggal) = 2 THEN total_harga ELSE 0 END) AS q2,
SUM(CASE WHEN EXTRACT(QUARTER FROM tanggal) = 3 THEN total_harga ELSE 0 END) AS q3,
SUM(CASE WHEN EXTRACT(QUARTER FROM tanggal) = 4 THEN total_harga ELSE 0 END) AS q4,
SUM(total_harga) AS total
FROM toko_berkah
WHERE EXTRACT(YEAR FROM tanggal) = 2025
GROUP BY kota
)
SELECT
kota,
q1, q2, q3, q4,
total,
ROUND(total * 100.0 / SUM(total) OVER (), 1) AS kontribusi_persen,
ROUND((q4 - q1) * 100.0 / NULLIF(q1, 0), 1) AS growth_q1_ke_q4
FROM pivot_kuartal
ORDER BY total DESC;
Kolom terakhir yang paling menarik. Dia ngebandingin Q4 sama Q1, jadi kamu langsung lihat kota mana yang tumbuh dan mana yang nyusut sepanjang tahun.
NULLIF(q1, 0) di penyebutnya buat ngehindarin error kalau ada kota yang Q1-nya nol.
Hasil dari dataset ngulikdata: dari 12 kota, 4 kota punya growth Q1 ke Q4 negatif. Yang paling parah Solo, turun 31%. Tapi total omzet Solo setahun masih peringkat 6 dari 12 — jadi kalau cuma lihat kolom total, dia kelihatan baik-baik aja.
Bentuk pivot yang bikin masalahnya kelihatan. Kolom total nyembunyiin trennya.
Gimana cara UNPIVOT, balikin kolom jadi baris?
Kadang kamu dapat file Excel dari user yang formatnya udah lebar, dan kamu perlu balikin ke bentuk baris biar bisa dianalisis.
Cara paling portabel: UNION ALL.
SELECT kota, 'jan' AS bulan, jan AS omzet FROM laporan_lebar
UNION ALL
SELECT kota, 'feb', feb FROM laporan_lebar
UNION ALL
SELECT kota, 'mar', mar FROM laporan_lebar;
Panjang, tapi jelas dan jalan di mana aja.
Di PostgreSQL ada cara yang lebih ringkas:
SELECT
kota,
unnest(ARRAY['jan', 'feb', 'mar']) AS bulan,
unnest(ARRAY[jan, feb, mar]) AS omzet
FROM laporan_lebar;
Kesalahan umum waktu bikin PIVOT
1. Pakai ELSE 0 di dalam COUNT. Ini bikin semua baris kehitung, bukan cuma yang cocok kondisinya. Buat COUNT, biarin ELSE-nya kosong (jadi NULL). Buat SUM, ELSE 0 aman.
2. Berharap kolomnya dinamis. SQL harus tau nama kolomnya waktu query di-compile. Kamu nggak bisa bilang "buatin kolom otomatis dari nilai yang ada". Kalau butuh dinamis, pivot di layer visualisasi (Power BI, Looker Studio) atau pakai dynamic SQL — dan dynamic SQL itu ribet dan rawan SQL injection.
3. Nggak filter periode dulu. Kalau tabelnya 5 tahun data dan kamu cuma butuh 2026, taruh WHERE-nya. Kalau nggak, database ngitung CASE WHEN buat semua baris yang bakal jadi 0 juga.
4. Bikin 30+ kolom pivot dalam satu query. Secara teknis bisa. Tapi query-nya jadi 200 baris, susah dibaca, dan lambat. Kalau kamu butuh 30 kolom, pertanyaannya: laporan ini beneran dibaca orang, atau cuma dump data?
5. Pivot padahal seharusnya pakai window function. Kalau yang kamu mau itu bandingin baris sama grupnya (misalnya kontribusi tiap transaksi ke total kota), PARTITION BY lebih tepat dan nggak bikin bentuk tabelnya berubah.
FAQ
Apa cara paling aman bikin PIVOT di SQL?
CASE WHEN di dalam fungsi agregat. Tulisannya: SUM(CASE WHEN bulan = 1 THEN total_harga ELSE 0 END) AS jan. Cara ini jalan di PostgreSQL, MySQL, SQL Server, SQLite, BigQuery, dan hampir semua database lain. Lebih panjang dari operator PIVOT khusus, tapi kamu nggak perlu hafal sintaks yang beda-beda tiap database.
Bisa nggak bikin PIVOT dengan kolom yang dinamis?
Nggak bisa langsung. SQL butuh tau nama kolomnya waktu query di-compile. Solusinya ada dua: bikin dynamic SQL (query yang menghasilkan string query lain lalu dijalankan), atau — dan biasanya lebih baik — pivot di layer visualisasi kayak Power BI atau Looker Studio yang emang dirancang buat itu.
Apa bedanya PIVOT dan GROUP BY?
GROUP BY meringkas data ke bawah, hasilnya satu baris per grup. PIVOT nyebar hasil agregat itu ke samping jadi kolom. Biasanya dua-duanya dipakai bareng: GROUP BY nentuin baris laporannya (per kota), CASE WHEN di dalam agregat nentuin kolomnya (per bulan). Jadi PIVOT bukan pengganti GROUP BY, tapi lapisan tambahan di atasnya.
Gimana cara UNPIVOT, balikin kolom jadi baris?
Cara paling portabel pakai UNION ALL: satu SELECT per kolom yang mau diubah jadi baris. PostgreSQL punya cara lebih ringkas pakai UNNEST, SQL Server punya operator UNPIVOT. Ini sering dibutuhin waktu kamu terima file Excel dari user yang formatnya udah lebar, dan kamu mau masukin ke database dalam bentuk baris yang rapi.
Apakah PIVOT bikin query jadi lambat?
Teknik CASE WHEN nggak jauh lebih lambat dari GROUP BY biasa, soalnya database cuma jalan sekali lewat data. Yang bikin lambat itu kalau kamu bikin puluhan kolom CASE WHEN sekaligus, atau kalau tabelnya nggak di-filter dulu. Taruh WHERE buat batasi periode sebelum agregasi, dan jangan bikin lebih dari 20 kolom hasil pivot dalam satu query.
Ringkasan
Tiga cara bikin PIVOT: CASE WHEN (jalan di mana aja, ini yang aku pakai), operator PIVOT (SQL Server dan Oracle doang), dan crosstab (PostgreSQL, tapi ribet).
Pola CASE WHEN yang perlu kamu hafal: SUM(CASE WHEN kondisi THEN nilai ELSE 0 END) AS nama_kolom. Ganti SUM jadi COUNT? Buang ELSE-nya.
Dan inget: kolom pivot nggak bisa dinamis di SQL murni. Kalau butuh dinamis, itu kerjaan tool visualisasi.
Mau langsung latihan? Coba latihan CASE WHEN di NgulikSQL pakai dataset toko_berkah, terus bikin laporan kuartalan versi kamu sendiri.
Habis itu lanjut ke window function SQL — buat kasus yang kelihatan kayak butuh pivot, padahal sebenernya butuh PARTITION BY.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel Terkait
7 SQL Client Gratis Terbaik buat Analis Data (2026)
Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.