Sepanjang section ini kita membongkar alatnya satu per satu: OVER dan PARTITION BY, fungsi ranking, LAG/LEAD, running total, rata-rata bergerak, lalu spesifikasi frame. Analis data tidak bekerja dengan alat terpisah-pisah seperti itu — pertanyaan bisnis datang utuh (“produk mana yang sedang menggeser pesaingnya?”, “apakah bulan ini masih sehat dibanding trennya?”) dan menjawabnya membutuhkan beberapa alat dalam satu query. Chapter penutup ini merangkai semuanya menjadi lima query analisis yang siap dipakai pada laporan sungguhan: pertumbuhan tahunan antar-produk, tiga produk teratas per bulan, pangsa pasar dalam satu bulan, pangsa kumulatif gaya Pareto, dan indikator tren tiga bulanan. Semua query dijalankan pada satu dataset yang dirancang menyerupai gudang data sederhana: arsip tahunan untuk tahun-tahun lama dan rincian bulanan untuk tahun berjalan.

Dataset: Arsip Tahunan dan Rincian Bulanan #

Tabel penjualan_tahunan menyimpan total per produk untuk 2024 dan 2025 — angka arsip yang biasanya tinggal di laporan tahunan. Tabel penjualan_bulanan menyimpan rincian bulanan 2026 untuk empat produk — termasuk Biskuit, produk kecil yang belum pernah muncul di chapter-chapter sebelumnya:

sql buat-tabel.sql
-- dataset analisis: arsip tahunan 2024-2025 + rincian bulanan 2026 (4 produk)
CREATE TABLE penjualan_tahunan (
    tahun       integer NOT NULL,
    produk      text NOT NULL,
    pendapatan  numeric(14,2) NOT NULL,
    PRIMARY KEY (tahun, produk)
);

CREATE TABLE penjualan_bulanan (
    id          integer PRIMARY KEY,
    produk      text NOT NULL,
    bulan       date NOT NULL,
    pendapatan  numeric(12,2) NOT NULL
);

-- arsip: total tahunan per produk untuk 2024 dan 2025
INSERT INTO penjualan_tahunan (tahun, produk, pendapatan) VALUES
    (2024, 'Biskuit',    19000000.00),
    (2024, 'Gula Pasir', 42000000.00),
    (2024, 'Kopi Gayo',  98000000.00),
    (2024, 'Teh Melati', 47000000.00),
    (2025, 'Biskuit',    17100000.00),
    (2025, 'Gula Pasir', 44100000.00),
    (2025, 'Kopi Gayo', 113000000.00),
    (2025, 'Teh Melati', 51700000.00);

