Day 33: SQL Untuk Modeling

Learning In HomeOriginal Date: 14 Februari 2009

Hari Valentine akhirnya tiba juga. Nuansa cinta terlihat dimana-mana, gadis manis tetangga yang sering menatapku (?) juga terlihat tambah menarik. Komunikasi tanpa kata-kata (dan tanpa protocol tentunya) diantara kami memang sulit dipahami, tapi itu bukanlah topik tulisan hari ini. Pagi ini, aku akan kembali mempelajari menggunakan SQL. Kali ini dalam kaitannya dengan MODEL yang memungkinkan manipulasi isi tabel sampai ke tahap cell. Sebagai contoh, misalnya, aku membuat sebuah tabel sederhana yang mirip lembaran kerja di Excel:


CREATE TABLE tbl_latihan_model (
  A VARCHAR2(10),
  B VARCHAR2(10),
  C VARCHAR2(10),
  D NUMBER(10)
);

INSERT INTO tbl_latihan_model 
  VALUES ('A1', 'B1', 'C1', 10);
INSERT INTO tbl_latihan_model 
  VALUES ('A2', 'B2', 'C2', 20);
INSERT INTO tbl_latihan_model 
  VALUES ('A3', 'B3', 'C3', 30);
INSERT INTO tbl_latihan_model 
  VALUES ('A4', 'B4', 'C4', 40);
INSERT INTO tbl_latihan_model 
  VALUES ('A5', 'B5', 'C5', 50);
INSERT INTO tbl_latihan_model 
  VALUES ('A6', 'B6', 'C6', 60);

Lalu, aku ingin mengubah nilai kolom D sesuai keinginan dengan MODEL:


SELECT
*
FROM
tbl_latihan_model
MODEL
DIMENSION BY (A, B, C)
MEASURES (D)
RULES UPDATE (
D[A='A1', B='B1', C='C1'] = 999,
D[A='A4', B='B4', C='C4'] = D[A='A1', B='B1', C='C1'] + 1
)
ORDER BY A
;

Hasil akhirnya akan mirip seperti ini:


-------------------
 A   B   C   D
-------------------
 A1  B1  C1   999
 A2  B2  C2    20
 A3  B3  C3    30
 A4  B4  C4  1000
 A5  B5  C5    50
 A6  B6  C6    60
-------------------

Aku telah mengubah nilai kolom D di baris 1 menjadi 999. Bukan hanya itu, aku juga mengubah nilai kolom D di baris 4 menjadi nilai kolom D di baris pertama ditambah dengan 1. Semuanya itu dalam sekali query. Dan bahkan bisa lebih lagi, dengan menambah statement baru di bagian klausa RULES.

Contoh yang lebih berguna, misalnya, aku akan menghitung dan menampilkan diskon untuk produk berdasarkan rule yang telah aku tentukan:


SELECT 
  tanggal,
  kategori_id,
  sum_jumlah,
  diskon
FROM 
  (SELECT
      tanggal, 
      brg.id AS brg_id, 
      SUM(pb.jumlah) AS sum_jumlah
   FROM
      tbl_pembelian pb, tbl_barang brg
   WHERE
      pb.barang_id = brg.id
   GROUP BY
      tanggal, brg.id
  ) pb,
  tbl_kategori kat, 
  tbl_barang brg
WHERE
  pb.brg_id = brg.id AND
  brg.kategori = kat.id 
MODEL  
  PARTITION BY (tanggal)
  DIMENSION BY (
    brg_id, 
    kat.id as kategori_id, 
    sum_jumlah)
  MEASURES (0 as diskon)
  RULES UPDATE (
     diskon[any, 
            any, 
            sum_jumlah > 10] = 0.01,
     diskon[any, 
            kategori_id = 3, 
            any] = 
      diskon[cv(brg_id), 
             cv(kategori_id), 
             cv(sum_jumlah)] +
        cv(sum_jumlah) * 0.1
  )
ORDER BY
  tanggal
;  

Pada contoh tersebut, aku memberikan dua rule, yaitu diskon sebesar 0.01 untuk jika atribut sum_jumlah lebih dari 10. Selain itu, jika kategori_id adalah 3, maka aku akan menambahkan diskon sebesar 0.01 dari sum_jumlah saat ini. Aku menggunakan fungsi cv() untuk mendapatkan nilai saat cell saat ini (cell yang sedang diproses). Contoh hasil query-nya, misalnya:


---------------------------------
 TANGGAL KATEGORI SUM     DISKON
         ID       JUMLAH 
---------------------------------
 28       5         2        0
 28       3        13     1,31
 30      10        15     0,01
 30       4         4        0
 31       3         2      0,2
---------------------------------

Sekarang sudah saatnya untuk beristirahat sejenak, sebab tadi aku melihat the girl next door sudah pulang. Rambutnya yang panjang dibiarkan terurai rapi.. Ini pasti pertanda yang baik di hari Valentine. Aku jadi semakin betah disini.

Iklan

Day 32: SQL Untuk Analisa Data

Learning In HomeOriginal Date: 13 Februari 2009

