Kompetisi nyata mengenal hasil seri: dua peserta dengan skor sama sama-sama menjadi juara pertama, lalu peserta berikutnya otomatis berperingkat tiga. ROW_NUMBER dari chapter sebelumnya jujur-jujur saja tidak bisa memodelkan itu — fungsinya memaksa setiap baris menerima nomor unik, sehingga dua peserta yang seri dipaksa berbeda. Keluarga fungsi ranking hadir untuk memperbaikinya: RANK memberi peringkat yang melompat setelah seri, DENSE_RANK memberi peringkat yang menumpuk rapat, dan NTILE membelah peserta menjadi kelompok-kelompok yang ukurannya seimbang. Chapter ini membedah ketiganya di atas data penjualan yang sama, lengkap dengan dua jebakan yang paling sering menjatuhkan pemula.

Tabel Latihan #

Domain latihan tetap penjualan_bulanan — 16 baris penjualan bulanan per produk Toko Maju Jaya sepanjang semester I 2026. Nilai jumlah yang sengaja seri di beberapa tempat (Kopi Gayo 150 unit pada April dan Mei, Teh Melati 80 unit pada Januari dan Maret, Gula Pasir 70 unit pada Maret dan April) adalah bahan bakar utama demo ranking:

sql buat-tabel.sql
-- tabel latihan: penjualan bulanan per produk Toko Maju Jaya, semester I 2026
CREATE TABLE penjualan_bulanan (
    id      integer PRIMARY KEY,
    produk  text NOT NULL,
    bulan   date NOT NULL,
    kanal   text NOT NULL,
    jumlah  integer NOT NULL
);

INSERT INTO penjualan_bulanan (id, produk, bulan, kanal, jumlah) VALUES
    ( 1, 'Kopi Gayo',  DATE '2026-01-01', 'toko',   120),
    ( 2, 'Teh Melati', DATE '2026-01-01', 'toko',    80),
    ( 3, 'Kopi Gayo',  DATE '2026-02-01', 'online', 135),
    ( 4, 'Teh Melati', DATE '2026-02-01', 'toko',    95),
    ( 5, 'Gula Pasir', DATE '2026-02-01', 'toko',    60),
    ( 6, 'Gula Pasir', DATE '2026-03-01', 'toko',    70),
    ( 7, 'Kopi Gayo',  DATE '2026-03-01', 'toko',   128),
    ( 8, 'Teh Melati', DATE '2026-03-01', 'toko',    80),
    ( 9, 'Gula Pasir', DATE '2026-04-01', 'toko',    70),
    (10, 'Kopi Gayo',  DATE '2026-04-01', 'online', 150),
    (11, 'Teh Melati', DATE '2026-04-01', 'toko',   110),
    (12, 'Kopi Gayo',  DATE '2026-05-01', 'online', 150),
    (13, 'Gula Pasir', DATE '2026-05-01', 'toko',    55),
    (14, 'Kopi Gayo',  DATE '2026-06-01', 'online', 160),
    (15, 'Teh Melati', DATE '2026-06-01', 'online', 130),
    (16, 'Gula Pasir', DATE '2026-06-01', 'toko',    75);

SELECT id, produk, bulan, kanal, jumlah
FROM penjualan_bulanan
ORDER BY bulan, produk;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 16
 id |   produk   |   bulan    | kanal  | jumlah 
----+------------+------------+--------+--------
  1 | Kopi Gayo  | 2026-01-01 | toko   |    120
  2 | Teh Melati | 2026-01-01 | toko   |     80
  5 | Gula Pasir | 2026-02-01 | toko   |     60
  3 | Kopi Gayo  | 2026-02-01 | online |    135
  4 | Teh Melati | 2026-02-01 | toko   |     95
  6 | Gula Pasir | 2026-03-01 | toko   |     70
  7 | Kopi Gayo  | 2026-03-01 | toko   |    128
  8 | Teh Melati | 2026-03-01 | toko   |     80
  9 | Gula Pasir | 2026-04-01 | toko   |     70
 10 | Kopi Gayo  | 2026-04-01 | online |    150
 11 | Teh Melati | 2026-04-01 | toko   |    110
 13 | Gula Pasir | 2026-05-01 | toko   |     55
 12 | Kopi Gayo  | 2026-05-01 | online |    150
 16 | Gula Pasir | 2026-06-01 | toko   |     75
 14 | Kopi Gayo  | 2026-06-01 | online |    160
 15 | Teh Melati | 2026-06-01 | online |    130
