Penyegaran normalisasi

Penyegaran normalisasi hingga 3NF dan titik di mana desain analitik berbeda arah.

Daftar isi

Topik ini menyegarkan normalisasi sampai bentuk normal ketiga (3NF) menggunakan satu contoh tabel klinik yang disusun ulang bertahap, kemudian menunjukkan titik belok tempat desain analitik memilih arah yang berlawanan. Penyebab anomali pembaruan diperagakan dengan kode yang benar-benar dijalankan, dan bentuk 3NF di-join kembali untuk membuktikan konsistensinya. Dua bentuk skema analitik, yaitu star schema serta one big table, ditempatkan sebagai ujung dari perbedaan arah tersebut.

Mengapa normalisasi#

Normalisasi adalah proses menyusun tabel dan kunci agar redundansi berkurang dan anomali tidak muncul ketika data dibaca, ditambah, diubah, atau dihapus. Redundansi berarti nilai yang sama tersimpan di banyak baris, dan bahayanya bukan pada ukuran berkasnya, melainkan pada kemungkinan salinan-salinan itu berpisah arah saat diperbarui. Konsep ini dibangun di atas model relasional Codd dan menjadi disiplin dasar perancangan basis data operasional.

Anomali pembaruan (update anomaly) adalah kondisi ketika pembaruan sebagian menyisakan data yang saling bertentangan dalam satu basis data. Sebagai contoh, tabel kunjungan klinik yang menyimpan nama dan telepon pasien di setiap baris kunjungan akan memuat telepon yang sama berulang kali. Pembaruan yang hanya menyentuh satu baris membuat pasien yang sama memiliki dua nomor berbeda, dan tidak ada aturan dalam tabel itu yang mencegahnya.

Tiga bentuk normal pertama menjadi sasaran paling lazim untuk basis data transaksional. Bentuk pertama menertibkan isi sel, bentuk kedua menertibkan ketergantungan pada kunci, dan bentuk ketiga menertibkan ketergantungan antar kolom non-kunci. Contoh klinik yang sama dipakai untuk menelusuri ketiganya secara berurutan.

Tiga bentuk normal dengan satu contoh#

Bentuk normal pertama (1NF) menuntut setiap sel berisi satu nilai atomik, tanpa daftar atau pengulangan yang dipadatkan dalam satu kolom. Tabel kunjungan berikut melanggarnya karena kolom obat menampung lebih dari satu nilai.

id_pasientanggalpoliobat
12026-02-02Poli Umumparacetamol, salep

Memecah daftar itu menjadi satu baris per obat memenuhi 1NF, tetapi redundansi justru bertambah karena setiap baris obat kembali mengulang data pasien. Inilah alasan normalisasi tidak berhenti di 1NF.

Bentuk normal kedua (2NF) menuntut setiap kolom non-kunci bergantung pada seluruh kunci. Tabel kunjungan berikut memakai kunci gabungan (id_pasien, tanggal), sedangkan nama dan telepon hanya bergantung pada id_pasien, sehingga keduanya berulang tanpa alasan pada setiap tanggal kunjungan.

id_pasientanggalnamateleponpoli
12026-02-02Ali0812-1111-01Poli Umum
12026-02-09Ali0812-1111-01Poli Gigi

Perbaikannya memisahkan atribut yang hanya bergantung pada pasien ke tabel pasien, sehingga tersisa tabel kunjungan yang setiap kolomnya bergantung pada kunci gabungan penuh.

Bentuk normal ketiga (3NF) menambahkan satu larangan lagi, yaitu kolom non-kunci tidak boleh bergantung pada kolom non-kunci lain. Apabila tabel kunjungan ikut menyimpan lantai gedung polinya, lantai sesungguhnya bergantung pada polinya, bukan pada kunjungan, sehingga atribut polinya dipisah ke tabel poli sendiri. Hasil akhirnya adalah setiap fakta tersimpan tepat satu kali dan setiap pembaruan cukup menyentuh satu baris.

Demonstrasi: anomali pembaruan dan perbaikan 3NF#