-- rincian bulanan sepanjang 2026
INSERT INTO penjualan_bulanan (id, produk, bulan, pendapatan) VALUES
    ( 1, 'Biskuit',    DATE '2026-01-01',  1200000.00),
    ( 2, 'Gula Pasir', DATE '2026-01-01',  3200000.00),
    ( 3, 'Kopi Gayo',  DATE '2026-01-01',  9200000.00),
    ( 4, 'Teh Melati', DATE '2026-01-01',  4100000.00),
    ( 5, 'Biskuit',    DATE '2026-02-01',  1300000.00),
    ( 6, 'Gula Pasir', DATE '2026-02-01',  3600000.00),
    ( 7, 'Kopi Gayo',  DATE '2026-02-01',  9800000.00),
    ( 8, 'Teh Melati', DATE '2026-02-01',  4300000.00),
    ( 9, 'Biskuit',    DATE '2026-03-01',  1100000.00),
    (10, 'Gula Pasir', DATE '2026-03-01',  3100000.00),
    (11, 'Kopi Gayo',  DATE '2026-03-01',  8900000.00),
    (12, 'Teh Melati', DATE '2026-03-01',  4600000.00),
    (13, 'Biskuit',    DATE '2026-04-01',  1400000.00),
    (14, 'Gula Pasir', DATE '2026-04-01',  3800000.00),
    (15, 'Kopi Gayo',  DATE '2026-04-01', 10200000.00),
    (16, 'Teh Melati', DATE '2026-04-01',  4400000.00),
    (17, 'Biskuit',    DATE '2026-05-01',  1200000.00),
    (18, 'Gula Pasir', DATE '2026-05-01',  3500000.00),
    (19, 'Kopi Gayo',  DATE '2026-05-01', 10800000.00),
    (20, 'Teh Melati', DATE '2026-05-01',  4800000.00),
    (21, 'Biskuit',    DATE '2026-06-01',  1300000.00),
    (22, 'Gula Pasir', DATE '2026-06-01',  3900000.00),
    (23, 'Kopi Gayo',  DATE '2026-06-01', 11500000.00),
    (24, 'Teh Melati', DATE '2026-06-01',  5100000.00),
    (25, 'Biskuit',    DATE '2026-07-01',  1200000.00),
    (26, 'Gula Pasir', DATE '2026-07-01',  3700000.00),
    (27, 'Kopi Gayo',  DATE '2026-07-01', 11100000.00),
    (28, 'Teh Melati', DATE '2026-07-01',  5000000.00),
    (29, 'Biskuit',    DATE '2026-08-01',  1400000.00),
    (30, 'Gula Pasir', DATE '2026-08-01',  4000000.00),
    (31, 'Kopi Gayo',  DATE '2026-08-01', 11900000.00),
    (32, 'Teh Melati', DATE '2026-08-01',  5300000.00),
    (33, 'Biskuit',    DATE '2026-09-01',  1300000.00),
    (34, 'Gula Pasir', DATE '2026-09-01',  3800000.00),
    (35, 'Kopi Gayo',  DATE '2026-09-01', 10600000.00),
    (36, 'Teh Melati', DATE '2026-09-01',  4700000.00),
    (37, 'Biskuit',    DATE '2026-10-01',  1500000.00),
    (38, 'Gula Pasir', DATE '2026-10-01',  4100000.00),
    (39, 'Kopi Gayo',  DATE '2026-10-01', 10900000.00),
    (40, 'Teh Melati', DATE '2026-10-01',  4900000.00),
    (41, 'Biskuit',    DATE '2026-11-01',  1300000.00),
    (42, 'Gula Pasir', DATE '2026-11-01',  4700000.00),
    (43, 'Kopi Gayo',  DATE '2026-11-01', 11400000.00),
    (44, 'Teh Melati', DATE '2026-11-01',  4500000.00),
    (45, 'Biskuit',    DATE '2026-12-01',  1700000.00),
    (46, 'Gula Pasir', DATE '2026-12-01',  4200000.00),
    (47, 'Kopi Gayo',  DATE '2026-12-01', 12400000.00),
    (48, 'Teh Melati', DATE '2026-12-01',  4900000.00);

-- dua tabel dalam satu pandangan: arsip tahunan dan total bulanan 2026
SELECT tahun, produk, pendapatan
FROM penjualan_tahunan
ORDER BY tahun, produk;

SELECT bulan, sum(pendapatan) AS total_bulan
FROM penjualan_bulanan
GROUP BY bulan
ORDER BY bulan;

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
INSERT 0 8
INSERT 0 48
 tahun |   produk   |  pendapatan  
-------+------------+--------------
  2024 | Biskuit    |  19000000.00
  2024 | Gula Pasir |  42000000.00
  2024 | Kopi Gayo  |  98000000.00
  2024 | Teh Melati |  47000000.00
  2025 | Biskuit    |  17100000.00
  2025 | Gula Pasir |  44100000.00
  2025 | Kopi Gayo  | 113000000.00
  2025 | Teh Melati |  51700000.00
(8 rows)

   bulan    | total_bulan 
------------+-------------
 2026-01-01 | 17700000.00
 2026-02-01 | 19000000.00
 2026-03-01 | 17700000.00
 2026-04-01 | 19800000.00
 2026-05-01 | 20300000.00
 2026-06-01 | 21800000.00
 2026-07-01 | 21000000.00
 2026-08-01 | 22600000.00
 2026-09-01 | 20400000.00
 2026-10-01 | 21400000.00
 2026-11-01 | 21900000.00
 2026-12-01 | 23200000.00
(12 rows)

Pertumbuhan Tahunan dengan LAG #

Pertanyaan pertama: bagaimana pertumbuhan tiap produk dari 2024 ke 2025 ke 2026? Datanya terbelah dua tabel, jadi langkahnya menyatukan arsip dengan hasil agregasi 2026 lewat UNION ALL, menempelkan nilai tahun lalu dengan LAG per produk, lalu menghitung persentase pertumbuhannya:

-- pertumbuhan tahunan per produk: LAG pada total tahunan gabungan arsip + 2026

WITH tahunan AS (
    SELECT tahun, produk, pendapatan
    FROM penjualan_tahunan
    UNION ALL
    SELECT date_part('year', bulan)::integer AS tahun,
           produk,
           sum(pendapatan) AS pendapatan
    FROM penjualan_bulanan
    GROUP BY date_part('year', bulan), produk
),
dengan_lalu AS (
    SELECT produk,
           tahun,
           pendapatan,
           lag(pendapatan) OVER (PARTITION BY produk ORDER BY tahun) AS pendapatan_lalu
    FROM tahunan
)
SELECT produk,
       tahun,
       pendapatan,
       pendapatan_lalu,
       round((pendapatan - pendapatan_lalu) / pendapatan_lalu * 100, 1) AS pertumbuhan_pct
FROM dengan_lalu
ORDER BY produk, tahun;

Output yang Dihasilkan:

text
   produk   | tahun |  pendapatan  | pendapatan_lalu | pertumbuhan_pct 
------------+-------+--------------+-----------------+-----------------
 Biskuit    |  2024 |  19000000.00 |                 |                
 Biskuit    |  2025 |  17100000.00 |     19000000.00 |           -10.0
 Biskuit    |  2026 |  15900000.00 |     17100000.00 |            -7.0
 Gula Pasir |  2024 |  42000000.00 |                 |                
 Gula Pasir |  2025 |  44100000.00 |     42000000.00 |             5.0
 Gula Pasir |  2026 |  45600000.00 |     44100000.00 |             3.4
 Kopi Gayo  |  2024 |  98000000.00 |                 |                
 Kopi Gayo  |  2025 | 113000000.00 |     98000000.00 |            15.3
 Kopi Gayo  |  2026 | 128700000.00 |    113000000.00 |            13.9
 Teh Melati |  2024 |  47000000.00 |                 |                
 Teh Melati |  2025 |  51700000.00 |     47000000.00 |            10.0
 Teh Melati |  2026 |  56600000.00 |     51700000.00 |             9.5
(12 rows)

Mari kita bedah alurnya lapis demi lapis:

  1. CTE tahunan — delapan baris arsip digabung dengan agregasi GROUP BY dari rincian bulanan; date_part('year', bulan)::integer menghasilkan 2026 sehingga kedua sumber berbagi struktur kolom yang sama.
  2. CTE dengan_lalu — lag(pendapatan) OVER (PARTITION BY produk ORDER BY tahun) mengambil total tahun sebelumnya untuk produk yang sama; baris pertama tiap partisi tidak punya tahun sebelumnya sehingga kolomnya NULL.
  3. SELECT terluar — persentase pertumbuhan dihitung dari kedua kolom yang sudah tersedia; pembagian oleh NULL menghasilkan NULL, sehingga baris 2024 tampil kosong secara alami tanpa perlu penyaringan khusus.

Cara membaca hasilnya: Kopi Gayo tumbuh 15,3% lalu 13,9% — mesin utama toko yang tetap kencang. Biskuit justru menyusut -10,0% dan -7,0% dua tahun berturut-turut, kandidat pertama untuk evaluasi produk. Pola query ini adalah dasar analisis year-over-year: bandingkan angka sekarang dengan angka periode setara tahun sebelumnya, satu baris per produk-tahun.

Tiga Produk Teratas per Bulan #

Pertanyaan kedua klasik di laporan ritel: tiga produk mana yang menyumbang pendapatan terbesar di setiap bulan? Ranking per bulan dihitung row_number() OVER (PARTITION BY bulan ...), dan karena hasil window function tidak boleh disaring di WHERE, ranking disaring satu tingkat di luar — lewat derived table:

-- tiga produk teratas tiap bulan: ROW_NUMBER di derived table, saring di luar

SELECT bulan, produk, pendapatan
FROM (
    SELECT bulan,
           produk,
           pendapatan,
           row_number() OVER (PARTITION BY bulan
                              ORDER BY pendapatan DESC, produk) AS peringkat
    FROM penjualan_bulanan
    WHERE bulan >= DATE '2026-10-01'
) AS peringkat_bulanan
WHERE peringkat <= 3
ORDER BY bulan, peringkat;