(16 rows)

RANK vs DENSE_RANK: Melompat atau Menumpuk #

Mekanisme kedua fungsi ini hanya berbeda satu keputusan: apa yang terjadi pada baris setelah sebuah seri. RANK memberi baris-baris seri peringkat yang sama, lalu melompat — peringkat berikutnya dihitung dari posisi baris. DENSE_RANK juga memberi peringkat sama untuk seri, tetapi peringkat berikutnya langsung menyambung tanpa lompatan. Mari kita lihat keduanya berdampingan:

-- RANK melompat setelah seri; DENSE_RANK tidak; ROW_NUMBER memaksa urutan unik

-- (a) satu jendela global: tiga fungsi berdampingan atas urutan jumlah tertinggi
SELECT bulan,
       produk,
       jumlah,
       row_number() OVER (ORDER BY jumlah DESC, id) AS nomor,
       rank()       OVER (ORDER BY jumlah DESC)     AS peringkat,
       dense_rank() OVER (ORDER BY jumlah DESC)     AS peringkat_padat
FROM penjualan_bulanan
ORDER BY jumlah DESC, id;

-- (b) leaderboard per produk: ranking di dalam partisi
SELECT bulan,
       produk,
       jumlah,
       rank()       OVER (PARTITION BY produk ORDER BY jumlah DESC) AS peringkat,
       dense_rank() OVER (PARTITION BY produk ORDER BY jumlah DESC) AS peringkat_padat
FROM penjualan_bulanan
ORDER BY produk, jumlah DESC, bulan;

Output yang Dihasilkan:

text
   bulan    |   produk   | jumlah | nomor | peringkat | peringkat_padat 
------------+------------+--------+-------+-----------+-----------------
 2026-06-01 | Kopi Gayo  |    160 |     1 |         1 |               1
 2026-04-01 | Kopi Gayo  |    150 |     2 |         2 |               2
 2026-05-01 | Kopi Gayo  |    150 |     3 |         2 |               2
 2026-02-01 | Kopi Gayo  |    135 |     4 |         4 |               3
 2026-06-01 | Teh Melati |    130 |     5 |         5 |               4
 2026-03-01 | Kopi Gayo  |    128 |     6 |         6 |               5
 2026-01-01 | Kopi Gayo  |    120 |     7 |         7 |               6
 2026-04-01 | Teh Melati |    110 |     8 |         8 |               7
 2026-02-01 | Teh Melati |     95 |     9 |         9 |               8
 2026-01-01 | Teh Melati |     80 |    10 |        10 |               9
 2026-03-01 | Teh Melati |     80 |    11 |        10 |               9
 2026-06-01 | Gula Pasir |     75 |    12 |        12 |              10
 2026-03-01 | Gula Pasir |     70 |    13 |        13 |              11
 2026-04-01 | Gula Pasir |     70 |    14 |        13 |              11
 2026-02-01 | Gula Pasir |     60 |    15 |        15 |              12
 2026-05-01 | Gula Pasir |     55 |    16 |        16 |              13
(16 rows)

   bulan    |   produk   | jumlah | peringkat | peringkat_padat 