Skrip berikut memperagakan anomali itu di DuckDB. Tabel gabungan klinik dibuat, satu pembaruan telepon diterapkan hanya pada satu baris, lalu konsekuensinya dicetak. Setelah itu tabel dibentuk ulang menjadi 3NF, di-join kembali untuk tampilan yang sama, dan keluarannya membuktikan bahwa konsistensi kembali utuh.

python normalisasi.py
"""Memperagakan anomali pembaruan pada tabel kunjungan klinik dan bentuk 3NF-nya."""

import duckdb

con = duckdb.connect(":memory:")

# Bentuk awal (belum memenuhi 2NF): data pasien berulang pada setiap kunjungan
con.execute("""
    CREATE TABLE kunjungan_gabungan (
        id_pasien INTEGER NOT NULL,
        tanggal   DATE    NOT NULL,
        nama      VARCHAR NOT NULL,
        telepon   VARCHAR NOT NULL,
        poli      VARCHAR NOT NULL,
        PRIMARY KEY (id_pasien, tanggal)
    )
""")
con.executemany("INSERT INTO kunjungan_gabungan VALUES (?, ?, ?, ?, ?)", [
    (1, "2026-02-02", "Ali",  "0812-1111-01", "Poli Umum"),
    (2, "2026-02-03", "Budi", "0813-2222-02", "Poli Umum"),
    (1, "2026-02-09", "Ali",  "0812-1111-01", "Poli Gigi"),
])

# Pembaruan sebagian: nomor telepon Ali diperbarui hanya pada kunjungan pertama
con.execute("""
    UPDATE kunjungan_gabungan
    SET telepon = '0812-9999-99'
    WHERE id_pasien = 1 AND tanggal = DATE '2026-02-02'
""")

print("Tabel gabungan setelah pembaruan sebagian:")
rows = con.execute("SELECT * FROM kunjungan_gabungan ORDER BY id_pasien, tanggal").fetchall()
for id_p, tanggal, nama, telp, poli in rows:
    print(f"  pasien={id_p}  {tanggal}  {nama:<5} {telp}  {poli}")
print("Anomali: Ali kini memuat dua nomor telepon berbeda dalam satu tabel.")

# Bentuk 3NF: atribut yang bergantung pada pasien dipisah ke tabel sendiri
con.execute("CREATE TABLE pasien (id_pasien INTEGER PRIMARY KEY, nama VARCHAR NOT NULL, telepon VARCHAR NOT NULL)")
con.execute("""
    CREATE TABLE kunjungan (
        id_pasien INTEGER NOT NULL REFERENCES pasien(id_pasien),
        tanggal   DATE    NOT NULL,
        poli      VARCHAR NOT NULL,
        PRIMARY KEY (id_pasien, tanggal)
    )
""")
con.execute("INSERT INTO pasien VALUES (1, 'Ali', '0812-9999-99')")
con.execute("INSERT INTO pasien VALUES (2, 'Budi', '0813-2222-02')")
con.executemany("INSERT INTO kunjungan VALUES (?, ?, ?)", [
    (1, "2026-02-02", "Poli Umum"),
    (2, "2026-02-03", "Poli Umum"),
    (1, "2026-02-09", "Poli Gigi"),
])

print("Bentuk 3NF yang di-join kembali:")
rows = con.execute("""
    SELECT k.id_pasien, k.tanggal, p.nama, p.telepon, k.poli
    FROM kunjungan k
    JOIN pasien p ON p.id_pasien = k.id_pasien
    ORDER BY k.id_pasien, k.tanggal
""").fetchall()
for id_p, tanggal, nama, telp, poli in rows:
    print(f"  pasien={id_p}  {tanggal}  {nama:<5} {telp}  {poli}")

n_baris = con.execute("SELECT COUNT(*) FROM pasien WHERE nama = 'Ali'").fetchone()[0]
print(f"Sekarang satu pembaruan cukup: baris pasien Ali pada tabel pasien = {n_baris}.")
text Keluaran
Tabel gabungan setelah pembaruan sebagian:
  pasien=1  2026-02-02  Ali   0812-9999-99  Poli Umum
  pasien=1  2026-02-09  Ali   0812-1111-01  Poli Gigi
  pasien=2  2026-02-03  Budi  0813-2222-02  Poli Umum
