author-pic

Ferry S

An ISTJ, Type 5, Engineer, Gamer, and Thriller-Movies-Lover
Contoh Normalisasi Database (1NF hingga 5NF)
Mon. Aug 22nd, 2022 12:15 PM10 mins read
Contoh Normalisasi Database (1NF hingga 5NF)
Source: Pixabay - Normalization Database

Database Normalization 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 mudah dimaintain, akurat, dan konsisten. Kali ini kita akan melakukan normalisasi data dari yang belum dinormalisasi atau Unnormalized Form (UNF) menjadi Normalized Form 1NF, 2NF, 3NF, BCNF, 4NF, 5NF, dan DKNF. Unnormalized Form memiliki redundancy yang tinggi dan integritasnya rendah. Oleh karena itu, kita butuh menormalisasi data untuk mencapai kedua goals tersebut. Biar lebih paham, mending langsung praktekkan menggunakan database yang dimiliki😊. Kita ambil contoh menggunakan 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

Kriteria untuk memenuhi 1NF adalah tidak ada Multivalue pada kolom. Pada table director, kolom phone_no dengan NIK 999 Multivalue karena ada lebih dari satu nomor telepon sehingga melanggar 1NF. Masalahnya adalah ribet saat data dengan NIK 999 mengganti salah satu nomornya. Misalkan dari 08222 ke 08221 maka kita harus update kolom tersebut dengan memasukkan salah satu nomor lama, lalu tambahkan nomor baru di belakangnya secara comma-separated. Untuk itu kita perlu normalisasi 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 ga ada lagi kolom yang multivalue karena datanya sudah dipisah. Tabel di atas sudah memenuhi kriteria 1NF. Jika sutradara dengan NIK 999 mau mengganti nomornya dari 08222 ke 08221 maka cukup update satu row saja yang menyimpan value 08222.

Sebelum masuk ke tingkat normalisasi selanjutnya, kita perlu tahu dulu istilah “key” dalam database.

Candidate Key adalah satu atau kombinasi beberapa kolom unik yang menjadi penanda bahwa data tersebut merupakan satu kesatuan data yang mewakili kolom-kolom lainnya. Candidate Key dari tabel di atas ada 2, yaitu {nik} dan {phone_no}, karena nik adalah kolom unik yang mewakili kolom name & origin. Lewat nik kita bisa tahu nama dan asal sutradara karena satu NIK sudah pasti memiliki satu nama dan daerah asal. Kolom phone_no juga kolom unik yang mewakili kolom nik. Lewat phone_no kita bisa tahu ini nomor siapa karena satu nomor sudah pasti memiliki satu NIK. Kolom name bukan Candidate Key karena bisa saja ada orang yang namanya sama. Begitu juga dengan origin. Candidate Key fungsinya sebagai Identifier sebuah data secara bisnis.

Primary Key adalah kolom unik yang menjadi pembeda antar row yang bisa dijadikan referensi dari data tersebut dan tidak boleh null. Primary Key fungsinya sebagai Identifier sebuah data secara teknis. Candidate Key boleh dijadikan Primary Key. Tapi dalam hal ini Candidate Key tidak dianjurkan menjadi Primary Key karena bisa berubah nilainya. Misalkan pada tabel di atas kolom nik dijadikan Primary Key. Kemudian data tersebut berelasi dengan tabel lain. Lalu ternyata ada typo saat input NIK di awal dan harus diganti. Tentu tidak semudah itu mengganti Primary Key kalau datanya sudah berelasi. Meskipun ada fitur cascades, tetap saja ga worth it mengubah beberapa data dan tabel. Oleh karena itu, kita akan pakai auto-increment value sebagai Primary Key pada kasus ini, sedangkan Candidate Key cukup diberi constraint Unique Key. Untuk lebih jelasnya bisa dibaca tulisan tentang UUID vs Auto-Increment Key.

Unique Key adalah kolom yang nilainya unik. Bedanya dengan Primary Key adalah Unique Key membolehkan nilainya nullable maupun non-nullable.