------------+------------+--------+-----------+-----------------
 2026-06-01 | Gula Pasir |     75 |         1 |               1
 2026-03-01 | Gula Pasir |     70 |         2 |               2
 2026-04-01 | Gula Pasir |     70 |         2 |               2
 2026-02-01 | Gula Pasir |     60 |         4 |               3
 2026-05-01 | Gula Pasir |     55 |         5 |               4
 2026-06-01 | Kopi Gayo  |    160 |         1 |               1
 2026-04-01 | Kopi Gayo  |    150 |         2 |               2
 2026-05-01 | Kopi Gayo  |    150 |         2 |               2
 2026-02-01 | Kopi Gayo  |    135 |         4 |               3
 2026-03-01 | Kopi Gayo  |    128 |         5 |               4
 2026-01-01 | Kopi Gayo  |    120 |         6 |               5
 2026-06-01 | Teh Melati |    130 |         1 |               1
 2026-04-01 | Teh Melati |    110 |         2 |               2
 2026-02-01 | Teh Melati |     95 |         3 |               3
 2026-01-01 | Teh Melati |     80 |         4 |               4
 2026-03-01 | Teh Melati |     80 |         4 |               4
(16 rows)

Ambil empat baris teratas query (a) sebagai kunci pembacaannya. Juni milik Kopi Gayo (160 unit) mendapat nomor 1, peringkat 1, peringkat padat 1 — ketiganya setuju. April dan Mei milik Kopi Gayo (150 unit) lalu seri: keduanya menerima peringkat 2, dan ROW_NUMBER — yang butuh nomor unik — memisahkannya menjadi 2 dan 3 dengan tie-breaker id. Baris berikutnya, Febuari Kopi Gayo (135 unit), menjadi moment of truth: RANK melompat ke 4 (dua baris di depannya), DENSE_RANK menyambung ke 3, dan ROW_NUMBER berlanjut ke 4. Pola yang sama berulang di tiap seri: kedua bulan Teh Melati berjumlah 80 sama-sama berperingkat 10 lalu Gula Pasir 75 melompat ke 12, dan kedua bulan Gula Pasir berjumlah 70 sama-sama berperingkat 13 sebelum 60 melompat ke 15.

Query (b) memindahkan kompetisi ke dalam partisi — per produk — sehingga tiap produk punya papan peringkat sendiri yang dimulai dari 1. Di papan Kopi Gayo, seri 150 menghasilkan peringkat 2, 2, lalu 135 mendapat 4; di papan padatnya, 135 cukup mendapat 3. Tidak ada satu cara yang “benar” — keduanya menjawab pertanyaan berbeda:

PertanyaanFungsi yang tepatPerilaku setelah seri
“Saya peserta ke berapa di antara semua baris?”RANKmelompat: 1, 2, 2, 4
“Ada berapa tingkat nilai berbeda di depan saya?”DENSE_RANKmenumpuk: 1, 2, 2, 3
“Butuh nomor unik untuk tiap baris?”ROW_NUMBERselalu 1, 2, 3, 4

Jebakan: RANK tanpa ORDER BY Tidak Error #

Fungsi ranking tanpa ORDER BY di dalam OVER terasa seperti kesalahan yang seharusnya ditolak server. Kenyataannya tidak — dan justru di situ bahayanya. Tanpa ORDER BY, semua baris dalam partisi dianggap setara (seri sempurna), sehingga RANK memberi mereka peringkat yang sama: 1:

-- jebakan: RANK tanpa ORDER BY tidak error — semua baris dianggap setara
SELECT bulan,
       produk,
       jumlah,
       rank() OVER (PARTITION BY produk) AS peringkat
FROM penjualan_bulanan
ORDER BY bulan, produk;

Output yang Dihasilkan:

text
   bulan    |   produk   | jumlah | peringkat 
