Menghitung DAU dan MAU dengan SQL
TL;DR
DAU (Daily Active Users) dihitung dengan COUNT(DISTINCT user_id) dikelompokkan per hari, sedangkan MAU (Monthly Active Users) sama tapi dikelompokkan per bulan. Rasio DAU dibagi MAU disebut stickiness, yang nunjukin seberapa sering user balik dalam sebulan. Kunci akurasinya ada di DISTINCT, biar satu user yang aktif berkali-kali cuma dihitung sekali.
DAU dan MAU dihitung dengan cara yang mirip di SQL: COUNT(DISTINCT user_id) dikelompokkan per rentang waktu. DAU per hari, MAU per bulan. Kunci akurasinya ada di DISTINCT, biar satu user yang aktif berkali-kali cuma dihitung sekali.
DAU dan MAU dua metrik wajib buat produk digital. Aplikasi, marketplace, atau SaaS mana pun ngukur kesehatannya dari sini. Angka ini yang paling sering ditanya waktu meeting produk.
Yang bikin banyak orang salah bukan query-nya, tapi lupa DISTINCT. Satu kesalahan kecil bisa nggelembungin angka sampai berkali lipat.
Apa itu DAU dan MAU?
DAU (Daily Active Users) adalah jumlah pengguna unik yang aktif dalam satu hari. MAU (Monthly Active Users) adalah jumlah pengguna unik yang aktif dalam satu bulan. Dua-duanya ngitung orang, bukan aktivitas, jadi user yang buka aplikasi 10 kali tetap dihitung satu.
Metrik ini bagian dari KPI inti produk digital. Dari sini ketahuan apakah produk lagi tumbuh atau ditinggal user.
Gimana cara hitung DAU dengan SQL?
Kelompokkin tabel events per tanggal, lalu hitung user unik tiap hari. Anggap ada tabel events dengan kolom user_id dan event_time.
SELECT
DATE(event_time) AS tanggal,
COUNT(DISTINCT user_id) AS dau
FROM events
GROUP BY DATE(event_time)
ORDER BY tanggal;
Hasilnya satu baris per tanggal dengan jumlah user aktifnya. Fungsi DATE motong bagian jam, jadi semua aktivitas di hari sama masuk satu kelompok. Buat variasi format tanggal, cek fungsi tanggal SQL.
Kenapa DISTINCT wajib di DAU?
Tanpa DISTINCT, kamu ngitung jumlah aktivitas, bukan jumlah orang. Satu user yang buka aplikasi 30 kali bakal kehitung 30. Angka DAU-mu langsung menggelembung dan nggak nyambung sama kenyataan.
-- SALAH: ngitung aktivitas, bukan user
SELECT DATE(event_time), COUNT(user_id) FROM events
GROUP BY DATE(event_time);
-- BENAR: ngitung user unik
SELECT DATE(event_time), COUNT(DISTINCT user_id) FROM events
GROUP BY DATE(event_time);
Beda satu kata, beda hasil jauh. Ini alasan COUNT dan variasinya di SQL penting dikuasai betul.
Gimana cara hitung MAU dengan SQL?
Sama seperti DAU, tapi dikelompokkan per bulan. Pakai DATE_TRUNC buat motong tanggal ke awal bulan.
SELECT
DATE_TRUNC('month', event_time) AS bulan,
COUNT(DISTINCT user_id) AS mau
FROM events
GROUP BY DATE_TRUNC('month', event_time)
ORDER BY bulan;
DATE_TRUNC ngubah semua tanggal di bulan sama jadi satu nilai, misalnya 2026-10-01. Jadi semua aktivitas Oktober masuk satu kelompok, dan user unik dihitung sekali biarpun aktif tiap hari.
Apa itu stickiness dan gimana cara hitungnya?
Stickiness adalah rasio DAU dibagi MAU, dalam persen. Dia ngukur seberapa sering user balik dalam sebulan. Stickiness 20% artinya rata-rata user aktif sekitar 6 dari 30 hari.
WITH dau AS (
SELECT DATE(event_time) AS tanggal,
COUNT(DISTINCT user_id) AS jml
FROM events
GROUP BY DATE(event_time)
),
mau AS (
SELECT DATE_TRUNC('month', event_time) AS bulan,
COUNT(DISTINCT user_id) AS jml
FROM events
GROUP BY DATE_TRUNC('month', event_time)
)
SELECT
m.bulan,
ROUND(AVG(d.jml)::numeric, 0) AS rata_dau,
m.jml AS mau,
ROUND(AVG(d.jml) / m.jml * 100, 1) AS stickiness_persen
FROM mau m
JOIN dau d ON DATE_TRUNC('month', d.tanggal) = m.bulan
GROUP BY m.bulan, m.jml
ORDER BY m.bulan;
Query ini ngitung rata-rata DAU per bulan, lalu bagi dengan MAU bulan itu. Hasilnya persentase stickiness per bulan, gampang dibandingin dari waktu ke waktu.
Gimana cara hitung rolling MAU 30 hari?
MAU per bulan kalender lompat tiap ganti bulan. Buat angka yang lebih halus, pakai rolling MAU: user unik dalam 30 hari terakhir dari tiap tanggal.
SELECT
d.tanggal,
COUNT(DISTINCT e.user_id) AS rolling_mau
FROM (SELECT DISTINCT DATE(event_time) AS tanggal FROM events) d
JOIN events e
ON DATE(e.event_time) BETWEEN d.tanggal - INTERVAL '29 days' AND d.tanggal
GROUP BY d.tanggal
ORDER BY d.tanggal;
Tiap tanggal ngitung user unik dari 29 hari ke belakang sampai hari itu. Angkanya lebih stabil, tapi query-nya lebih berat karena ngejoin rentang tanggal.
Contoh kasus: aplikasi belanja Toko Berkah
Toko Berkah, UMKM di dataset latihan ngulikdata, baru rilis aplikasi pesan antar. Data event 3 bulan pertama nunjukin MAU tumbuh dari 1.200 ke 3.400 user. Kelihatan bagus.
Tapi stickiness-nya cuma 11%. Artinya rata-rata user cuma buka aplikasi sekitar 3 hari sebulan. Angka ini ngasih sinyal beda dari MAU yang naik: orang banyak yang instal tapi jarang balik. Temuan ini bikin tim geser fokus dari akuisisi ke retensi, dan tiga bulan berikutnya stickiness naik ke 17%.
Metrik retensi ini nyambung erat sama retention rate di SQL yang ngukur berapa user yang balik setelah pertama pakai.
Kesalahan umum
1. Lupa DISTINCT
Ini paling fatal. Tanpa DISTINCT, kamu ngitung aktivitas, dan angka DAU bisa 10 kali lebih besar dari user sebenarnya.
2. Nggak nyaring event yang relevan
Kalau tabel events nyampur semua log termasuk error dan ping otomatis, saring dulu pakai WHERE. Definisikan dulu apa arti active buat produkmu.
3. Salah timezone
DATE(event_time) ngikut timezone kolom. Kalau data disimpan UTC tapi usermu di WIB, batas harinya geser 7 jam. Konversi timezone dulu biar DAU-nya pas per hari lokal.
4. Bandingin MAU tanpa lihat stickiness
MAU naik belum tentu sehat. Tanpa stickiness, kamu nggak tau apakah user beneran balik atau cuma instal lalu hilang.
Buat detail fungsi tanggal kayak DATE_TRUNC, cek dokumentasi fungsi tanggal PostgreSQL.
FAQ
Apa beda DAU dan MAU?
DAU (Daily Active Users) ngitung jumlah pengguna unik yang aktif dalam satu hari. MAU (Monthly Active Users) ngitung pengguna unik yang aktif dalam satu bulan. Bedanya cuma di rentang waktu pengelompokan. Dua-duanya pakai COUNT DISTINCT user_id biar user yang aktif berkali-kali tetap dihitung sekali. DAU nunjukin denyut harian, MAU nunjukin jangkauan bulanan.
Kenapa harus pakai COUNT DISTINCT buat DAU?
Satu user bisa buka aplikasi puluhan kali dalam sehari, dan tiap aktivitas jadi satu baris di tabel events. COUNT biasa bakal ngitung tiap baris, jadi angkanya jauh lebih besar dari jumlah orang sebenarnya. COUNT DISTINCT user_id cuma ngitung tiap user sekali, berapa pun aktivitasnya. Tanpa DISTINCT, angka DAU-mu bakal salah menggelembung.
Apa itu stickiness dan gimana hitungnya?
Stickiness adalah rasio DAU dibagi MAU, ditulis sebagai persen. Dia nunjukin seberapa sering user balik dalam sebulan. Stickiness 20 persen artinya rata-rata user aktif sekitar 6 hari dari 30 hari. Makin tinggi angkanya, makin lengket produkmu. Hitungnya, ambil rata-rata DAU dalam sebulan, bagi dengan MAU bulan itu, kali seratus.
Gimana cara hitung rolling MAU 30 hari?
Rolling MAU ngitung user unik dalam 30 hari terakhir dari tiap tanggal, bukan per bulan kalender. Caranya, buat daftar tanggal, lalu buat tiap tanggal hitung COUNT DISTINCT user_id dari events dalam rentang 29 hari ke belakang sampai tanggal itu. Biasanya pakai self join atau window function. Angkanya lebih halus karena nggak lompat tiap ganti bulan.
Kegiatan apa yang dihitung sebagai active?
Definisi active tergantung produkmu, dan ini keputusan penting sebelum nulis query. Buat sebagian produk, buka aplikasi udah cukup. Buat yang lain, active berarti ngelakuin aksi bermakna kayak transaksi atau posting. Saring dulu tabel events sesuai definisi itu pakai WHERE sebelum COUNT DISTINCT, biar angka DAU dan MAU kamu benar-benar ngukur yang kamu maksud.
Penutup
- DAU dan MAU sama-sama pakai COUNT DISTINCT user_id, cuma beda rentang: per hari vs per bulan.
- DISTINCT wajib, kalau nggak kamu ngitung aktivitas bukan orang.
- Stickiness (DAU dibagi MAU) ngasih gambaran retensi yang MAU sendiri nggak bisa.
Coba sekarang: ambil satu tabel events, hitung DAU seminggu terakhir, lalu MAU-nya, dan bagi buat dapat stickiness. Angka itu sering ngungkap hal yang grafik pertumbuhan sembunyiin.
Mau lanjut ngukur kesehatan produk? Baca GROUP BY dan HAVING di SQL buat nyaring hasil agregasi kayak ini.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel terkait
Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain
Semi join ngambil baris yang punya pasangan di tabel lain, anti join ngambil yang nggak punya. Keduanya filter, bukan gabung kolom. Ini cara nulisnya di SQL pakai EXISTS dan NOT EXISTS.
Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom
Crosstab di PostgreSQL mivot data dari format panjang (baris) jadi format lebar (kolom), mirip pivot table Excel. Butuh extension tablefunc, dan ada alternatif pakai CASE atau FILTER kalau kolomnya tetap.
INSERT ON CONFLICT di PostgreSQL
INSERT ON CONFLICT bikin PostgreSQL insert baris baru atau update yang udah ada dalam satu perintah, tanpa error gara-gara duplikat primary key.