![Normalisasi Database](http://images.ctfassets.net/7wjlcr9d5ii8/3LoNYa32F0pLTUcoTE0ig5/c3ea83f5379fababfc2f2d0819e21074/web-g6f840b34f_1920.jpg?w=1920&h=1114&fl=progressive&q=75&fm=jpg)
Normalisasi Database adalah proses strukturisasi data dalam rangka mengurangi Data Redundancy dan meningkatkan Data Integrity. Data Redundancy adalah pengulangan data yang sama sehingga beresiko terhadap kesalahan penginputan. Data Integrity adalah data yang yang mudah dimaintain, akurat, dan konsisten. Lawan kata dari Normalisasi adalah Denormalisasi. Data Denormalisasi memiliki redundancy yang tinggi dan integritasnya rendah. Oleh karena itu, kita butuh menormalisasi data untuk mencapai kedua tujuan tersebut.
Kali ini kita akan mencoba melakukan strukturisasi data dari Denormalized menjadi Normalized hingga bentuk normalisasi kelima (5NF). Kita ambil satu contoh data yang Denormalized menggunakan use case data perfilman seperti ini:
Table director
nik | name | origin | phone_no |
---|---|---|---|
123 | Kimo | DKI Jakarta | 08555 |
321 | Timo | Papua | 08333 |
999 | Hanung | DKI Jakarta | 08111,08222 |
1NF
Kriteria untuk memenuhi 1NF adalah tidak ada multivalue pada sebuah kolom. Pada table director
, kolom phone_no
dengan NIK 999 memiliki multivalue karena mempunyai lebih dari satu nomor telepon, sehingga melanggar 1NF. Kekurangan pada data di atas adalah ketika data dengan NIK 999 mengganti salah satu nomornya. Misalkan dari 08222 ke 08221 maka kita harus update kolom tersebut dengan query memasukkan salah satu nomor lama, lalu tambahkan nomor baru di belakangnya secara comma-separated. Ke depannya bakal ribet maintain data seperti ini. Untuk itu kita perlu normalisasi di sini menjadi seperti berikut:
Table director
nik | name | origin | phone_no |
---|---|---|---|
123 | Kimo | DKI Jakarta | 08555 |
321 | Timo | Papua | 08333 |
999 | Hanung | DKI Jakarta | 08111 |
999 | Hanung | DKI Jakarta | 08222 |
Sekarang sudah tidak ada lagi satu kolom yang memiliki multivalue karena datanya sudah dipisah. Sehingga sekarang tabel di atas sudah memenuhi kriteria 1NF. Ketika sutradara dengan NIK 999 mau mengganti nomornya dari 08222 ke 08221 maka cukup update row yang menyimpan value 08222 saja tanpa mengganggu data pada nomor lainnya.
Key Dalam Database
Sebelum masuk ke tingkat normalisasi selanjutnya, kita perlu tahu dulu beberapa “key” dalam database.
Candidate key adalah kolom unik yang menjadi penanda bahwa data tersebut merupakan satu kesatuan data. Candidate key dari tabel di atas adalah nik
dan phone
, karena itu merupakan kolom unik. Kolom name
bukan candidate key karena setiap orang bisa saja memiliki nama yang sama. Begitu juga dengan origin
.
Primary Key adalah kolom unik yang menjadi pembeda antar row yang bisa dijadikan referensi dari data tersebut dan tidak boleh null. Candidate Key boleh dijadikan Primary Key. Tapi best practice-nya Candidate Key tidak dianjurkan menjadi Primary Key, karena candidate key bisa saja berubah nilainya. Misalkan pada tabel di atas kolom nik
kita jadikan primary key. Kemudian data nik
tersebut berelasi dengan tabel lainnya. Lalu ternyata ada typo saat input NIK di awal dan harus diganti. Tentu tidak semudah itu mengganti Primary Key ketika datanya sudah berelasi. Begitu juga dengan kolom phone_no
, nilainya memang unik, tapi nomor telepon itu bisa berubah atau berpindah tangan. Oleh karena itu, best practice-nya adalah menggunakan auto-incremented value sebagai Primary Key, sedangkan Candidate Key cukup diberi constraint Unique Key.
Unique Key adalah kolom yang memiliki value yang tidak boleh duplikat. Bedanya dengan Primary Key adalah kita bisa membuat Unique Key nullable atau non-nullable.
Foregin Key adalah kolom yang menjadi penghubung relasi antar tabel sebagai referensi dari satu tabel ke tabel lainnya. Untuk lebih jelasnya nanti ada contohnya di bawah.
2NF
Syarat untuk memenuhi kriteria 2NF adalah sudah memenuhi syarat 1NF dan tidak ada partial functional dependency pada non-candidate key di tabel relasi. Functional Dependency adalah kolom yang dependant pada kolom lainnya. Sebuah kolom disebut dependant jika data dari kolom tersebut bisa diidentifikasi lewat Candidate Key. Partial Functional Dependency adalah ketika ada kolom yang hanya dependant pada salah satu Candidate Key saja. Contohnya pada data di atas Candidate Key-nya adalah nik
dan phone_no
. Data nik
bisa mengidentifikasi nama dan daerah asal sutradara. Tetapi data phone_no
tidak bisa mengidentifikasi nama dan daerah asal sutradara, karena bisa saja si sutradara memiliki lebih dari satu nomor telepon, atau nomornya berpindah tangan, atau bahkan mengganti nomor teleponnya. Nama dan asal sutradara tersebut hanya bergantung pada nik
saja. Inilah yang disebut partial functional dependency sehingga tidak memenuhi syarat 2NF. Masalahnya adalah akan terdapat pengulangan beberapa row data jika sutradara tersebuk memiliki nomor lebih dari satu sehingga ribet dimaintain. Contohnya ketika sutradara dengan NIK 999 ganti nama atau ganti daerah asal, maka butuh update beberapa row. Oleh karena itu kolom-kolom yang memiliki partial functional dependency tersebut dipisah menjadi tabel sendiri agar memenuhi syarat 2NF seperti berikut:
Table director
id | nik | name | origin |
---|---|---|---|
1 | 123 | Kimo | DKI Jakarta |
2 | 321 | Timo | Papua |
3 | 999 | Hanung | DKI Jakarta |
Table phone
id | phone_no | director_id |
---|---|---|
1 | 08111 | 1 |
2 | 08222 | 1 |
3 | 08555 | 2 |
4 | 08333 | 3 |
Sekarang tidak ada lagi pengulangan data pada tabel-tabel di atas. Di sini kita juga udah memisahkan primary key-nya menggunakan auto-incremented, sedangkan candidate key menggunakan constraint unique key. Kedua tabel tersebut dihubungkan oleh kolom director_id
di tabel phone
. Itulah yang disebut foregin key seperti penjelasan sebelumnya. Sekarang ketika mengganti nama sutradara dengan NIK 999 cukup update satu row saja.
3NF
Misalkan tabel director
dibuat sebagai berikut:
Table director
id | nik | name | origin_code | origin_province |
---|---|---|---|---|
1 | 123 | Kimo | JKT | DKI Jakarta |
2 | 321 | Timo | PAP | Papua |
3 | 999 | Hanung | JKT | DKI Jakarta |
Kriteria yang memenuhi 3NF adalah sudah memenuhi 2NF dan tidak terdapat transitive dependency pada tabel relasi. Transitive Dependency adalah ketika kolom A bergantung pada kolom B dan kolom B bergantung pada kolom C, tetapi kolom C tidak bergantung pada kolom B, maka kolom A secara tidak langsung bergantung ke kolom C pada tabel relasi. Biar ga pusing kita ambil contoh di atas. Tabel di atas sudah memenuhi 2NF karena tidak ada partial functional dependency. Kolom origin_province
bergantung ke kolom origin_code
dan kolom origin_code
bergantung ke kolom nik
, sehingga kolom origin_province
secara tidak langsung bergantung ke kolom nik
, tetapi kolom nik
tidak bergantung pada kolom origin_code
. Jadi dari NIK, kita bisa tahu sutradara tersebut kode asalnya apa, dan dari kode tersebut kita bisa tahu itu provinsi apa, tapi dari kode tersebut kita tidak bisa tahu itu NIK siapa karena satu provinsi bisa terdapat lebih dari satu sutradara. Inilah yang disebut transitive dependency. Masalah pada transitive dependency pada tabel di atas adalah, jika data pada kolom origin_province
, misalkan sebelumnya dengan value "DKI Jakarta" berganti nama menjadi "Jakarta", maka perlu update semua row yang memiliki value "DKI Jakarta". Kalau data sutradara yang berasal dari DKI Jakarta sangat banyak tentu ga efisien. Sekarang kita ubah menjadi 3NF:
Table director
id | nik | name | origin_id |
---|---|---|---|
1 | 123 | Kimo | 1 |
2 | 321 | Timo | 2 |
3 | 999 | Hanung | 1 |
Table origin
id | origin_code | province |
---|---|---|
1 | JKT | DKI Jakarta |
2 | PAP | Papua |
Tabel di atas sudah memenuhi 3NF. Tidak ada lagi transitive dependency. Ketika melakukan perubahan daerah asal cukup ganti pada tabel origin
saja, tabel director
hanya mereferensikan datanya lewat foreign key origin_id
.
BCNF
BCNF adalah singkatan dari Boyce-Codd Normalization Form. Ini sebenarnya mirip dengan 3NF dan sering disebut 3.5NF. Keduanya memiliki kriteria yang mirip, hanya saja pada BCNF tidak ada functional dependency sama sekali pada tabel yang memiliki lebih dari satu Candidate Key yang saling bergantung untuk menghindari pengulangan data. Tagline-nya adalah "Nothing but the key". Jadi pada tabel relasi, isinya hanya Candidate Key saja. Contohnya pada tabel berikut:
Table movie
id | name | year |
---|---|---|
1 | Rumah Dara | 2010 |
2 | Ratu Ilmu Hitam | 2019 |
3 | Sebelum Iblis Menjemput | 2018 |
Table director
id | nik | name | movie_id |
---|---|---|---|
1 | 123 | Kimo | 1 |
2 | 321 | Timo | 1 |
3 | 123 | Kimo | 2 |
4 | 321 | Timo | 3 |
Tabel di atas memiliki 2 Candidate Key yaitu nik
dan movie_id
yang saling bergantung. Tabel di atas memenuhi 3NF karena tidak ada transitive dependency. Tapi melanggar BCNF karena terdapat functional dependency, yaitu name
yang hanya bergantung pada nik
. Satu sutradara bisa menyutradai lebih dari satu film, dan satu film bisa disutradai lebih dari satu orang. Kimo menyutradai Rumah Dara & Ratu Ilmu Hitam, Timo menyutradai Rumah Dara & Sebelum Iblis Menjemput. Oleh karena itu akan terjadi pengulangan data seperti di atas. Next kita coba terapkan BCNF seperti berikut:
Table movie
id | name | year |
---|---|---|
1 | Rumah Dara | 2010 |
2 | Ratu Ilmu Hitam | 2019 |
3 | Sebelum Iblis Menjemput | 2018 |
Table director
id | nik | name |
---|---|---|
1 | 123 | Kimo |
2 | 321 | Timo |
3 | 999 | Hanung |
Table movie_director
director_id | movie_id |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
2 | 3 |
Kita bikin tabel Composite khusus untuk mapping referensi data director
dan movie
yang hanya berisi Key tanpa dependency lain. Setelah kita pisah tabel relasinya seperti di atas, tidak ada lagi pengulangan data. Ini cocok untuk many-to-many relationship.
1NF hingga BCNF adalah normalisasi paling dasar pada RDBMS dan paling sering ditemui saat melakukan design schema. Selanjutnya kita lanjut ke part yang lebih ekstreme😎.
4NF
Umumnya, normalisasi yang lebih kuat dari 3NF atau BCNF ditemui pada kasus yang benar-benar kompleks. Kriteria 4NF adalah memenuhi syarat BCNF dan tidak ada multivalued dependency pada tabel relasi. Multivalued Dependency adalah ketika kolom A & B saling bergantung dan kolom B & C juga saling bergantung, sehingga terdapat lebih dari satu dependency pada masing-masing key. Biasanya ditandai dengan lebih dari dua kolom composite key. Langsung saja ke contoh berikut:
Table director
id | nik | name |
---|---|---|
1 | 123 | Kimo |
2 | 321 | Timo |
3 | 999 | Hanung |
Table genre
id | name |
---|---|
1 | Horror |
2 | Comedy |
3 | Biography |
4 | Drama |
5 | Acion |
6 | Thriller |
Table movie
id | name | year |
---|---|---|
1 | Rumah Dara | 2010 |
2 | Ratu Ilmu Hitam | 2019 |
3 | Sebelum Iblis Menjemput | 2018 |
4 | Rudy Habibie | 2016 |
Table director_movie_genre
director_id | movie_id | genre_id |
---|---|---|
1 | 1 | 1 |
1 | 1 | 6 |
2 | 1 | 1 |
2 | 1 | 6 |
3 | 4 | 3 |
3 | 4 | 4 |
Tabel di atas sudah memenuhi BCNF karena tidak ada functional dependency pada tabel relasi. Tapi tabel di atas relasinya multivalued sehingga melanggar 4NF karena satu sutradara bisa menyutradai lebih dari satu film, satu film bisa disutradai lebih dari satu orang, dan satu film bisa saja memiliki lebih dari satu genre, begitu juga satu genre bisa saja termasuk ke dalam lebih dari satu film. Jadi bisa disimpulkan bahwa, sutradara Kimo menyutradai film Rumah Dara, Rumah Dara adalah film Horror & Thriller, dan Kimo adalah sutradara film genre Horror & Thriller. Masalahnya adalah akan terjadi pengulangan data setiap ada film baru dengan multiple genre atau multiple sutradara. Mulai kompleks kan🤯? Solusinya kita akan pecah tabel director_movie_genre
menjadi seperti berikut:
Table movie_director
director_id | movie_id |
---|---|
1 | 1 |
2 | 1 |
3 | 4 |
Table movie_genre
movie_id | genre_id |
---|---|
1 | 1 |
1 | 6 |
4 | 3 |
4 | 4 |
Setelah dipecah, sekarang tidak ada lagi multivalued dependency karena antara movie_director
dan movie_genre
masing-masing sudah independent. Jadi tiap ada film baru, tinggal tambahin genre dan sutradaranya di masing-masing tabel.
5NF
Kriterianya adalah memenuhi syarat 4NF dan tidak ada Join Dependency pada tabel relasi. Join Dependency adalah ketika kolom A & B saling bergantung, kolom B & C saling bergantung, dan kolom A & C juga saling bergantung. Jadi masing-masing kolom pada tabel relasi saling bergantung. Biar lebih paham bisa dilihat dari skema di bawah:
Table genre
id | name |
---|---|
1 | Horror |
2 | Comedy |
3 | Biography |
4 | Drama |
5 | Acion |
6 | Thriller |
Table movie
id | name | year |
---|---|---|
1 | Rumah Dara | 2010 |
2 | Ratu Ilmu Hitam | 2019 |
3 | Sebelum Iblis Menjemput | 2018 |
4 | Rudy Habibie | 2016 |
Table subgenre
id | name |
---|---|
1 | Slasher |
2 | Gore |
3 | Supernatural |
4 | Psychological |
Table movie_genre
movie_id | genre_id |
---|---|
1 | 1 |
1 | 6 |
4 | 3 |
4 | 4 |
Table genre_subgenre
genre_id | subgenre_id |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | 4 |
Satu film memiliki beberapa genre, satu genre bisa juga termasuk ke dalam beberapa film. Satu genre bisa memiliki beberapa subgenre, satu subgenre juga bisa termasuk ke dalam beberapa genre. Satu film bisa saja termasuk ke beberapa subgenre, dan satu subgenre juga bisa termasuk ke dalam beberapa film. Jadi bisa disimpulkan seperti film A memiliki genre B, dan genre B memiliki subgenre C & D, namun film A tersebut hanya termasuk subgenre C saja. Makin kompleks kan😱. Tabel di atas sudah memenuhi 4NF karena tidak ada multivalued dependency. Tapi melanggar 5NF karena pada data di atas, film Rumah Dara bergenre Horror dan Thriller. Genre Horror memiliki subgenre seperti Slasher, Gore, Supernatural, dan Psychological. Akan tetapi film Rumah dara hanya termasuk ke dalam subgenre Slasher dan Gore saja. Sehingga kita harus memastikan bahwa subgenre dari film Rumah Dara hanya Slasher dan Gore. Maka dari itu kita normalize tabel di atas dengan menambahkan tabel baru seperti berikut:
Tabel movie_subgenre
movie_id | subgenre_id |
---|---|
1 | 1 |
1 | 2 |
Setelah dinormalisasi, sekarang tidak ada lagi redundancy pada skema tersebut. Ini adalah normalisasi terakhir dimana tidak ada redundancy sama sekali.
Verdict
Kita telah melakukan praktek normalisasi data dari denormalized menjadi normalized hingga 5NF. Sebenarnya 6NF juga ada, tapi 5NF sudah merupakan bentuk normal paling akhir dalam rangka mengurangi Data Redundancy. 6NF hanya bentuk penyempurnaannya saja dengan memastikan setiap kolom dari tabel relasi dipecah menjadi composite terkecil. Makanya 5NF sebenarnya udah cukup disebut bentuk paling normal. Oh ya, tidak semua kasus bisa langsung diterapkan Normalisasi yang tinggi, semuanya dilakukan secara bertahap dari 1NF hingga seterusnya tergantung use case. Normalized data cocok untuk menyimpan data transaksi (OLTP) yang membutuhkan integritas yang kuat dengan redundancy yang minimal. Tapi bukan berarti Denormalized Data merupakan skema yang buruk. Denormalized Data lebih cocok untuk menampung data analytics (OLAP) karena biasanya berfokus pada history data yang banyak, bukan untuk transaksi sehingga lebih optimized. Untuk Denormalized Data biasanya lebih cocok menggunakan Column-Based NoSql daripada RDBMS.