------------+------------+--------+-----------
 2026-01-01 | Kopi Gayo  |    120 |         1
 2026-01-01 | Teh Melati |     80 |         1
 2026-02-01 | Gula Pasir |     60 |         1
 2026-02-01 | Kopi Gayo  |    135 |         1
 2026-02-01 | Teh Melati |     95 |         1
 2026-03-01 | Gula Pasir |     70 |         1
 2026-03-01 | Kopi Gayo  |    128 |         1
 2026-03-01 | Teh Melati |     80 |         1
 2026-04-01 | Gula Pasir |     70 |         1
 2026-04-01 | Kopi Gayo  |    150 |         1
 2026-04-01 | Teh Melati |    110 |         1
 2026-05-01 | Gula Pasir |     55 |         1
 2026-05-01 | Kopi Gayo  |    150 |         1
 2026-06-01 | Gula Pasir |     75 |         1
 2026-06-01 | Kopi Gayo  |    160 |         1
 2026-06-01 | Teh Melati |    130 |         1
(16 rows)

Enam belas baris keluaran semuanya berperingkat 1 — query berjalan mulus, tidak ada error, tidak ada warning, dan laporan Anda salah diam-diam. Output ini sendiri deterministik (seri sempurna selalu menghasilkan 1 untuk semua baris), tetapi maknanya kosong: tanpa kriteria urutan, “peringkat” tidak bermakna.

Leaderboard yang Menghormati Seri #

Kembali ke janji di akhir chapter ROW_NUMBER : bagaimana bila “dua bulan terbaik” harus menampilkan semua baris yang seri di posisi ke-2? Polanya masih derived table, tetapi saringannya berganti dari nomor ke peringkat:

-- leaderboard "dua bulan terbaik": rank mempertahankan seri, row_number memotongnya

-- (a) ROW_NUMBER: tepat 2 baris — seri 150 dipotong oleh tie-breaker bulan
SELECT bulan, produk, jumlah
FROM (
    SELECT bulan, produk, jumlah,
           row_number() OVER (ORDER BY jumlah DESC, bulan) AS rn
    FROM penjualan_bulanan
) AS t
WHERE rn <= 2
ORDER BY jumlah DESC, bulan;

-- (b) RANK: 3 baris — kedua bulan berjumlah 150 sama-sama berperingkat 2
SELECT bulan, produk, jumlah
FROM (
    SELECT bulan, produk, jumlah,
           rank() OVER (ORDER BY jumlah DESC) AS peringkat
    FROM penjualan_bulanan
) AS t
WHERE peringkat <= 2
ORDER BY jumlah DESC, bulan;

Output yang Dihasilkan:

text
   bulan    |  produk   | jumlah 
------------+-----------+--------
 2026-06-01 | Kopi Gayo |    160
 2026-04-01 | Kopi Gayo |    150
(2 rows)

   bulan    |  produk   | jumlah 
------------+-----------+--------
 2026-06-01 | Kopi Gayo |    160
 2026-04-01 | Kopi Gayo |    150
 2026-05-01 | Kopi Gayo |    150
(3 rows)

Query (a) mengembalikan tepat dua baris — Juni (160) dan April (150); Mei yang juga 150 unit terbuang oleh tie-breaker. Query (b) mengembalikan tiga baris: Juni berperingkat 1, lalu April dan Mei sama-sama berperingkat 2 karena RANK memperlakukan keduanya setara. Pilihan di antara keduanya adalah kebijakan bisnis, bukan teknis: “tepat N baris” memakai ROW_NUMBER, “semua yang berhak masuk N besar” memakai RANK.

Menyaring Peringkat Tidak Bisa lewat HAVING #

Godaan berikutnya: karena HAVING boleh memuat agregat, seolah-olah ia juga boleh memuat rank() — misalnya “dua produk dengan total tertinggi”. PostgreSQL menolaknya:

-- mencoba menyaring peringkat di HAVING: error yang diharapkan
SELECT produk,
       sum(jumlah) AS total
FROM penjualan_bulanan
GROUP BY produk
HAVING rank() OVER (ORDER BY sum(jumlah) DESC) <= 2
ORDER BY produk;

Output yang Dihasilkan:

text
psql:rank-di-having-error.sql:7: ERROR:  window functions are not allowed in HAVING
LINE 5: HAVING rank() OVER (ORDER BY sum(jumlah) DESC) <= 2
               ^