Output yang Dihasilkan:

text
   bulan    |   produk   | pendapatan  
------------+------------+-------------
 2026-10-01 | Kopi Gayo  | 10900000.00
 2026-10-01 | Teh Melati |  4900000.00
 2026-10-01 | Gula Pasir |  4100000.00
 2026-11-01 | Kopi Gayo  | 11400000.00
 2026-11-01 | Gula Pasir |  4700000.00
 2026-11-01 | Teh Melati |  4500000.00
 2026-12-01 | Kopi Gayo  | 12400000.00
 2026-12-01 | Teh Melati |  4900000.00
 2026-12-01 | Gula Pasir |  4200000.00
(9 rows)

Kolom produk di dalam ORDER BY pendapatan DESC, produk berperan sebagai pemecah urut — kalau dua produk seri pada suatu bulan, urutannya tetap deterministik. Output di atas sengaja dibatasi kuartal keempat, dan di situlah ceritanya: pada Oktober dan Desember posisi dua dan tiga diisi Teh Melati dan Gula Pasir, tetapi pada November urutannya terbalik — Gula Pasir melesat ke posisi dua (4.700.000) dan menggeser Teh Melati ke posisi tiga (4.500.000). Perubahan peringkat bulanan seperti ini justru hanya terlihat bila ranking dihitung per bulan; satu ranking untuk setahun penuh akan menyembunyikan momen ketika pesaing saling menggeser.

Pangsa Pasar dalam Satu Bulan #

Pertanyaan ketiga memotivasi kolom yang sering diminta manajer: berapa persen kontribusi tiap produk dalam bulannya sendiri? Pembagiannya sederhana — pendapatan tiap baris dibagi total bulan itu, dan total per bulan didapat dari sum(...) OVER (PARTITION BY bulan):

-- pangsa pasar tiap produk di dalam bulannya: baris dibagi total bulan itu

SELECT bulan,
       produk,
       pendapatan,
       round(pendapatan / sum(pendapatan) OVER (PARTITION BY bulan) * 100, 1) AS pangsa_bulan_pct
FROM penjualan_bulanan
WHERE bulan < DATE '2026-04-01'
ORDER BY bulan, pendapatan DESC;

Output yang Dihasilkan:

text
   bulan    |   produk   | pendapatan | pangsa_bulan_pct 
------------+------------+------------+------------------
 2026-01-01 | Kopi Gayo  | 9200000.00 |             52.0
 2026-01-01 | Teh Melati | 4100000.00 |             23.2
 2026-01-01 | Gula Pasir | 3200000.00 |             18.1
 2026-01-01 | Biskuit    | 1200000.00 |              6.8
 2026-02-01 | Kopi Gayo  | 9800000.00 |             51.6
 2026-02-01 | Teh Melati | 4300000.00 |             22.6
 2026-02-01 | Gula Pasir | 3600000.00 |             18.9
 2026-02-01 | Biskuit    | 1300000.00 |              6.8
 2026-03-01 | Kopi Gayo  | 8900000.00 |             50.3
 2026-03-01 | Teh Melati | 4600000.00 |             26.0
 2026-03-01 | Gula Pasir | 3100000.00 |             17.5
 2026-03-01 | Biskuit    | 1100000.00 |              6.2
(12 rows)

Pada kuartal pertama, Kopi Gayo menguasai separuh lebih dari setiap bulan — 52,0% di Januari, 51,6% di Februari, 50,3% di Maret — sementara Biskuit stabil di kisaran 6–7%. Perhatikan pembaginya: OVER (PARTITION BY bulan) mengganti total keseluruhan dengan total kelompok, persis pola yang dipakai untuk “persen dari subtotal”. Pola yang sama dengan pembagi berbeda (misalnya PARTITION BY bulan, kanal) bisa dipakai untuk pangsa yang lebih granular.

Pangsa Kumulatif: Membaca Pola Pareto #

Pertanyaan keempat naik satu tingkat ke level tahun: bagaimana pendapatan 2026 terbagi antar-produk, dan berapa cepat persentasenya menumpuk? Mengurutkan produk dari terbesar lalu menghitung sum(...) OVER (ORDER BY pendapatan DESC) menghasilkan pangsa kumulatif — bahan analisis Pareto yang terkenal dengan patokan 80/20:

