Window Functions — SQL

Window Functions adalah fungsi yang menghitung nilai berdasarkan "jendela" baris — beda dari aggregate function yang me-collapse banyak baris jadi satu…

Window function menghitung sesuatu dari sekelompok baris, tapi tiap baris tetap muncul, dengan hasil hitungannya di kolom sebelahnya. Sekelompok baris itu disebut window. Bayangin rapor kelas: tiap siswa tetap punya barisnya sendiri, tapi di sebelah nilainya ada kolom "peringkat di kelas". Untuk mengisi kolom itu, guru melihat semua siswa di kelas yang sama, tanpa menggabungkan mereka jadi satu baris. Kelas itu window-nya.

🧪 Tebakan cepat: SUM(salary) ... GROUP BY department menghasilkan 2 baris. Kalau diganti SUM(salary) OVER (PARTITION BY department), hasilnya berapa baris? Tebak dulu, jawabannya ada di bagian "GROUP BY melipat, window enggak".

GROUP BY melipat, window enggak

Datanya tabel employees dari latihan, 5 karyawan (id, name, department, salary): Budi (IT, 5.000.000), Siti (IT, 8.000.000), Andi (IT, 6.500.000), Dewi (HR, 7.000.000), dan Rizky (HR, 4.500.000).

Di pelajaran GROUP BY, kamu menjumlah per kelompok. SELECT department, SUM(salary) AS total FROM employees GROUP BY department; menghasilkan 2 baris: HR 11.500.000 dan IT 19.500.000. Lima baris dilipat jadi dua, dan nama karyawannya hilang. Sekarang versi window:

SELECT name, department, salary,
       SUM(salary) OVER (PARTITION BY department) AS total
FROM employees;
name department salary total
Dewi HR 7.000.000 11.500.000
Rizky HR 4.500.000 11.500.000
Budi IT 5.000.000 19.500.000
Siti IT 8.000.000 19.500.000
Andi IT 6.500.000 19.500.000

Lima baris tetap lima. Itu jawaban tebakannya: tiap karyawan tetap ada, dengan total departemennya di kolom sebelah.

Isi kurung OVER punya tiga bagian, semuanya boleh gak ada: PARTITION BY (bagi jadi beberapa window), ORDER BY (urutan baris di dalam tiap window), dan frame seperti ROWS BETWEEN ... (baris mana yang ikut dihitung).

Ranking: ROW_NUMBER, RANK, DENSE_RANK

Ini query latihannya: nomor urut gaji di tiap departemen.

SELECT name, department, salary,
       ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank
FROM employees
ORDER BY department ASC, rank ASC;
name department salary rank
Dewi HR 7.000.000 1
Rizky HR 4.500.000 2
Siti IT 8.000.000 1
Andi IT 6.500.000 2
Budi IT 5.000.000 3

Beda tiga fungsi ranking baru kelihatan kalau ada yang seri. Misalnya Joko masuk IT dengan gaji 6.500.000, sama dengan Andi. Hasil SQLite di window IT:

name salary ROW_NUMBER RANK DENSE_RANK
Siti 8.000.000 1 1 1
Andi 6.500.000 2 2 2
Joko 6.500.000 3 2 2
Budi 5.000.000 4 4 3

Running total dan LAG

Saldo di mutasi rekening itu running total: jumlah semua transaksi dari awal sampai baris itu. LAG mengambil nilai dari baris sebelumnya, seperti membandingkan omzet hari ini dengan kemarin.

SELECT name, department, salary,
       SUM(salary) OVER (
         PARTITION BY department
         ORDER BY salary DESC
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS running_total,
       LAG(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS prev_salary
FROM employees
ORDER BY department, salary DESC;
name department salary running_total prev_salary
Dewi HR 7.000.000 7.000.000 NULL
Rizky HR 4.500.000 11.500.000 7.000.000
Siti IT 8.000.000 8.000.000 NULL
Andi IT 6.500.000 14.500.000 8.000.000
Budi IT 5.000.000 19.500.000 6.500.000

Top-N per kelompok: window + CTE

Mau 2 gaji tertinggi di tiap departemen? WHERE rn <= 2 langsung di query yang sama gak bisa, karena database mengerjakan WHERE sebelum menghitung kolom di SELECT. Hitung nomornya dulu di CTE (pelajaran sebelumnya), lalu saring di query utama:

WITH ranked AS (
  SELECT name, department, salary,
         ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
)
SELECT * FROM ranked WHERE rn <= 2;

Hasilnya 4 baris: Dewi dan Rizky dari HR, Siti dan Andi dari IT. Ganti PARTITION BY department dengan PARTITION BY email dan ORDER BY created_at DESC, dan pola yang sama membuang data dobel: simpan rn = 1, yaitu baris terbaru tiap email.

📌 Beda SQLite, PostgreSQL dan MySQL

Latihan dan Lab di sini memakai SQLite. Window function ada di SQLite 3.25 ke atas, PostgreSQL, dan MySQL 8.0 ke atas; MySQL 5.7 belum punya.

⚠️ Jebakan yang sering ketemu

TL;DR: Window function menghitung dari sekelompok baris (window) tanpa melipat baris: fungsi() OVER (PARTITION BY ... ORDER BY ... frame). GROUP BY memberi 1 baris per kelompok, SUM(...) OVER (PARTITION BY ...) memberi semua baris dengan total kelompoknya. ROW_NUMBER selalu memberi nomor beda (pas untuk top-N), RANK memberi nomor sama untuk yang seri lalu melompat, DENSE_RANK gak melompat. SUM(...) OVER (ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) adalah running total, LAG mengambil baris sebelumnya. Hasil window function gak bisa disaring di WHERE query yang sama: hitung dulu di CTE.

Yang akan kamu pelajari