Foreign Key adalah kolom yang menjadi penghubung relasi antar tabel sebagai referensi data dari satu tabel ke tabel lainnya.

Syarat untuk memenuhi kriteria 2NF adalah memenuhi syarat 1NF dan tidak ada Partial/Functional Dependency. Partial/Functional Dependency adalah ketika sebuah tabel memiliki lebih dari satu Candidate Key dan ada kolom yang bergantung pada salah satu Candidate Key saja. Contohnya pada data di atas Candidate Key-nya {nik} dan {phone_no}. Kita bisa tahu nama dan daerah asal sutradara lewat NIK karena datanya diwakili NIK. Tapi kita tidak bisa tahu nama dan daerah asal sutradara lewat nomor telepon karena itu bukan bagian dari nomor telepon. Jadi kolom name dan origin hanya bergantung pada Candidate Key nik saja. Masalahnya, akan terjadi pengulangan data pada kolom Non-Candidate Key jika sutradara tersebut memiliki nomor telepon lebih dari satu sehingga ribet dimaintain. Contohnya jika sutradara dengan NIK 999 yang memiliki lebih dari satu nomor telepon pada data di atas ganti nama atau ganti daerah asal, maka butuh update lebih dari satu row. Oleh karena itu kolom-kolom yang memiliki Partial/Functional Dependency dipisah menjadi tabel sendiri agar memenuhi syarat 2NF seperti berikut:

Table director

id (PK) nik (UK) name origin
1 123 Kimo DKI Jakarta
2 321 Timo Papua
3 999 Hanung DKI Jakarta

Table phone

id (PK) phone_no (UK) director_id (FK)
1 08111 3
2 08222 3
3 08555 1
4 08333 2

Sekarang tidak ada lagi pengulangan data pada kolom Non-Candidate Key pada tabel di atas. Kita juga memisahkan Primary Key-nya menggunakan auto-increment, sedangkan Candidate Key menggunakan constraint Unique Key. Kedua tabel tersebut dihubungkan oleh kolom director_id di tabel phone. Itulah yang disebut Foreign Key seperti penjelasan sebelumnya. Sekarang kalau mau ganti nama sutradara cukup update satu row saja.

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 ada Transitive Dependency. Transitive Dependency adalah ketika kolom A bergantung pada kolom B yang bukan Candidate Key, dan kolom B bergantung pada kolom C yang menjadi Candidate Key, tetapi kolom C tidak bergantung pada kolom B. Jadi, ada kolom yang bergantung pada kolom lain yang bukan Candidate Key. Biar ga pusing kita ambil contoh di atas. Tabel di atas sudah memenuhi 2NF karena tidak ada Partial/Functional Dependency, Candidate Key-nya hanya nik karena cuma NIK yang unik. Namun itu tidak memenuhi 3NF. Kolom origin_province bergantung ke kolom origin_code yang bukan Candidate Key, dan kolom origin_code bergantung ke kolom nik. 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 daerah bisa terdapat lebih dari satu sutradara. Masalahnya, jika data pada kolom origin_province, misalkan sebelumnya dengan value "DKI Jakarta" diubah jadi "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. Ga ada lagi Transitive Dependency. Saat melakukan perubahan nama provinsi, cukup ganti satu row pada tabel origin saja, tabel director hanya mereferensikan datanya lewat origin_id.

BCNF adalah singkatan dari Boyce-Codd Normalization Form. Ini sebenarnya mirip dengan 3NF dan disebut 3.5NF. Keduanya memiliki kriteria yang mirip, hanya saja pada BCNF tidak ada Functional Dependency sama sekali pada tabel yang memiliki kombinasi kolom Candidate Key untuk menghindari redundancy. Tagline-nya adalah "Nothing but the key". Jadi pada tabel relasi, isinya hanya Key yang berelasi saja. Contoh kasusnya seperti ini:

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 memenuhi 3NF karena tidak ada Transitive Dependency. Kolom nik mewakili name & movie_id, sedangkan kolom movie_id hanya mewakili nik. Jadi di sini ada satu Candidate Key, yaitu kombinasi {nik, movie_id} karena keduanya saling bergantung. Tapi itu melanggar BCNF karena ada Functional Dependency, yaitu kolom name yang bergantung ke nik dan bukan Candidate Key. Permasalahannya adalah satu orang 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. Makanya terjadi pengulangan data pada kolom name di atas. Next kita terapkan BCNF:

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 berisi kombinasi Candidate Key saja tanpa kolom lain. Sekarang tidak ada lagi redundancy. Ini cocok untuk many-to-many relationship.