-- pangsa total dan pangsa kumulatif per produk 2026: analisis Pareto

WITH tahunan AS (
    SELECT produk,
           sum(pendapatan) AS pendapatan
    FROM penjualan_bulanan
    GROUP BY produk
)
SELECT produk,
       pendapatan,
       round(pendapatan / sum(pendapatan) OVER () * 100, 1) AS pangsa_pct,
       round(sum(pendapatan) OVER (ORDER BY pendapatan DESC) / sum(pendapatan) OVER () * 100, 1) AS pangsa_kumulatif_pct
FROM tahunan
ORDER BY pendapatan DESC;

Output yang Dihasilkan:

text
   produk   |  pendapatan  | pangsa_pct | pangsa_kumulatif_pct 
------------+--------------+------------+----------------------
 Kopi Gayo  | 128700000.00 |       52.1 |                 52.1
 Teh Melati |  56600000.00 |       22.9 |                 75.1
 Gula Pasir |  45600000.00 |       18.5 |                 93.6
 Biskuit    |  15900000.00 |        6.4 |                100.0
(4 rows)

Angkanya nyaris tepat delapan puluh per dua puluh: dua produk teratas menyumbang 75,1% pendapatan 2026, dan tiga produk teratas 93,6%. Dua kolom jendela yang berbeda peran di query ini: sum(pendapatan) OVER () adalah total seluruh partisi (246.800.000) yang menjadi pembagi tetap, sedangkan sum(pendapatan) OVER (ORDER BY pendapatan DESC) berjalan menumpuk mengikuti urutan dari besar ke kecil. Nilai pendapatan tiap produk berbeda sehingga tidak ada peer — frame default RANGE berperilaku sama dengan ROWS dan hasilnya deterministik; bila dua produk bisa seri, tambahkan pemecah urut atau ganti ke ROWS eksplisit.

Indikator Tren Tiga Bulanan #

Query penutup menggabungkan tiga alat dalam satu lapisan: LAG untuk perubahan bulan-ke-bulan, rata-rata bergerak tiga bulan untuk tren yang terhaluskan, dan CASE yang menerjemahkan keduanya menjadi label yang bisa langsung dibaca manajemen:

-- indikator tren: total bulanan, perubahan bulan-ke-bulan, rata-rata 3 bulan, posisi tren

