TL;DR
Retention rate adalah persentase pelanggan dari satu periode yang balik lagi di periode berikutnya. Cara ngukurnya di SQL: kelompokin pelanggan berdasarkan bulan transaksi pertama (cohort), hitung selisih bulan tiap transaksi berikutnya, lalu bagi jumlah pelanggan aktif tiap bulan dengan ukuran cohort awalnya. Hasilnya cohort table, tabel segitiga yang nunjukin berapa persen pelanggan bertahan tiap bulan.
Retention rate adalah persentase pelanggan dari satu periode yang balik belanja lagi di periode berikutnya.
Angka ini yang bedain bisnis yang tumbuh dari bisnis yang cuma sibuk. Kamu bisa dapet 500 pelanggan baru tiap bulan dan tetap jalan di tempat. Kalau 480 dari bulan lalu nggak pernah balik.
Ini cara bikin cohort retention table pakai SQL, dari nol sampai bisa dibaca.
Cohort retention adalah cara ngukur berapa persen pelanggan yang bertahan, dikelompokin berdasarkan kapan mereka pertama kali belanja. Kelompok itu namanya cohort. Pelanggan yang transaksi pertamanya di Januari masuk cohort Januari, dan mereka dilacak terus tiap bulan setelahnya.
Rumusnya:
retention_bulan_N = (pelanggan cohort yang aktif di bulan ke-N)
/ (ukuran cohort di bulan ke-0) * 100
Perhatiin penyebutnya: selalu ukuran cohort awal. Bukan jumlah pelanggan bulan sebelumnya. Kalau kamu salah di sini, angka retention bisa kelihatan naik padahal pelanggannya terus berkurang.
Tiga kolom dari tabel transaksi.
transaksi
---------
pelanggan_id integer
tanggal_transaksi date
status text -- 'selesai', 'batal', 'pending'
Itu doang. Nggak butuh tabel event, nggak butuh tracking pixel.
Cohort seseorang ditentuin sama bulan transaksi pertamanya.
WITH cohort_pelanggan AS (
SELECT
pelanggan_id,
DATE_TRUNC('month', MIN(tanggal_transaksi)) AS cohort_bulan
FROM transaksi
WHERE status = 'selesai'
GROUP BY pelanggan_id
)
SELECT * FROM cohort_pelanggan LIMIT 3;
pelanggan_id | cohort_bulan
-------------+-------------
101 | 2026-01-01
102 | 2026-01-01
103 | 2026-03-01
Filter status = 'selesai' itu penting. Transaksi yang dibatalin nggak boleh nentuin cohort seseorang. Detail DATE_TRUNC ada di halaman fungsinya.
Sekarang gabungin cohort tadi ke semua transaksi, dan ambil bulan-bulan mereka aktif.
aktivitas AS (
SELECT DISTINCT
t.pelanggan_id,
c.cohort_bulan,
DATE_TRUNC('month', t.tanggal_transaksi) AS bulan_aktif
FROM transaksi t
JOIN cohort_pelanggan c ON c.pelanggan_id = t.pelanggan_id
WHERE t.status = 'selesai'
)
DISTINCT di sini nyegah pelanggan kehitung dua kali kalau dia belanja 5 kali dalam sebulan. Buat retention, yang kita hitung itu orangnya, bukan transaksinya.
Tiap baris aktivitas perlu tau ini bulan ke berapa sejak cohort-nya lahir.
hitung AS (
SELECT
cohort_bulan,
(DATE_PART('year', bulan_aktif) - DATE_PART('year', cohort_bulan)) * 12
+ (DATE_PART('month', bulan_aktif) - DATE_PART('month', cohort_bulan)) AS bulan_ke,
COUNT(DISTINCT pelanggan_id) AS pelanggan_aktif
FROM aktivitas
GROUP BY 1, 2
)
Rumus selisih bulannya kelihatan ribet, tapi logikanya sederhana: selisih tahun dikali 12, ditambah selisih bulan. Ini nyegah bug klasik di mana Januari 2027 dianggap 11 bulan sebelum Februari 2026.
ukuran_cohort AS (
SELECT cohort_bulan, pelanggan_aktif AS ukuran_awal
FROM hitung
WHERE bulan_ke = 0
)
SELECT
h.cohort_bulan,
h.bulan_ke,
u.ukuran_awal,
h.pelanggan_aktif,
ROUND(100.0 * h.pelanggan_aktif / u.ukuran_awal, 1) AS retention_pct
FROM hitung h
JOIN ukuran_cohort u ON u.cohort_bulan = h.cohort_bulan
ORDER BY h.cohort_bulan, h.bulan_ke;
Angka 100.0 bukan 100. Di PostgreSQL, integer dibagi integer hasilnya integer. Lupa titik nol, semua retention kamu jadi 0%.
Aku jalanin query lengkapnya di dataset toko_berkah. Ini cohort table-nya:
| Cohort | Ukuran | Bln 0 | Bln 1 | Bln 2 | Bln 3 | Bln 4 | Bln 5 |
|---|---|---|---|---|---|---|---|
| Jan 2026 | 214 | 100% | 31,3% | 22,4% | 19,6% | 18,7% | 18,2% |
| Feb 2026 | 187 | 100% | 29,9% | 21,4% | 18,7% | 17,6% | - |
| Mar 2026 | 241 | 100% | 36,5% | 27,8% | 24,9% | - | - |
| Apr 2026 | 198 | 100% | 38,4% | 29,3% | - | - | - |
| Mei 2026 | 223 | 100% | 37,7% | - | - | - | - |
Ada tiga pola yang langsung kelihatan dari tabel ini.
Bulan 1 selalu paling anjlok. Dari 100% ke sekitar 30-38%. Ini normal di e-commerce. Kebanyakan orang beli sekali terus ilang.
Kurvanya mendatar setelah bulan 2. Cohort Januari turun dari 22,4% ke 18,2% dalam empat bulan. Yang bertahan sampai bulan 2 itu pelanggan beneran, dan mereka cenderung nempel.
Cohort Maret ke atas jauh lebih baik. Retention bulan-1 naik dari 29,9% (Feb) ke 36,5% (Mar) dan 38,4% (Apr). Lompatan 8,5 poin persentase itu gede.
Ternyata program poin loyalitas diluncurin awal Maret. Cohort table-nya nangkep efeknya dengan bersih, tanpa perlu A/B test.
Ini yang bikin cohort analysis kepake: dia misahin efek perubahan produk dari efek musiman. Kalau kamu cuma lihat total pelanggan aktif per bulan, kenaikan itu bakal ketutup sama pertumbuhan pelanggan baru.
Mau lanjut segmentasi siapa yang bertahan? Gabungin sama RFM analysis di SQL.
1. Penyebutnya salah. Bagi sama jumlah pelanggan bulan sebelumnya, bukan sama ukuran cohort awal. Ini bikin angka retention kelihatan naik terus padahal pelanggannya makin habis.
2. Lupa 100.0 di pembagian. Integer dibagi integer di PostgreSQL hasilnya integer. Semua retention kamu jadi 0%.
3. Lupa DISTINCT. Pelanggan yang belanja 5 kali sebulan kehitung 5 orang. Retention kamu bisa tembus 300%.
4. Nggak filter transaksi batal. Orang yang beli terus refund kehitung sebagai pelanggan yang bertahan.
5. Bandingin cohort yang umurnya beda. Cohort Mei belum punya data bulan-3. Sel kosong bukan berarti retention-nya 0. Bentuk segitiga itu wajar.
Referensi soal fungsi tanggal di PostgreSQL ada di dokumentasi resminya. Kalau CTE bertingkat masih bikin bingung, cek dulu glosarium CTE.
Retention rate adalah persentase pelanggan dari satu periode yang masih aktif di periode berikutnya. Rumusnya: jumlah pelanggan cohort yang aktif di bulan ke-N, dibagi ukuran cohort di bulan ke-0, dikali 100. Yang penting diinget, penyebutnya selalu ukuran cohort awal, bukan jumlah pelanggan bulan sebelumnya. Kalau penyebutnya salah, angkanya bisa kelihatan naik padahal pelanggannya berkurang.
Repeat purchase rate cuma ngitung berapa persen pelanggan yang pernah beli lebih dari sekali, tanpa peduli kapan. Cohort retention ngasih dimensi waktu: berapa persen yang masih balik di bulan ke-1, ke-2, ke-3. Cohort retention lebih berguna buat ngelacak apakah perubahan produk atau kampanye kamu beneran ngefek, soalnya kamu bisa bandingin cohort lama sama cohort baru.
Soalnya cohort yang lahir bulan Januari udah punya kesempatan bertahan 6 bulan, sementara cohort Juni baru punya data 1 bulan. Jadi baris paling atas panjang, baris paling bawah pendek. Bentuk segitiga itu normal dan justru penting: jangan bandingin angka bulan ke-3 dari cohort yang belum sampai bulan ke-3.
Tergantung jenis bisnisnya. Buat e-commerce kebutuhan harian, retention bulan 1 di kisaran 25-35% udah lumayan. Buat barang tahan lama kayak elektronik, angka 5% pun wajar. Patokan yang lebih berguna daripada benchmark orang lain: bandingin cohort kamu bulan ini sama cohort tiga bulan lalu. Kalau kurvanya makin datar, kamu ke arah yang bener.
Perlu sedikit penyesuaian. MySQL nggak punya DATE_TRUNC, jadi ganti pakai DATE_FORMAT dengan pola tahun-bulan. Buat hitung selisih bulan, MySQL punya TIMESTAMPDIFF yang malah lebih ringkas daripada versi PostgreSQL. Logika CTE-nya sendiri sama persis dan jalan di MySQL 8 ke atas.
Poin penting dari query di atas:
Copy query di atas, ganti nama tabel dan kolomnya sesuai database kamu, dan jalanin. Lima menit, dan kamu bakal tau apakah bisnis kamu beneran tumbuh atau cuma sibuk. Lanjut ke metrik harian data analyst e-commerce buat lihat retention duduk di mana dalam gambaran besarnya.
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.