TL;DR
Ada 5 cara hitung persentase di SQL: (1) COUNT sederhana, (2) SUM-based, (3) per grup dengan GROUP BY, (4) running percentage pake window function, dan (5) persentase kumulatif. Jangan lupa handle division by zero!
"Berapa persen customer kita dari Jakarta?"
"Market share produk A berapa persen dari total revenue?"
"Persentase order yang completed gimana trennya bulan ke bulan?"
Pertanyaan kayak gini pasti sering banget muncul di kerjaan Data Analyst. Dan jawabannya: persentase.
Menghitung persentase di SQL itu skill dasar yang wajib kamu kuasai. Kabar baiknya, ada beberapa cara berbeda tergantung kebutuhan kamu. Kita bahas satu per satu.
Buat tutorial ini, kita pakai dataset market share e-commerce Indonesia. Data penjualan dari berbagai platform marketplace.
| id | tanggal | platform | kategori | kota | jumlah_order | revenue |
|---|---|---|---|---|---|---|
| 1 | 2024-01-15 | Tokopedia | Elektronik | Jakarta | 150 | 750000000 |
| 2 | 2024-01-15 | Shopee | Fashion | Bandung | 200 | 400000000 |
| 3 | 2024-01-15 | Bukalapak | Elektronik | Surabaya | 80 | 320000000 |
| 4 | 2024-01-15 | Tokopedia | Fashion | Jakarta | 180 | 360000000 |
| 5 | 2024-02-15 | Shopee | Elektronik | Jakarta | 220 | 880000000 |
| 6 | 2024-02-15 | Tokopedia | Makanan | Bandung | 300 | 150000000 |
| 7 | 2024-02-15 | Lazada | Elektronik | Jakarta | 100 | 500000000 |
| 8 | 2024-02-15 | Bukalapak | Fashion | Surabaya | 120 | 240000000 |
| 9 | 2024-03-15 | Tokopedia | Elektronik | Jakarta | 180 | 900000000 |
| 10 | 2024-03-15 | Shopee | Makanan | Bandung | 250 | 125000000 |
| 11 | 2024-03-15 | Shopee | Fashion | Jakarta | 280 | 560000000 |
| 12 | 2024-03-15 | Blibli | Elektronik | Surabaya | 90 | 450000000 |
| customer_id | nama | kota | status | total_order |
|---|---|---|---|---|
| 1 | Budi | Jakarta | active | 15 |
| 2 | Siti | Bandung | active | 8 |
| 3 | Andi | Jakarta | inactive | 2 |
| 4 | Dewi | Surabaya | active | 12 |
| 5 | Rudi | Jakarta | active | 20 |
| 6 | Maya | Medan | inactive | 1 |
| 7 | Agus | Jakarta | active | 10 |
| 8 | Rina | Bandung | inactive | 3 |
| 9 | Doni | Surabaya | active | 7 |
| 10 | Linda | Jakarta | active | 5 |
Ini cara paling basic. Kamu mau tau berapa persen customer dari Jakarta.
SELECT
ROUND(
100.0 * COUNT(CASE WHEN kota = 'Jakarta' THEN 1 END) / COUNT(*),
2
) AS persen_jakarta
FROM customers;
Hasil:
| persen_jakarta |
|---|
| 50.00 |
50% customer kita dari Jakarta. Simple kan?
Breakdown query-nya:
- COUNT(CASE WHEN kota = 'Jakarta' THEN 1 END) = hitung yang Jakarta aja
- COUNT(*) = total semua customer
- 100.0 * = kalikan 100 biar jadi persentase (pake 100.0 biar hasilnya decimal)
- ROUND(..., 2) = bulatkan 2 angka di belakang koma
Kalau mau hitung persentase dari nilai (bukan count), pake SUM.
Misalnya, berapa persen revenue dari platform Tokopedia?
SELECT
ROUND(
100.0 * SUM(CASE WHEN platform = 'Tokopedia' THEN revenue ELSE 0 END) / SUM(revenue),
2
) AS persen_revenue_tokped
FROM penjualan;
Hasil:
| persen_revenue_tokped |
|---|
| 38.33 |
Tokopedia menyumbang 38.33% dari total revenue. Lumayan gede ya.
Versi alternatif pake subquery:
SELECT
ROUND(
100.0 * (SELECT SUM(revenue) FROM penjualan WHERE platform = 'Tokopedia')
/ SUM(revenue),
2
) AS persen_revenue_tokped
FROM penjualan;
Hasilnya sama, tinggal pilih mana yang lebih readable buat kamu.
Nah ini yang sering dipake di reporting. Kamu mau liat persentase revenue tiap platform.
SELECT
platform,
SUM(revenue) AS total_revenue,
ROUND(
100.0 * SUM(revenue) / (SELECT SUM(revenue) FROM penjualan),
2
) AS persen_revenue
FROM penjualan
GROUP BY platform
ORDER BY persen_revenue DESC;
Hasil:
| platform | total_revenue | persen_revenue |
|---|---|---|
| Tokopedia | 2160000000 | 38.33 |
| Shopee | 1965000000 | 34.87 |
| Bukalapak | 560000000 | 9.94 |
| Lazada | 500000000 | 8.87 |
| Blibli | 450000000 | 7.99 |
Sekarang kamu bisa liat market share tiap platform. Tokopedia dan Shopee dominan dengan combined 73% market share.
Cara yang lebih elegan pake window function:
SELECT
platform,
SUM(revenue) AS total_revenue,
ROUND(
100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (),
2
) AS persen_revenue
FROM penjualan
GROUP BY platform
ORDER BY persen_revenue DESC;
SUM(SUM(revenue)) OVER () itu grand total dari semua platform. Lebih clean karena ga perlu subquery.
Running percentage itu persentase yang "berjalan" dari row pertama sampai row saat ini. Berguna buat liat kontribusi kumulatif.
Misalnya, kalau platform diurutkan dari revenue terbesar, berapa persen akumulasi di setiap level?
WITH platform_revenue AS (
SELECT
platform,
SUM(revenue) AS total_revenue
FROM penjualan
GROUP BY platform
)
SELECT
platform,
total_revenue,
SUM(total_revenue) OVER (ORDER BY total_revenue DESC) AS running_total,
ROUND(
100.0 * SUM(total_revenue) OVER (ORDER BY total_revenue DESC)
/ SUM(total_revenue) OVER (),
2
) AS running_persen
FROM platform_revenue
ORDER BY total_revenue DESC;
Hasil:
| platform | total_revenue | running_total | running_persen |
|---|---|---|---|
| Tokopedia | 2160000000 | 2160000000 | 38.33 |
| Shopee | 1965000000 | 4125000000 | 73.20 |
| Bukalapak | 560000000 | 4685000000 | 83.14 |
| Lazada | 500000000 | 5185000000 | 92.01 |
| Blibli | 450000000 | 5635000000 | 100.00 |
Baris terakhir pas di 100%. Semua platform udah keakumulasi. Kadang kamu bisa liat selisih kecil kayak 99.99% atau 100.01% gara-gara pembulatan, dan itu normal.
Insight-nya: Tokopedia + Shopee aja udah 73% market share. Classic 80/20 rule nih.
Mirip running percentage, tapi biasanya dipake buat analisis distribusi. Misalnya, distribusi customer berdasarkan total order.
WITH customer_orders AS (
SELECT
customer_id,
nama,
total_order,
ROW_NUMBER() OVER (ORDER BY total_order DESC) AS rank
FROM customers
),
total_customers AS (
SELECT COUNT(*) AS total FROM customers
)
SELECT
co.rank,
co.nama,
co.total_order,
ROUND(100.0 * co.rank / tc.total, 2) AS persen_customer,
ROUND(
100.0 * SUM(co.total_order) OVER (ORDER BY co.total_order DESC)
/ SUM(co.total_order) OVER (),
2
) AS persen_order_kumulatif
FROM customer_orders co
CROSS JOIN total_customers tc
ORDER BY co.rank;
Hasil:
| rank | nama | total_order | persen_customer | persen_order_kumulatif |
|---|---|---|---|---|
| 1 | Rudi | 20 | 10.00 | 24.10 |
| 2 | Budi | 15 | 20.00 | 42.17 |
| 3 | Dewi | 12 | 30.00 | 56.63 |
| 4 | Agus | 10 | 40.00 | 68.67 |
| 5 | Siti | 8 | 50.00 | 78.31 |
| 6 | Doni | 7 | 60.00 | 86.75 |
| 7 | Linda | 5 | 70.00 | 92.77 |
| 8 | Rina | 3 | 80.00 | 96.39 |
| 9 | Andi | 2 | 90.00 | 98.80 |
| 10 | Maya | 1 | 100.00 | 100.00 |
Dari sini keliatan: 30% customer teratas (Rudi, Budi, Dewi) menyumbang hampir 57% dari total order. Power users!
Case yang lebih kompleks: persentase revenue tiap kategori DALAM setiap platform.
SELECT
platform,
kategori,
SUM(revenue) AS revenue,
ROUND(
100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (PARTITION BY platform),
2
) AS persen_dalam_platform
FROM penjualan
GROUP BY platform, kategori
ORDER BY platform, persen_dalam_platform DESC;
Hasil:
| platform | kategori | revenue | persen_dalam_platform |
|---|---|---|---|
| Blibli | Elektronik | 450000000 | 100.00 |
| Bukalapak | Elektronik | 320000000 | 57.14 |
| Bukalapak | Fashion | 240000000 | 42.86 |
| Lazada | Elektronik | 500000000 | 100.00 |
| Shopee | Elektronik | 880000000 | 44.78 |
| Shopee | Fashion | 960000000 | 48.85 |
| Shopee | Makanan | 125000000 | 6.36 |
| Tokopedia | Elektronik | 1650000000 | 76.39 |
| Tokopedia | Fashion | 360000000 | 16.67 |
| Tokopedia | Makanan | 150000000 | 6.94 |
Di Tokopedia, Elektronik dominan banget (76%). Sementara Shopee lebih balance antara Elektronik dan Fashion.
Ini penting banget. Kalau total-nya 0, query bakal error. Gimana cara handle-nya?
SELECT
platform,
SUM(revenue) AS total_revenue,
ROUND(
100.0 * SUM(revenue) / NULLIF(SUM(SUM(revenue)) OVER (), 0),
2
) AS persen_revenue
FROM penjualan
GROUP BY platform;
NULLIF(x, 0) bakal return NULL kalau x = 0. Jadi hasilnya NULL, bukan error.
SELECT
platform,
SUM(revenue) AS total_revenue,
CASE
WHEN SUM(SUM(revenue)) OVER () = 0 THEN 0
ELSE ROUND(100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (), 2)
END AS persen_revenue
FROM penjualan
GROUP BY platform;
Lebih verbose, tapi kamu bisa set default value (0 atau NULL atau apa aja).
SELECT
platform,
SUM(revenue) AS total_revenue,
COALESCE(
ROUND(100.0 * SUM(revenue) / NULLIF(SUM(SUM(revenue)) OVER (), 0), 2),
0
) AS persen_revenue
FROM penjualan
GROUP BY platform;
Kalau hasil pembagian NULL, ganti dengan 0.
SELECT
platform,
SUM(revenue) AS total_revenue,
ROUND(100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (), 2) || '%' AS persen_revenue
FROM penjualan
GROUP BY platform;
Hasil:
| platform | total_revenue | persen_revenue |
|---|---|---|
| Tokopedia | 2160000000 | 38.33% |
| Shopee | 1965000000 | 34.87% |
SELECT
platform,
TO_CHAR(SUM(revenue), 'FM999,999,999,999') AS total_revenue,
TO_CHAR(
100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (),
'FM990.00%'
) AS persen_revenue
FROM penjualan
GROUP BY platform;
Hasil:
| platform | total_revenue | persen_revenue |
|---|---|---|
| Tokopedia | 2,160,000,000 | 38.33% |
| Shopee | 1,965,000,000 | 34.87% |
Lebih readable untuk reporting.
-- SALAH (hasilnya 0.3833, bukan 38.33%)
SELECT SUM(revenue) / (SELECT SUM(revenue) FROM penjualan) AS persen
FROM penjualan
WHERE platform = 'Tokopedia';
-- BENAR
SELECT 100.0 * SUM(revenue) / (SELECT SUM(revenue) FROM penjualan) AS persen
FROM penjualan
WHERE platform = 'Tokopedia';
-- SALAH (integer division, hasilnya 0 atau 1)
SELECT 100 * COUNT(CASE WHEN kota = 'Jakarta' THEN 1 END) / COUNT(*) AS persen
FROM customers;
-- BENAR (pake 100.0 biar decimal)
SELECT 100.0 * COUNT(CASE WHEN kota = 'Jakarta' THEN 1 END) / COUNT(*) AS persen
FROM customers;
-- POTENSI ERROR kalau total = 0
SELECT 100.0 * SUM(revenue) / SUM(total_revenue) AS persen
FROM empty_table;
-- AMAN
SELECT 100.0 * SUM(revenue) / NULLIF(SUM(total_revenue), 0) AS persen
FROM empty_table;
-- KURANG EFISIEN (subquery dijalankan berkali-kali)
SELECT
platform,
100.0 * SUM(revenue) / (SELECT SUM(revenue) FROM penjualan) AS persen
FROM penjualan
GROUP BY platform;
-- LEBIH EFISIEN (pake window function)
SELECT
platform,
100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER () AS persen
FROM penjualan
GROUP BY platform;
Kalau kamu hitung persentase per grup, totalnya harus 100% (atau mendekati karena rounding).
SELECT SUM(persen_revenue) AS total_persen
FROM (
SELECT ROUND(100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (), 2) AS persen_revenue
FROM penjualan
GROUP BY platform
) sub;
WITH total_revenue AS (
SELECT SUM(revenue) AS total FROM penjualan
),
platform_revenue AS (
SELECT platform, SUM(revenue) AS revenue
FROM penjualan
GROUP BY platform
)
SELECT
pr.platform,
pr.revenue,
ROUND(100.0 * pr.revenue / tr.total, 2) AS persen
FROM platform_revenue pr
CROSS JOIN total_revenue tr;
Lebih readable dan gampang di-debug.
SELECT
platform,
SUM(revenue) AS total_revenue,
-- Market share = (platform revenue / total revenue) * 100
ROUND(100.0 * SUM(revenue) / SUM(SUM(revenue)) OVER (), 2) AS market_share_pct
FROM penjualan
GROUP BY platform;
Coba kerjain query ini:
Soal: Hitung persentase customer aktif vs inactive per kota. Output-nya: kota, total_customer, persen_active, persen_inactive.
SELECT
kota,
COUNT(*) AS total_customer,
ROUND(
100.0 * COUNT(CASE WHEN status = 'active' THEN 1 END) / COUNT(*),
2
) AS persen_active,
ROUND(
100.0 * COUNT(CASE WHEN status = 'inactive' THEN 1 END) / COUNT(*),
2
) AS persen_inactive
FROM customers
GROUP BY kota
ORDER BY total_customer DESC;
**Hasil:**
| kota | total_customer | persen_active | persen_inactive |
|------|----------------|---------------|-----------------|
| Jakarta | 5 | 80.00 | 20.00 |
| Bandung | 2 | 50.00 | 50.00 |
| Surabaya | 2 | 100.00 | 0.00 |
| Medan | 1 | 0.00 | 100.00 |
Itu gara-gara integer division. Kalau kamu nulis 100 * a / b dan dua-duanya integer, database bakal buang angka di belakang koma. Solusinya gampang kok: pakai 100.0 (ada .0-nya) biar hasilnya decimal. Ini kesalahan yang paling sering muncul waktu orang pertama kali hitung persentase di SQL.
Pakai NULLIF di penyebutnya: 100.0 * nilai / NULLIF(total, 0). Kalau total-nya 0, hasilnya NULL, bukan error yang bikin query gagal. Mau hasilnya 0 dan bukan NULL? Bungkus lagi pakai COALESCE. Dua fungsi ini udah cukup buat hampir semua kasus pembagian di reporting.
Window function. SUM(SUM(revenue)) OVER () ngitung grand total sekali jalan, jadi query-nya lebih rapi dan biasanya lebih cepat dari subquery yang dieksekusi berulang. Tapi kalau kamu masih baru, subquery lebih gampang dibaca. Mulai dari situ dulu, pindah ke window function pas udah nyaman.
Karena rounding. ROUND(..., 2) motong tiap angka jadi 2 desimal, jadi jumlahnya bisa 99.99% atau 100.01%. Selisih sekecil itu normal dan nggak perlu dibenerin. Kalau report kamu wajib pas 100%, bulatkan baris terakhir sebagai sisa: 100 dikurangi jumlah baris lainnya.
Menghitung persentase di SQL itu gampang kalau kamu tau caranya. Inget poin-poin ini:
Persentase itu bahasa universal di bisnis. Stakeholder lebih gampang ngerti "market share 40%" daripada "revenue 2.16 milyar". Jadi kuasai skill ini ya!
Happy calculating!
Kalau kamu udah paham cara hitung persentase, next step-nya:
- Window Functions - buat analisis yang lebih advanced
- GROUP BY dan HAVING - dasar agregasi data
- Cohort Analysis - analisis retention dengan persentase
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.