WITH bulanan AS (
    SELECT bulan,
           sum(pendapatan) AS total_bulan
    FROM penjualan_bulanan
    GROUP BY bulan
),
bergerak AS (
    SELECT bulan,
           total_bulan,
           lag(total_bulan) OVER w AS bulan_lalu,
           round(avg(total_bulan) OVER (w ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS rata3_bulan
    FROM bulanan
    WINDOW w AS (ORDER BY bulan)
)
SELECT bulan,
       total_bulan,
       bulan_lalu,
       round((total_bulan / bulan_lalu - 1) * 100, 1) AS perubahan_pct,
       rata3_bulan,
       CASE WHEN total_bulan > rata3_bulan THEN 'di atas tren'
            WHEN total_bulan < rata3_bulan THEN 'di bawah tren'
            ELSE 'sejajar tren'
       END AS posisi_tren
FROM bergerak
ORDER BY bulan;

Output yang Dihasilkan:

text
   bulan    | total_bulan | bulan_lalu  | perubahan_pct | rata3_bulan |  posisi_tren  
------------+-------------+-------------+---------------+-------------+---------------
 2026-01-01 | 17700000.00 |             |               | 17700000.00 | sejajar tren
 2026-02-01 | 19000000.00 | 17700000.00 |           7.3 | 18350000.00 | di atas tren
 2026-03-01 | 17700000.00 | 19000000.00 |          -6.8 | 18133333.33 | di bawah tren
 2026-04-01 | 19800000.00 | 17700000.00 |          11.9 | 18833333.33 | di atas tren
 2026-05-01 | 20300000.00 | 19800000.00 |           2.5 | 19266666.67 | di atas tren
 2026-06-01 | 21800000.00 | 20300000.00 |           7.4 | 20633333.33 | di atas tren
 2026-07-01 | 21000000.00 | 21800000.00 |          -3.7 | 21033333.33 | di bawah tren
 2026-08-01 | 22600000.00 | 21000000.00 |           7.6 | 21800000.00 | di atas tren
 2026-09-01 | 20400000.00 | 22600000.00 |          -9.7 | 21333333.33 | di bawah tren
 2026-10-01 | 21400000.00 | 20400000.00 |           4.9 | 21466666.67 | di bawah tren
 2026-11-01 | 21900000.00 | 21400000.00 |           2.3 | 21233333.33 | di atas tren
 2026-12-01 | 23200000.00 | 21900000.00 |           5.9 | 22166666.67 | di atas tren
(12 rows)

Klausa WINDOW w AS (ORDER BY bulan) memberi nama pada definisi jendela sehingga lag(total_bulan) OVER w dan avg(total_bulan) OVER (w ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) berbagi urutan yang sama — frame tambahan ditulis di depan nama jendelanya. CTE bergerak menghitung dua kolom jendela lebih dulu supaya SELECT terluar cukup membandingkan total_bulan dengan rata3_bulan di dalam CASE.

Dua indikatornya mengukur hal yang berbeda dan Oktober adalah contohnya: perubahan_pct positif 4,9% (naik dibanding September), tetapi posisi_tren tetap di bawah tren karena total 21.400.000 masih sedikit di bawah rata-rata tiga bulannya sendiri (21.466.666,67). Kenaikan satu bulan belum tentu pemulihan — barulah November dan Desember yang menutup tahun dengan posisi di atas tren dua bulan berturut-turut, sinyal pemulihan yang lebih kuat.

Peta Ringkas: Fungsi dan Frame untuk Pertanyaan Bisnis #

Lima query di atas hanyalah kombinasi dari sejumlah kecil pola. Tabel berikut merangkum jaringannya — pertanyaan bisnis di kiri, pola window function di kanan:

Pertanyaan bisnisPola window function
Berapa nilainya dibanding periode sebelumnya?lag(x) OVER (PARTITION BY kelompok ORDER BY waktu)
Siapa N teratas dalam tiap kelompok?row_number() OVER (PARTITION BY kelompok ORDER BY x DESC) + saring di derived table
Berapa pangsa tiap baris dalam kelompoknya?x / sum(x) OVER (PARTITION BY kelompok)
Berapa pangsa tiap baris dari keseluruhan?x / sum(x) OVER ()
Berapa akumulasi berjalan?sum(x) OVER (ORDER BY waktu)
Berapa pangsa kumulatif terurut (Pareto)?sum(x) OVER (ORDER BY x DESC) / sum(x) OVER ()
Bagaimana tren halus beberapa periode?avg(x) OVER (ORDER BY waktu ROWS BETWEEN n PRECEDING AND CURRENT ROW)
Apakah nilai sekarang menyimpang dari tren?rata-rata bergerak + CASE di tingkat query luar

Dengan ini section window function tuntas: satu jenis fungsi yang sama — diberi OVER — kini menjadi sumber kolom perbandingan, peringkat, pangsa, akumulasi, dan tren tanpa pernah mengorbankan baris rinciannya. Kedepannya data yang kita olah tidak selalu rapi berbentuk baris-kolom; layanan modern sering menyimpan atribut yang berubah-ubah bentuknya sebagai dokumen JSON. PostgreSQL menjawab kebutuhan itu dengan tipe jsonb beserta operator dan indeksnya, dan itulah pembuka section berikutnya: JSON dan Data Semi-Terstruktur .

Referensi #

Berkas Sumber

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

buat-tabel.sql

Unduh berkas
sql
-- dataset analisis: arsip tahunan 2024-2025 + rincian bulanan 2026 (4 produk)
CREATE TABLE penjualan_tahunan (
    tahun       integer NOT NULL,
    produk      text NOT NULL,
    pendapatan  numeric(14,2) NOT NULL,
    PRIMARY KEY (tahun, produk)
);

CREATE TABLE penjualan_bulanan (
    id          integer PRIMARY KEY,
    produk      text NOT NULL,
    bulan       date NOT NULL,
    pendapatan  numeric(12,2) NOT NULL
);

-- arsip: total tahunan per produk untuk 2024 dan 2025
INSERT INTO penjualan_tahunan (tahun, produk, pendapatan) VALUES
    (2024, 'Biskuit',    19000000.00),
    (2024, 'Gula Pasir', 42000000.00),
    (2024, 'Kopi Gayo',  98000000.00),
    (2024, 'Teh Melati', 47000000.00),
    (2025, 'Biskuit',    17100000.00),
    (2025, 'Gula Pasir', 44100000.00),
    (2025, 'Kopi Gayo', 113000000.00),
    (2025, 'Teh Melati', 51700000.00);

-- rincian bulanan sepanjang 2026
INSERT INTO penjualan_bulanan (id, produk, bulan, pendapatan) VALUES
    ( 1, 'Biskuit',    DATE '2026-01-01',  1200000.00),
    ( 2, 'Gula Pasir', DATE '2026-01-01',  3200000.00),
    ( 3, 'Kopi Gayo',  DATE '2026-01-01',  9200000.00),
    ( 4, 'Teh Melati', DATE '2026-01-01',  4100000.00),
    ( 5, 'Biskuit',    DATE '2026-02-01',  1300000.00),
    ( 6, 'Gula Pasir', DATE '2026-02-01',  3600000.00),
    ( 7, 'Kopi Gayo',  DATE '2026-02-01',  9800000.00),
    ( 8, 'Teh Melati', DATE '2026-02-01',  4300000.00),
    ( 9, 'Biskuit',    DATE '2026-03-01',  1100000.00),
    (10, 'Gula Pasir', DATE '2026-03-01',  3100000.00),
    (11, 'Kopi Gayo',  DATE '2026-03-01',  8900000.00),
    (12, 'Teh Melati', DATE '2026-03-01',  4600000.00),
    (13, 'Biskuit',    DATE '2026-04-01',  1400000.00),
    (14, 'Gula Pasir', DATE '2026-04-01',  3800000.00),
    (15, 'Kopi Gayo',  DATE '2026-04-01', 10200000.00),
    (16, 'Teh Melati', DATE '2026-04-01',  4400000.00),
    (17, 'Biskuit',    DATE '2026-05-01',  1200000.00),
    (18, 'Gula Pasir', DATE '2026-05-01',  3500000.00),
    (19, 'Kopi Gayo',  DATE '2026-05-01', 10800000.00),
    (20, 'Teh Melati', DATE '2026-05-01',  4800000.00),
    (21, 'Biskuit',    DATE '2026-06-01',  1300000.00),
    (22, 'Gula Pasir', DATE '2026-06-01',  3900000.00),
    (23, 'Kopi Gayo',  DATE '2026-06-01', 11500000.00),
    (24, 'Teh Melati', DATE '2026-06-01',  5100000.00),
    (25, 'Biskuit',    DATE '2026-07-01',  1200000.00),
    (26, 'Gula Pasir', DATE '2026-07-01',  3700000.00),
    (27, 'Kopi Gayo',  DATE '2026-07-01', 11100000.00),
    (28, 'Teh Melati', DATE '2026-07-01',  5000000.00),
    (29, 'Biskuit',    DATE '2026-08-01',  1400000.00),
    (30, 'Gula Pasir', DATE '2026-08-01',  4000000.00),
    (31, 'Kopi Gayo',  DATE '2026-08-01', 11900000.00),
    (32, 'Teh Melati', DATE '2026-08-01',  5300000.00),
    (33, 'Biskuit',    DATE '2026-09-01',  1300000.00),
    (34, 'Gula Pasir', DATE '2026-09-01',  3800000.00),
    (35, 'Kopi Gayo',  DATE '2026-09-01', 10600000.00),
    (36, 'Teh Melati', DATE '2026-09-01',  4700000.00),
    (37, 'Biskuit',    DATE '2026-10-01',  1500000.00),
    (38, 'Gula Pasir', DATE '2026-10-01',  4100000.00),
    (39, 'Kopi Gayo',  DATE '2026-10-01', 10900000.00),
    (40, 'Teh Melati', DATE '2026-10-01',  4900000.00),
    (41, 'Biskuit',    DATE '2026-11-01',  1300000.00),
    (42, 'Gula Pasir', DATE '2026-11-01',  4700000.00),
    (43, 'Kopi Gayo',  DATE '2026-11-01', 11400000.00),
    (44, 'Teh Melati', DATE '2026-11-01',  4500000.00),
    (45, 'Biskuit',    DATE '2026-12-01',  1700000.00),
    (46, 'Gula Pasir', DATE '2026-12-01',  4200000.00),
    (47, 'Kopi Gayo',  DATE '2026-12-01', 12400000.00),
    (48, 'Teh Melati', DATE '2026-12-01',  4900000.00);

-- dua tabel dalam satu pandangan: arsip tahunan dan total bulanan 2026
SELECT tahun, produk, pendapatan
FROM penjualan_tahunan
ORDER BY tahun, produk;

SELECT bulan, sum(pendapatan) AS total_bulan
FROM penjualan_bulanan
GROUP BY bulan
ORDER BY bulan;

pangsa-bulanan.sql

Unduh berkas
sql
-- pangsa pasar tiap produk di dalam bulannya: baris dibagi total bulan itu

SELECT bulan,
       produk,
       pendapatan,
       round(pendapatan / sum(pendapatan) OVER (PARTITION BY bulan) * 100, 1) AS pangsa_bulan_pct
FROM penjualan_bulanan
WHERE bulan < DATE '2026-04-01'
ORDER BY bulan, pendapatan DESC;

pangsa-kumulatif.sql

Unduh berkas
sql
-- pangsa total dan pangsa kumulatif per produk 2026: analisis Pareto

WITH tahunan AS (
    SELECT produk,
           sum(pendapatan) AS pendapatan
    FROM penjualan_bulanan
    GROUP BY produk
)
SELECT produk,
       pendapatan,
       round(pendapatan / sum(pendapatan) OVER () * 100, 1) AS pangsa_pct,
       round(sum(pendapatan) OVER (ORDER BY pendapatan DESC) / sum(pendapatan) OVER () * 100, 1) AS pangsa_kumulatif_pct
FROM tahunan
ORDER BY pendapatan DESC;

pertumbuhan-tahunan.sql

Unduh berkas
sql
-- pertumbuhan tahunan per produk: LAG pada total tahunan gabungan arsip + 2026

WITH tahunan AS (
    SELECT tahun, produk, pendapatan
    FROM penjualan_tahunan
    UNION ALL
    SELECT date_part('year', bulan)::integer AS tahun,
           produk,
           sum(pendapatan) AS pendapatan
    FROM penjualan_bulanan
    GROUP BY date_part('year', bulan), produk
),
dengan_lalu AS (
    SELECT produk,
           tahun,
           pendapatan,
           lag(pendapatan) OVER (PARTITION BY produk ORDER BY tahun) AS pendapatan_lalu
    FROM tahunan
)
SELECT produk,
       tahun,
       pendapatan,
       pendapatan_lalu,
       round((pendapatan - pendapatan_lalu) / pendapatan_lalu * 100, 1) AS pertumbuhan_pct
FROM dengan_lalu
ORDER BY produk, tahun;

top-tiga-produk.sql

Unduh berkas
sql
-- tiga produk teratas tiap bulan: ROW_NUMBER di derived table, saring di luar

SELECT bulan, produk, pendapatan
FROM (
    SELECT bulan,
           produk,
           pendapatan,
           row_number() OVER (PARTITION BY bulan
                              ORDER BY pendapatan DESC, produk) AS peringkat
    FROM penjualan_bulanan
    WHERE bulan >= DATE '2026-10-01'
) AS peringkat_bulanan
WHERE peringkat <= 3
ORDER BY bulan, peringkat;

tren-tiga-bulanan.sql

Unduh berkas
sql
-- indikator tren: total bulanan, perubahan bulan-ke-bulan, rata-rata 3 bulan, posisi tren

WITH bulanan AS (
    SELECT bulan,
           sum(pendapatan) AS total_bulan
    FROM penjualan_bulanan
    GROUP BY bulan
),
bergerak AS (
    SELECT bulan,
           total_bulan,
           lag(total_bulan) OVER w AS bulan_lalu,
           round(avg(total_bulan) OVER (w ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS rata3_bulan
    FROM bulanan
    WINDOW w AS (ORDER BY bulan)
)
SELECT bulan,
       total_bulan,
       bulan_lalu,
       round((total_bulan / bulan_lalu - 1) * 100, 1) AS perubahan_pct,
       rata3_bulan,
       CASE WHEN total_bulan > rata3_bulan THEN 'di atas tren'
            WHEN total_bulan < rata3_bulan THEN 'di bawah tren'
            ELSE 'sejajar tren'
       END AS posisi_tren
FROM bergerak
ORDER BY bulan;