Pesan window functions are not allowed in HAVING adalah saudara dari not allowed in WHERE yang kita temui di chapter Konsep Window Function : window function dihitung setelah GROUP BY dan HAVING selesai, jadi klausa-klausa itu tidak mungkin menyandarkan kondisinya pada hasil window. Solusinya sudah terlihat dua kali di chapter ini — hitung peringkat di query dalam, lalu saring dengan WHERE peringkat <= 2 di query luar, persis pola file leaderboard-seri.sql.

NTILE dan Persentil #

NTILE(n) menempatkan baris ke dalam n kelompok yang ukurannya dibuat seimbang: baris diurutkan menurut ORDER BY di dalam OVER, lalu dibagi dari atas ke bawah. Kelompok awal boleh berisi satu baris lebih banyak bila jumlah baris tidak habis dibagi. Sebagai pelengkap, PERCENT_RANK dan CUME_DIST mengukur posisi relatif sebuah baris di dalam partisinya — pasangan yang lazim dipakai untuk analisis persentil gaji:

-- NTILE membagi baris menjadi n kelompok; PERCENT_RANK dan CUME_DIST mengukur posisi relatif

-- (a) kuartil global berdasarkan jumlah tertinggi, lalu hitung isi tiap kelompok
SELECT kuartil, count(*) AS baris
FROM (
    SELECT ntile(4) OVER (ORDER BY jumlah DESC, id) AS kuartil
    FROM penjualan_bulanan
) AS t
GROUP BY kuartil
ORDER BY kuartil;

-- (b) bagi bulan-bulan tiap produk menjadi 3 kelompok performa
SELECT produk,
       bulan,
       jumlah,
       ntile(3) OVER (PARTITION BY produk ORDER BY jumlah DESC, bulan) AS kelompok
FROM penjualan_bulanan
ORDER BY produk, jumlah DESC, bulan;

-- (c) PERCENT_RANK dan CUME_DIST untuk Kopi Gayo: posisi relatif di dalam partisi
SELECT bulan,
       jumlah,
       round(percent_rank() OVER (ORDER BY jumlah DESC)::numeric, 2) AS persen_rank,
       round(cume_dist()    OVER (ORDER BY jumlah DESC)::numeric, 2) AS cume_dist
FROM penjualan_bulanan
WHERE produk = 'Kopi Gayo'
ORDER BY jumlah DESC, bulan;

Output yang Dihasilkan:

text
 kuartil | baris 
---------+-------
       1 |     4
       2 |     4
       3 |     4
       4 |     4
(4 rows)

   produk   |   bulan    | jumlah | kelompok 
------------+------------+--------+----------
 Gula Pasir | 2026-06-01 |     75 |        1
 Gula Pasir | 2026-03-01 |     70 |        1
 Gula Pasir | 2026-04-01 |     70 |        2
 Gula Pasir | 2026-02-01 |     60 |        2
 Gula Pasir | 2026-05-01 |     55 |        3
 Kopi Gayo  | 2026-06-01 |    160 |        1
 Kopi Gayo  | 2026-04-01 |    150 |        1
 Kopi Gayo  | 2026-05-01 |    150 |        2
 Kopi Gayo  | 2026-02-01 |    135 |        2
 Kopi Gayo  | 2026-03-01 |    128 |        3
 Kopi Gayo  | 2026-01-01 |    120 |        3
 Teh Melati | 2026-06-01 |    130 |        1
 Teh Melati | 2026-04-01 |    110 |        1
 Teh Melati | 2026-02-01 |     95 |        2
 Teh Melati | 2026-01-01 |     80 |        2
 Teh Melati | 2026-03-01 |     80 |        3
(16 rows)

   bulan    | jumlah | persen_rank | cume_dist 
------------+--------+-------------+-----------
 2026-06-01 |    160 |        0.00 |      0.17
 2026-04-01 |    150 |        0.20 |      0.50
 2026-05-01 |    150 |        0.20 |      0.50
 2026-02-01 |    135 |        0.60 |      0.67
 2026-03-01 |    128 |        0.80 |      0.83
 2026-01-01 |    120 |        1.00 |      1.00
