TL;DR
Sessionization SQL adalah teknik ngelompokkan event berurutan dari satu user jadi sesi, berdasarkan jeda waktu antar event. Caranya: pakai LAG buat ambil timestamp event sebelumnya, tandai event yang jedanya lewat ambang batas (umumnya 30 menit) sebagai awal sesi baru, lalu SUM kumulatif penanda itu buat bikin session_id. Hasilnya bisa dipakai ngitung durasi sesi, event per sesi, dan sesi per user.
Sessionization SQL adalah teknik ngelompokkan event berurutan dari satu user jadi sesi, berdasarkan jeda waktu antar event.
Masalahnya gini. Log event kamu isinya ribuan baris: user 88 buka halaman produk jam 09:14, klik keranjang 09:16, terus muncul lagi jam 20:41. Itu satu sesi atau dua?
Dua. Dan SQL nggak tau itu sampai kamu yang kasih tau aturannya.
Sessionization ngasih nomor sesi ke tiap baris event, dengan aturan: kalau jeda dari event sebelumnya lewat ambang batas tertentu, event itu jadi awal sesi baru.
Kamu butuh ini kalau mau jawab pertanyaan kayak:
Tanpa sesi, kamu cuma bisa ngitung total event dan total user. Dua angka yang nggak ngasih tau apa-apa soal perilaku.
Angka 30 menit warisan Google Analytics, dan udah jadi standar de facto. Tapi itu bukan hukum alam.
Buat aplikasi ojek online yang dipakai cepat, 15 menit lebih masuk akal. Buat marketplace yang orang suka bandingin harga sambil ngerjain hal lain, 60 menit lebih realistis.
Cara nentuinnya: lihat distribusi jeda antar event, cari titik di mana jumlah jeda mulai anjlok. Itu batas alaminya.
Tiga langkah. Semuanya pakai window function.
SELECT user_id,
waktu,
halaman,
LAG(waktu) OVER (PARTITION BY user_id ORDER BY waktu) AS waktu_sebelumnya
FROM event_log;
LAG ngambil nilai dari baris sebelumnya. PARTITION BY user_id bikin perhitungannya reset tiap ganti user. Event user lain nggak nyampur. Detail cara kerjanya ada di halaman fungsi LAG.
Event pertama tiap user bakal punya waktu_sebelumnya = NULL. Itu wajar, dan nanti kita perlakuin sebagai awal sesi.
WITH dengan_lag AS (
SELECT user_id, waktu, halaman,
LAG(waktu) OVER (PARTITION BY user_id ORDER BY waktu) AS waktu_sebelumnya
FROM event_log
)
SELECT user_id, waktu, halaman,
CASE
WHEN waktu_sebelumnya IS NULL THEN 1
WHEN waktu - waktu_sebelumnya > INTERVAL '30 minutes' THEN 1
ELSE 0
END AS awal_sesi
FROM dengan_lag;
Sekarang tiap baris punya flag 1 atau 0. Angka 1 artinya "di sini sesi baru dimulai".
Ini bagian yang bikin orang mikir agak lama pas pertama lihat.
SELECT user_id, waktu, halaman,
SUM(awal_sesi) OVER (
PARTITION BY user_id
ORDER BY waktu
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS nomor_sesi
FROM ditandai;
SUM kumulatif ngejumlahin flag dari baris pertama sampai baris sekarang. Selama flagnya 0, jumlahnya nggak berubah. Jadi event-event itu dapat nomor sesi yang sama. Begitu ketemu flag 1, jumlahnya naik satu, dan sesi baru dimulai.
Hasilnya: event 1, 2, 3 dapat nomor sesi 1. Event 4 (yang jedanya 11 jam) dapat nomor sesi 2. Persis yang kita mau.
Dataset event_log dari toko online toko_berkah: 62.400 event dari 4.180 user selama Maret 2026.
Contoh baris mentah buat user 88:
user_id waktu halaman
88 2026-03-14 09:14:02 /produk/beras-5kg
88 2026-03-14 09:16:41 /keranjang
88 2026-03-14 09:18:03 /checkout
88 2026-03-14 20:41:15 /produk/minyak-goreng
88 2026-03-14 20:43:50 /keranjang
Setelah sessionization dengan ambang 30 menit, user 88 punya 2 sesi: satu pagi (3 event, durasi 4 menit) dan satu malam (2 event, durasi 2,5 menit).
Query ringkasan per sesi:
SELECT user_id,
nomor_sesi,
MIN(waktu) AS mulai,
MAX(waktu) - MIN(waktu) AS durasi,
COUNT(*) AS jumlah_event
FROM sesi
GROUP BY user_id, nomor_sesi;
Angka agregatnya menarik:
| Metrik | Nilai |
|---|---|
| Total sesi | 9.640 |
| Sesi per user | 2,31 |
| Event per sesi | 6,47 |
| Median durasi sesi | 3 menit 12 detik |
| Sesi berisi 1 event doang | 28% |
Yang bikin aku berhenti: 28 persen sesi cuma berisi satu event. Orang buka satu halaman, terus pergi.
Kalau kamu masukin sesi-sesi ini ke rata-rata durasi, angkanya keseret ke bawah. Rata-rata jatuh ke 2 menit 4 detik, padahal sesi yang beneran aktif punya median 5 menit 40 detik.
Kesimpulannya beda total tergantung kamu keluarin sesi satu-event atau nggak. Ini keputusan yang harus kamu ambil sadar-sadar, bukan kebetulan.
RANGE bakal ngasih hasil yang beda dari yang kamu harapin.Sintaks window function ini standar SQL, tapi tiap database punya aturan interval yang beda. Cek dokumentasi resmi PostgreSQL soal window function kalau kamu pakai Postgres.
Sessionization adalah proses ngelompokkan event berurutan dari satu user jadi kelompok yang disebut sesi. Aturannya berbasis jeda waktu: kalau jarak antara satu event dengan event sebelumnya lewat ambang batas tertentu, event itu dianggap awal sesi baru. Di SQL, ini dikerjain pakai LAG buat lihat event sebelumnya, terus SUM kumulatif buat ngasih nomor sesi.
Angka 30 menit itu warisan dari Google Analytics dan udah jadi standar de facto, bukan hukum alam. Buat aplikasi yang dipakai cepat kayak ojek online, 15 menit lebih masuk akal. Cara nentuinnya: lihat distribusi jeda antar event, cari titik di mana jumlah jeda mulai anjlok.
Bisa tapi jauh lebih ribet dan lambat, biasanya pakai self-join yang bandingin tiap baris dengan baris sebelumnya. Semua database modern yang umum dipakai (PostgreSQL, MySQL 8, BigQuery, SQL Server) udah dukung LAG dan SUM OVER.
Setelah tiap event punya session_id, kelompokkan per session_id lalu kurangin timestamp terakhir dengan timestamp pertama. Sesi yang cuma berisi satu event bakal keluar nol, dan itu bukan bug. Banyak tim yang milih ngeluarin sesi satu-event dari rata-rata durasi supaya angkanya nggak keseret ke bawah.
Satu user bisa punya banyak sesi. Buka aplikasi pagi, siang, dan malam = satu user tiga sesi. Makanya metrik kayak conversion rate harus jelas pembaginya: per sesi atau per user. Dua angka itu bisa beda jauh.
Rangkumannya:
Kalau window function masih terasa asing, mulai dari fungsi LAG dan definisi window function di glossary. Dua itu pondasi buat semua query pola-waktu.
Habis ini, coba pakai hasil sesinya buat bikin chart tren harian. Aturannya ada di kapan pakai line chart.
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.