Anomali: Ali kini memuat dua nomor telepon berbeda dalam satu tabel.
Bentuk 3NF yang di-join kembali:
  pasien=1  2026-02-02  Ali   0812-9999-99  Poli Umum
  pasien=1  2026-02-09  Ali   0812-9999-99  Poli Gigi
  pasien=2  2026-02-03  Budi  0813-2222-02  Poli Umum
Sekarang satu pembaruan cukup: baris pasien Ali pada tabel pasien = 1.

Keluaran bagian atas memperlihatkan Ali dengan dua nomor telepon berbeda dalam satu tabel, akibat pembaruan yang sah menurut basis data karena tidak ada aturan yang melarangnya. Keluaran bagian bawah memperlihatkan bentuk 3NF yang di-join kembali, dan kini nomor telepon Ali konsisten di seluruh baris karena hanya tersimpan satu kali pada tabel pasien. Satu pembaruan pada satu baris otomatis membenahi seluruh tampilan, dan itulah jaminan yang tidak diberikan oleh tabel gabungan.

Titik belok desain analitik#

Sampai titik ini normalisasi bergerak ke satu arah, yaitu memecah tabel agar redundansi hilang. Desain analitik membelok ke arah sebaliknya pada lapisan konsumsi, karena kueri analitik menyapu jutaan baris dan setiap join tambahan membayar biayanya berulang-ulang. Redundansi yang dulu dihindari kini diterima secara terkendali demi kecepatan pembacaan.

Dua bentuk akhir yang paling lazim adalah star schema dan one big table (OBT), yaitu satu tabel lebar yang menyatukan seluruh kolom fakta dan dimensi dalam satu tempat. Star schema membagi konteks ke dalam tabel dimensi, sedangkan OBT meniadakan join sama sekali. Keduanya berangkat dari data yang telah dibersihkan, dan sumbernya umumnya berupa skema tertenormalisasi, sehingga 3NF tetap berperan sebagai titik berangkat, bukan sebagai lawan.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat normalisasi dipraktikkan.

  1. Berhenti di 1NF dan menyimpan daftar dalam satu sel. Penyebabnya, nilai gabungan terasa lebih sederhana, padahal agregasi dan penghitungan per item menjadi rumit dan rentan salah. Perbaikannya, pecah menjadi satu baris per nilai lalu lanjutkan normalisasi ke bentuk berikutnya.
  2. Menganggap 3NF sebagai bentuk akhir untuk semua kebutuhan. Penyebabnya, disiplin normalisasi diterapkan tanpa memandang beban kerja, sehingga kueri analitik membayar banyak join pada data yang sangat besar. Perbaikannya, pertahankan 3NF di sisi transaksional dan bentuk star schema atau OBT di lapisan analitik.
  3. Memutakhirkan salinan redundan satu per satu dengan skrip terpisah. Penyebabnya, tabel gabungan tidak menegakkan satu sumber kebenaran sehingga sebagian salinan tertinggal. Perbaikannya, simpan setiap fakta sekali pada tabel pemiliknya dan susun ulang tampilan lewat join ketika dibutuhkan.

Ringkasan#

  • Normalisasi menyusun tabel dan kunci agar redundansi berkurang dan anomali pembaruan tidak muncul, dengan 1NF menertibkan isi sel, 2NF ketergantungan pada kunci, dan 3NF ketergantungan antar kolom non-kunci.
  • Anomali pembaruan muncul karena pembaruan sebagian pada data berulang, dan bentuk 3NF menutup celah itu dengan menyimpan setiap fakta satu kali.
  • Desain analitik membelok setelah 3NF, yaitu menerima redundansi terkendali dalam bentuk star schema atau one big table demi kecepatan pembacaan.
  • Demonstrasi menunjukkan anomali pada tabel gabungan dan pemulihannya setelah data dipisah lalu di-join kembali.

Referensi#