(6 rows)

Mari kita bedah ketiganya:

  1. Query (a): 16 baris dibagi NTILE(4) menjadi kuartil yang masing-masing berisi tepat 4 baris. Perhatikan strukturnya — NTILE dibungkus derived table lebih dulu karena menghitung count(*) per kelompok kuartil menuntut GROUP BY, dan GROUP BY tidak boleh memuat window function langsung.
  2. Query (b): dalam partisi 6 baris milik Kopi Gayo, NTILE(3) menghasilkan kelompok 2-2-2; pada partisi 5 baris milik Teh Melati dan Gula Pasir, kelompok pertama mendapat kelebihan satu anggota sehingga komposisinya 2-2-1.
  3. Query (c): untuk seri 150 milik Kopi Gayo, PERCENT_RANK memberi keduanya nilai 0.20 (posisi relatif dari depan; baris pertama selalu 0, terakhir selalu 1) dan CUME_DIST memberi 0.50 (proporsi baris yang nilainya kurang dari atau sama dengan baris tersebut, termasuk rekan serinya). Cast ::numeric diperlukan karena kedua fungsi mengembalikan double precision yang tidak bisa langsung dibulatkan dengan round(..., 2).

Ranking kini lengkap: posisi, tingkat, kelompok, hingga persentil. Satu kelas pertanyaan masih terbuka — yang tidak menanyakan “baris ini peringkat ke berapa” melainkan “berapa nilai baris sebelum dan sesudah saya?” Pertanyaan itu tentang pertumbuhan bulan-ke-bulan, dan jawabannya ada di LAG dan LEAD, chapter penutup rangkaian dasar window function ini.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
-- tabel latihan: penjualan bulanan per produk Toko Maju Jaya, semester I 2026
CREATE TABLE penjualan_bulanan (
    id      integer PRIMARY KEY,
    produk  text NOT NULL,
    bulan   date NOT NULL,
    kanal   text NOT NULL,
    jumlah  integer NOT NULL
);

INSERT INTO penjualan_bulanan (id, produk, bulan, kanal, jumlah) VALUES
    ( 1, 'Kopi Gayo',  DATE '2026-01-01', 'toko',   120),
    ( 2, 'Teh Melati', DATE '2026-01-01', 'toko',    80),
    ( 3, 'Kopi Gayo',  DATE '2026-02-01', 'online', 135),
    ( 4, 'Teh Melati', DATE '2026-02-01', 'toko',    95),
    ( 5, 'Gula Pasir', DATE '2026-02-01', 'toko',    60),
    ( 6, 'Gula Pasir', DATE '2026-03-01', 'toko',    70),
    ( 7, 'Kopi Gayo',  DATE '2026-03-01', 'toko',   128),
    ( 8, 'Teh Melati', DATE '2026-03-01', 'toko',    80),
    ( 9, 'Gula Pasir', DATE '2026-04-01', 'toko',    70),
    (10, 'Kopi Gayo',  DATE '2026-04-01', 'online', 150),
    (11, 'Teh Melati', DATE '2026-04-01', 'toko',   110),
    (12, 'Kopi Gayo',  DATE '2026-05-01', 'online', 150),
    (13, 'Gula Pasir', DATE '2026-05-01', 'toko',    55),
    (14, 'Kopi Gayo',  DATE '2026-06-01', 'online', 160),
    (15, 'Teh Melati', DATE '2026-06-01', 'online', 130),
    (16, 'Gula Pasir', DATE '2026-06-01', 'toko',    75);

SELECT id, produk, bulan, kanal, jumlah
FROM penjualan_bulanan
ORDER BY bulan, produk;

leaderboard-seri.sql

Unduh berkas
sql
-- leaderboard "dua bulan terbaik": rank mempertahankan seri, row_number memotongnya