1NF hingga BCNF adalah normalisasi paling dasar dan paling sering ditemui saat desain skema. Kita lanjut ke part yang lebih ekstreme😎.

Kriteria 4NF adalah memenuhi syarat BCNF dan tidak ada Multivalued Dependency. Multivalued Dependency adalah ketika terdapat lebih dari satu dependency pada tabel. Misalnya Candidate Key {A, B} saling bergantung dan Candidate Key {B, C} juga saling bergantung, sehingga terdapat lebih dari satu kombinasi Candidate Key pada tabel. Contohnya seperti berikut:

Table director

id nik name
1 123 Kimo
2 321 Timo
3 999 Hanung

Table movie

id name year
1 Rumah Dara 2010
2 Ratu Ilmu Hitam 2019
3 Sebelum Iblis Menjemput 2018
4 Rudy Habibie 2016

Table genre

id name
1 Horror
2 Comedy
3 Biography
4 Drama
5 Action
6 Thriller

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 memenuhi BCNF karena hanya ada Candidate Key pada tabel relasi. Tapi itu Multivalued karena ada 2 Candidate Key, yaitu kombinasi {director_id, movie_id} dan kombinasi {movie_id, genre_id} sehingga melanggar 4NF. Satu orang bisa menyutradai lebih dari satu film, dan satu film bisa disutradai lebih dari satu orang. 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, akan terjadi redundancy pada tabel di setiap penambahan film baru dengan multi-genre atau multi-sutradara karena director_id secara tidak langsung ada dependency dengan kolom genre. 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 tidak ada lagi Multivalued Dependency karena masing-masing tabel sudah independent. Jadi tiap ada film baru, tinggal insert genre dan sutradaranya di masing-masing tabel relasi.

Kriterianya adalah memenuhi syarat 4NF dan tidak ada Join Dependency pada tabel relasi. Join Dependency artinya terjadi redundancy saat Join dan menghasilkan data yang tidak diinginkan. Misalnya Candidate Key {A, B} saling bergantung, lalu Candidate Key {B, C} di tabel lain juga saling bergantung, sehingga secara tidak langsung Candidate Key A ada dependency ke Candidate Key C melalui perantara Candidate Key B pada saat Join dan menimbulkan redundancy yang tidak diinginkan. Biar lebih paham bisa dilihat skema di bawah:

Table genre

id name
1 Horror
2 Comedy
3 Biography
4 Drama
5 Action
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 ke dalam subgenre C saja. Makin kompleks dong😱. Skema 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. Masalahnya, film Rumah Dara akan memiliki semua subgenre Horror saat semua table di-Join dan tidak sesuai kriteria bisnis karena akan terjadi redundancy subgenre yang tidak diinginkan seperti berikut:

Join Result

movie genre subgenre
Rumah Dara Horror Slasher
Rumah Dara Horror Gore
Rumah Dara Horror Supernatural
Rumah Dara Horror Psychological

Kita harus memastikan bahwa subgenre dari film Rumah Dara hanya Slasher dan Gore. Maka dari itu kita normalize skema di atas dengan menambahkan tabel baru seperti berikut:

Tabel movie_subgenre

movie_id subgenre_id
1 1
1 2

Setelah dinormalisasi tidak ada lagi redundancy pada skema tersebut saat semua tabel di-Join dan sudah sesuai kriteria bisnis. Ini adalah normalisasi terakhir dimana tidak ada redundancy sama sekali.

Join Result

movie genre subgenre
Rumah Dara Horror Slasher
Rumah Dara Horror Gore