Sebagai lanjutan dari pelajaran kemarin, hari ini aku akan mempelajari beberapa perluasan SQL milik Oracle Database yang berfungsi untuk analisa data. Aku akan mulai dengan RANK() dan DENSE_RANK(). Kedua fungsi tersebut akan mengembalikan nilai peringkat mulai dari 1, 2, dan seterusnya. Perbedaannya terletak pada penanganan dua atau lebih record yang memiliki peringkat yang sama. Misalnya, ada 3 record yang memiliki peringkat yang sama, yaitu peringkat 1. Jika menggunakan RANK(), record berikut-nya yang bernilai berbeda akan memiliki peringkat 4. Jika menggunakan DENSE_RANK(), record berikut-nya yang bernilai berbeda akan memiliki peringkat 2. Contoh penggunakan DENSE_RANK(), misalnya, aku ingin mengetahui produk apa yang nilai penjualannya terbesar dan memberikan peringkat terhadap masing-masing produk:


SELECT
s.prod_id, p.prod_name,
SUM(s.amount_sold),
DENSE_RANK() OVER (ORDER BY SUM(s.amount_sold) DESC)
FROM
sales s, products p
WHERE
s.prod_id = p.prod_id
GROUP BY
s.prod_id, p.prod_name;

Fungsi CUME_DIST() dapat dipakai untuk memberikan nilai yang relatif terhadap kumpulan nilai (bisa berupa seluruh kolom, atau berdasarkan partisi yang ditentukan). Nilai yang dikembalikan antara nilai lebih besar dari 0 (nol) hingga mencapai 1 (satu). Aku dapat menggunakan fungsi ini untuk mendapatkan nilai distribusi kumulatif, seperti pada contoh:


SELECT
c.cust_state_province,
SUM(s.quantity_sold),
CUME_DIST() OVER (ORDER BY SUM(s.quantity_sold)) as "Distribusi"
FROM
sales s, customers c
WHERE
s.cust_id = c.cust_id
GROUP BY
c.cust_state_province;

Fungsi NTILE() dapat dipakai untuk membagi tabel ke dalam sejumlah bagian yang ditentukan dan menghasilkan nilai mulai dari 1 hingga N (jumlah yang ditentukan) untuk masing-masing baris. Misalnya, aku ingin membagi tbl_pembelian ke dalam 5 kelompok, aku dapat menggunakan query berikut:


SELECT TANGGAL, JUMLAH, NTILE(5) OVER (ORDER BY TANGGAL ASC) FROM tbl_pembelian;

Dan database Oracle akan mengembalikan nilai seperti berikut untuk tabel dengan 15 baris:


---------------------------------
  TANGGAL  JUMLAH     NTILE(5)
---------------------------------
    x         x        1
    x         x        1
    ...       ...      1
                       2
                       2
                       2
                       3 
                       3
                       3
                       4
                       4
                       4
   ...        ...      5
   x          x        5
---------------------------------

Aku dapat menggunakan windowing function untuk menghitung nilai aggregate secara kumulatif. Misalnya, aku ingin menghitung nilai SUM untuk penjualan secara kumulatif mulai dari bulan Januari hingga bulan Desember dengan menggunakan SQL berikut:


SELECT
t.calendar_month_number,
SUM(s.amount_sold),
SUM(SUM(s.amount_sold)) OVER
(ORDER BY t.calendar_month_number ROWS UNBOUNDED PRECEDING) AS "Total Kumulatif"
FROM
sales s, times t
WHERE
s.time_id = t.time_id
GROUP BY
t.calendar_month_number;

Beberapa variasi untuk windowing function, misalnya moving berdasarkan selang 2 record sebelumnya:


AVG(x) OVER (ORDER BY x ROWS 2 PRECEDING)

atau centered pada nilai saat ini (row aktif), misalnya menghitung jumlah dari 2 baris sebelum hingga 2 baris sesudah untuk masing-masing baris:


SUM(x) OVER (ORDER BY x RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING)

Query SQL analisa lain yang cukup berguna adalah perluasan terhadap fungsi aggregate seperti SUM, AVG, COUNT(*), dsb sehingga mendukung OVER (PARTITION BY). Sebagai contoh, aku memiliki tabel TBL_PEMBELIAN, dan TBL_BARANG. Aku ingin mendapatkan laporan total pembelian per tanggal, dimana total tersebut didapatkan dari SUM field jumlah yang ada di TBL_PEMBELIAN dikali dengan field harga yang ada di TBL_BARANG. Perluasan OVER (PARTITION BY) untuk fungsi agregate SUM() mempermudah aku dalam membuat query-nya:


SELECT DISTINCT "Tanggal", "Total Pembelian" FROM
(SELECT
pb.tanggal AS "Tanggal",
brg.nama AS "Nama Barang",
brg.harga,
SUM(pb.jumlah),
SUM(SUM(pb.jumlah) * brg.harga) OVER (PARTITION BY pb.tanggal) AS "Total Pembelian"
FROM
tbl_pembelian pb, tbl_barang brg
WHERE
pb.barang_id = brg.id
GROUP BY
tanggal, brg.nama, brg.harga);

Untuk mendapatkan nilai ratio untuk suatu ekspresi, aku dapat menggunakan fungsi RATIO_TO_REPORT. Sebagai contoh, aku ingin mendapatkan nilai persentase jumlah penjualan berdasarkan bulan:


SELECT
t.calendar_month_number,
sum(s.quantity_sold),
TO_CHAR(RATIO_TO_REPORT(SUM(s.quantity_sold)) OVER() * 100, '99.99') || ' %' AS "Persentase"
FROM
sales s, times t
WHERE
s.time_id = t.time_id
GROUP BY
t.calendar_month_number;

Fungsi LAG dan LEAD dapat dipakai untuk menggeser baris dari sebuah kolom berdasarkan nilai offset yang ditentukan. LAG akan menggeser sejumlah N offset ke bawah, sementara LEAD akan menggser sejumlah N offset ke atas.