-- (a) ROW_NUMBER: tepat 2 baris — seri 150 dipotong oleh tie-breaker bulan
SELECT bulan, produk, jumlah
FROM (
    SELECT bulan, produk, jumlah,
           row_number() OVER (ORDER BY jumlah DESC, bulan) AS rn
    FROM penjualan_bulanan
) AS t
WHERE rn <= 2
ORDER BY jumlah DESC, bulan;

-- (b) RANK: 3 baris — kedua bulan berjumlah 150 sama-sama berperingkat 2
SELECT bulan, produk, jumlah
FROM (
    SELECT bulan, produk, jumlah,
           rank() OVER (ORDER BY jumlah DESC) AS peringkat
    FROM penjualan_bulanan
) AS t
WHERE peringkat <= 2
ORDER BY jumlah DESC, bulan;

ntile-dan-persentil.sql

Unduh berkas
sql
-- NTILE membagi baris menjadi n kelompok; PERCENT_RANK dan CUME_DIST mengukur posisi relatif

-- (a) kuartil global berdasarkan jumlah tertinggi, lalu hitung isi tiap kelompok
SELECT kuartil, count(*) AS baris
FROM (
    SELECT ntile(4) OVER (ORDER BY jumlah DESC, id) AS kuartil
    FROM penjualan_bulanan
) AS t
GROUP BY kuartil
ORDER BY kuartil;

-- (b) bagi bulan-bulan tiap produk menjadi 3 kelompok performa
SELECT produk,
       bulan,
       jumlah,
       ntile(3) OVER (PARTITION BY produk ORDER BY jumlah DESC, bulan) AS kelompok
FROM penjualan_bulanan
ORDER BY produk, jumlah DESC, bulan;

-- (c) PERCENT_RANK dan CUME_DIST untuk Kopi Gayo: posisi relatif di dalam partisi
SELECT bulan,
       jumlah,
       round(percent_rank() OVER (ORDER BY jumlah DESC)::numeric, 2) AS persen_rank,
       round(cume_dist()    OVER (ORDER BY jumlah DESC)::numeric, 2) AS cume_dist
FROM penjualan_bulanan
WHERE produk = 'Kopi Gayo'
ORDER BY jumlah DESC, bulan;

rank-di-having-error.sql

Unduh berkas
sql
-- mencoba menyaring peringkat di HAVING: error yang diharapkan
SELECT produk,
       sum(jumlah) AS total
FROM penjualan_bulanan
GROUP BY produk
HAVING rank() OVER (ORDER BY sum(jumlah) DESC) <= 2
ORDER BY produk;

rank-tanpa-order-by.sql

Unduh berkas
sql
-- jebakan: RANK tanpa ORDER BY tidak error — semua baris dianggap setara
SELECT bulan,
       produk,
       jumlah,
       rank() OVER (PARTITION BY produk) AS peringkat
FROM penjualan_bulanan
ORDER BY bulan, produk;

rank-vs-dense-rank.sql

Unduh berkas
sql
-- RANK melompat setelah seri; DENSE_RANK tidak; ROW_NUMBER memaksa urutan unik

-- (a) satu jendela global: tiga fungsi berdampingan atas urutan jumlah tertinggi
SELECT bulan,
       produk,
       jumlah,
       row_number() OVER (ORDER BY jumlah DESC, id) AS nomor,
       rank()       OVER (ORDER BY jumlah DESC)     AS peringkat,
       dense_rank() OVER (ORDER BY jumlah DESC)     AS peringkat_padat
FROM penjualan_bulanan
ORDER BY jumlah DESC, id;

-- (b) leaderboard per produk: ranking di dalam partisi
SELECT bulan,
       produk,
       jumlah,
       rank()       OVER (PARTITION BY produk ORDER BY jumlah DESC) AS peringkat,
       dense_rank() OVER (PARTITION BY produk ORDER BY jumlah DESC) AS peringkat_padat
FROM penjualan_bulanan
ORDER BY produk, jumlah DESC, bulan;