DKNF (Domain Key Normalization Form) syaratnya tidak ada constraint lain selain Key Constraint dan Domain Constraint untuk menghindari anomaly. Key Constraint artinya kolom tersebut memiliki reference pada kolom unik di tabel lain seperti normalisasi-normalisasi di atas. Sedangkan Domain Constraint artinya kolom tersebut memiliki reference secara bisnis berdasarkan satu set data di tabel lain. Biar ga bingung bisa liat contoh berikut:

Table movie

id name budget budget_status
1 Rumah Dara 1000000000 Milyaran
2 Ratu Ilmu Hitam 5550000000 Milyaran
3 Sebelum Iblis Menjemput 1000000 Milyaran
4 Rudy Habibie 70000000 Jutaan

Tabel di atas melanggar DKNF karena bisa saja terjadi anomaly pada kolom budget_status. Misalkan user melakukan insert budget Rp1.000.000 tapi budget statusnya diisi Milyaran seperti data film Sebelum Iblis Menjemput di atas. Ini tentu tidak sesuai kriteria bisnis karena harusnya statusnya Jutaan. Untuk itu kita akan buat tabel budgets sebagai Domain Constraint untuk budget_status seperti berikut:

Table movie

id name budget
1 Rumah Dara 1000000000
2 Ratu Ilmu Hitam 5550000000
3 Sebelum Iblis Menjemput 1000000
4 Rudy Habibie 70000000

Table budgets

id budget_status min_budget max_budget
1 Milyaran 1000000000 999999999999
2 Jutaan 1000000 999999999

Sekarang data di atas sudah memenuhi DKNF karena ada Domain Constraint dari kolom budget_status di table movie ke tabel budgets. Sehingga tidak ada lagi anomaly data karena statusnya bersumber dari tabel budgets. Query joinnya nanti kurang lebih seperti berikut:

SELECT
    m.name,
    m.budget,
    b.budget_status
FROM movie m
         INNER JOIN budgets b
ON m.budget BETWEEN b.min_budget AND b.max_budget
;

Join Result

id name budget budget_status
1 Rumah Dara 1000000000 Milyaran
2 Ratu Ilmu Hitam 5550000000 Milyaran
3 Sebelum Iblis Menjemput 1000000 Jutaan
4 Rudy Habibie 70000000 Jutaan

Kita telah melakukan praktek normalisasi data dari Unnormalized Form (UNF) menjadi Normalized Form 1NF, 2NF, 3NF, BCNF, 4NF, 5NF, dan DKNF. Sebenarnya 6NF juga ada sebagai bentuk penyempurnaannya dengan memecah setiap kolom dari tabel. Contohnya kolom tahun dan nama film pada tabel movie dipecah lagi ke tabel baru, yaitu tabel movies_year dan movies_name misalnya sehingga kolom year dan name pada tabel movie nanti valuenya adalah reference dari tabel-tabel tersebut. Tapi jarang dibutuhkan kasus seperti itu sih. Makanya 5NF sebenarnya udah cukup disebut bentuk paling normal. Selain itu ada juga EKNF (Elementary Key), tapi EKNF itu mirip BCNF. Lalu ada juga ETNF (Essential Tuple) yang menjadi improvement dari 4NF dalam rangka mengurangi Join Dependency, tapi 5NF biasanya lebih diutamakan untuk mencegah Join Dependency karena lebih strict. DKNF adalah bentuk normalisasi yang mereferensikan datanya lewat Key Constraint dan Domain Constraint. Oh ya, tidak semua kasus bisa langsung diterapkan Normalisasi yang tinggi, semuanya dilakukan secara bertahap dari 1NF hingga seterusnya tergantung kasus. Normalized data cocok untuk menyimpan data transaksi yang membutuhkan integritas yang kuat dengan redundancy yang minimal. Tapi bukan berarti Data yang tidak Normalized merupakan skema yang buruk. Itu lebih cocok untuk menampung data analytics karena berfokus pada history data dan biasanya lebih cocok menggunakan NoSql daripada RDBMS.