Soal 1Dua karyawan sama di peringkat ke-5 dengan total yang sama. Peringkat apa yang didapat orang berikutnya di bawah RANK() vs. DENSE_RANK()?
Window Function ③ — Ranking, Fungsi Analitik, dan Urutan Eksekusi
Telusuri bagaimana ROW_NUMBER / RANK / DENSE_RANK menangani nilai sama, bagaimana LAG / LEAD menarik nilai dari baris sebelumnya / berikutnya, bagaimana NTILE(4) membagi baris menjadi n grup, dan mengapa kamu harus memakai subquery untuk filter pada peringkat — semuanya dengan data sales.
Ranking — ROW_NUMBER / RANK / DENSE_RANK
Fungsi ranking khusus adalah ROW_NUMBER() / RANK() / DENSE_RANK().
Ketiganya memberi nomor pada baris dalam urutan yang diberikan oleh OVER (ORDER BY ...), tetapi mereka menangani nilai sama (equal values) secara berbeda.
| Fungsi | Cara nilai sama (equal values) ditangani |
|---|---|
ROW_NUMBER() | Memaksa penomoran berurutan bahkan pada nilai sama (selalu 1, 2, 3, ...) |
RANK() | Nilai sama berbagi peringkat yang sama, lalu peringkat berikutnya melompat sebanyak jumlah yang sama (2 sama di urutan 5 → berikutnya 7) |
DENSE_RANK() | Nilai sama berbagi peringkat yang sama, tetapi peringkat berikutnya tidak melompat (baris setelah nilai sama di urutan 5 adalah 6) |
SQL di bawah — dan diagram yang memvisualkan hasilnya — menelusuri kasus konkret dengan total yang nilainya sama baris demi baris.
-- Hitung total penjualan tiap karyawan, lalu peringkatkan dalam urutan total menurun dengan 3 cara
SELECT emp_id,
SUM(amount) AS total,
ROW_NUMBER() OVER (ORDER BY SUM(amount) DESC) AS rn,
RANK() OVER (ORDER BY SUM(amount) DESC) AS rk,
DENSE_RANK() OVER (ORDER BY SUM(amount) DESC) AS dr
FROM sales
GROUP BY emp_id
ORDER BY total DESC;
-- Kamu bisa pertama-tama agregasi dengan GROUP BY dan kemudian peringkatkan agregat tersebut
LAG / LEAD — tarik nilai dari baris sebelumnya / berikutnya
LAG(column) menarik nilai dari baris sebelumnya ke baris saat ini, dan LEAD(column) menarik dari baris berikutnya (lag = tertinggal, lead = mendahului).
OVER (PARTITION BY ... ORDER BY ...) menentukan apa arti "sebelumnya" dan "berikutnya" dengan menetapkan urutan.
Pakai untuk selisih periode-ke-periode (vs. penjualan sebelumnya), delta vs. bulan lalu, atau perbandingan dengan acara terjadwal berikutnya.
LAG(amount) mengembalikan NULL pada baris pertama tiap partisi (tidak ada baris sebelumnya); demikian pula LEAD adalah NULL pada baris terakhir.
Argumen kedua opsional mengontrol offset ("berapa baris ke belakang") dan yang ketiga menetapkan nilai default ketika baris target tidak ada — misal, LAG(amount, 1, 0).
Kurangkan dengan amount - LAG(amount) OVER (...) untuk mendapatkan selisih vs. baris sebelumnya secara langsung.
-- Tampilkan penjualan sebelumnya (LAG) dan selisih vs. penjualan sebelumnya
SELECT emp_id, sale_date, amount,
LAG(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) AS prev_amount,
amount - LAG(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) AS diff_prev
FROM sales
ORDER BY emp_id, sale_date;
-- Pakai LEAD jika kamu ingin menampilkan penjualan berikutnya di samping baris saat ini
SELECT emp_id, sale_date, amount,
LEAD(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) AS next_amount
FROM sales
ORDER BY emp_id, sale_date;
NTILE — bagi baris yang diurutkan menjadi n grup berukuran merata
NTILE(n) membagi baris yang diurutkan oleh OVER (ORDER BY ...) menjadi n grup berukuran semerata mungkin, memberi label setiap baris dengan nomor grup 1 sampai n.
Pakai untuk kuartil, kuintil, "top 25%", dan bucketing serupa.
Ketika jumlah baris tidak habis dibagi n, grup-grup awal mendapat tambahan satu baris masing-masing (misal, membagi 5 baris menjadi 4 grup menghasilkan 2 / 1 / 1 / 1).
Gabungkan dengan PARTITION BY untuk hal seperti "bagi penjualan setiap karyawan menjadi bucket atas / bawah dalam karyawan tersebut."
Diagram di bawah menunjukkan apa yang terjadi ketika 5 penjualan emp_id=11 diurutkan berdasarkan amount menurun dan dibagi dengan NTILE(4).
-- Per karyawan, bagi penjualan karyawan tersebut menjadi 4 grup berdasarkan amount menurun
SELECT emp_id, sale_date, amount,
NTILE(4) OVER (PARTITION BY emp_id ORDER BY amount DESC) AS quartile
FROM sales
ORDER BY emp_id, amount DESC;
-- Bagi semua penjualan menjadi 4 bucket berdasarkan amount (top 25% memiliki quartile = 1)
SELECT sale_id, emp_id, amount,
NTILE(4) OVER (ORDER BY amount DESC) AS quartile
FROM sales
ORDER BY amount DESC;
Urutan eksekusi SQL — mengapa kamu tidak bisa menaruh window function di WHERE
Kamu mungkin bertanya, "tidak bisa kah saya menyaring berdasarkan peringkat yang baru saja saya hitung, seperti WHERE rk = 1?"
Kamu akan mendapat error. Alasannya adalah urutan eksekusi logis SQL.
Urutan kamu menulis klausa (SELECT → FROM → WHERE ...) bukan urutan ketika mereka dievaluasi.
Evaluasi kira-kira berjalan: FROM / JOIN (susun tabel) → WHERE (saring baris) → GROUP BY (kelompokkan) → HAVING (saring grup) → SELECT (hitung kolom — di sinilah window function jalan) → ORDER BY (urutkan) → LIMIT (potong).
Karena WHERE jalan sebelum SELECT, ia tidak bisa melihat peringkat yang akan dihitung SELECT.
Untuk filter pada peringkat, dorong query yang menghitung window function ke dalam subquery (atau CTE) dan terapkan WHERE di luar.
SELECT bagian dalam selesai menghitung window function, dan query luar lalu bisa memperlakukan hasil itu sebagai kolom biasa.
-- Ini error: WHERE jalan sebelum SELECT, jadi rk belum ada
-- SELECT emp_id, RANK() OVER (ORDER BY SUM(amount) DESC) AS rk
-- FROM sales GROUP BY emp_id WHERE rk <= 3;
-- Benar: hitung peringkat di subquery dalam, filter di luar
SELECT * FROM (
SELECT emp_id,
SUM(amount) AS total,
RANK() OVER (ORDER BY SUM(amount) DESC) AS rk
FROM sales
GROUP BY emp_id
) AS ranked
WHERE rk <= 3
ORDER BY rk, emp_id;
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Apa yang dikembalikan LAG(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) pada baris penjualan pertama tiap karyawan?
Soal 3Mengapa SELECT emp_id, RANK() OVER (ORDER BY SUM(amount) DESC) AS rk FROM sales GROUP BY emp_id WHERE rk <= 3; tidak bekerja?