Belajar dengan membaca secara berurutan

Desain Tabel dan Normalisasi (Skema 3 Lapis)

Pakai orders_flat dengan kolom berulang item1/item2 untuk merasakan langsung anomali update, insert, dan delete, lalu pecah tabelnya langkah demi langkah lewat 1NF / 2NF / 3NF menjadi skema 3 lapis customer / order_record / order_line.

Dataset untuk artikel ini — orders_flat (contoh tidak ternormalisasi)

Normalisasi (teknik desain yang memecah tabel untuk mengurangi data yang duplikat dan tidak konsisten) adalah gagasan memecah satu tabel raksasa yang menyimpan segalanya menjadi beberapa tabel yang lebih mudah dijaga konsistensinya.

Di artikel ini kamu akan melihat masalah yang muncul di tabel tidak ternormalisasi dengan kolom berulang, lalu membangun pemahaman tentang 1NF / 2NF / 3NF dan skema 3 lapis lewat latihan desain langsung.

Dataset-nya adalah orders_flat (2 baris), yang menjejalkan data order ke dalam satu tabel.

Ini adalah tabel tidak ternormalisasi klasik di mana nama customer, nomor telepon, produk 1, dan produk 2 semuanya disusun dalam satu baris — dan kamu akan menormalisasinya menjadi tiga tabel customer / order_record / order_line.

Sebelum masuk ke latihan, lihat definisi kolom dan sampel data dari tabel orders_flat.

① Jalankan PRAGMA table_info(orders_flat); untuk memeriksa nama kolom dan tipe. Perhatikan bagaimana kolom produk muncul sebagai kolom berulang seperti item1 / item2.

② Jalankan SELECT * FROM orders_flat; untuk mengintip semua baris. Amati juga bagaimana baris dengan hanya satu produk berakhir dengan string kosong atau NULL di item2.

SQL Editor

Jalankan query untuk melihat hasil

Tiga anomali di tabel tidak ternormalisasi

Desain seperti orders_flat yang menjejalkan segalanya ke satu tabel mudah menghasilkan kontradiksi ketika kamu menambah, mengubah, atau menghapus data.

Contoh klasiknya ada tiga: anomali update / anomali insert / anomali delete.

Anomali update terjadi ketika fakta yang sama diduplikat di banyak baris — misalnya, kalau kamu ingin mengubah nomor telepon, kamu harus mengupdate setiap baris tanpa melewatkan satu pun.

Anomali insert adalah ketika kamu tidak bisa mendaftarkan customer yang belum melakukan order, karena tidak ada baris order untuk menempelkan informasi customer-nya.

Anomali delete adalah ketika menghapus satu order juga menghapus informasi yang ingin kamu simpan, seperti nomor telepon customer.

Ini terjadi karena desainnya tidak menjaga "satu fakta di satu tempat".

Tiga anomali di tabel tidak ternormalisasi
orders_flat(customer + order+ produk dalam satu)Anomali updateAnomali insertAnomali deleteNomor telepon duplikatdi banyak barisupdate jadi tidak cocokTidak bisa mendaftarkan customertanpa orderMenghapus order jugamenghapus info customer
Menjejalkan customer, order, dan produk ke dalam satu tabel menyebabkan kontradiksi atau kehilangan informasi saat update, insert, dan delete. Itulah motivasi untuk memecah tabel lewat normalisasi.
-- Contoh anomali delete: menghapus order 2 juga menghapus info kontak Bob
-- (amati perilaku di tabel sekali pakai flat_demo supaya orders_flat asli tidak tersentuh)
DROP TABLE IF EXISTS flat_demo;
CREATE TABLE flat_demo(
  order_id INTEGER, customer TEXT, customer_tel TEXT, product TEXT
);
INSERT INTO flat_demo VALUES
  (1,'Alice','03-1111','Pen'),
  (2,'Bob','03-2222','Clip');

-- Bob hanya punya satu order. Menghapusnya juga menghilangkan nomor telepon Bob 03-2222
DELETE FROM flat_demo WHERE order_id = 2;
SELECT * FROM flat_demo;
-- -> Info kontak Bob sekarang hilang dari database (anomali delete)

Reproduksi di tabel sekali pakai situasi di mana "Nomor telepon Alice berubah dan kamu ingin mengupdate-nya, tapi nomor telepon yang sama diduplikat di banyak baris." Latihan ini berdiri sendiri — kamu akan membuat tabel khusus flat_anomaly agar mandiri. (Kalau kamu menjalankannya dengan benar, penjelasan akan muncul.)

DROP TABLE IF EXISTS tabel flat_anomaly, lalu CREATE dengan 4 kolom: order_id / customer / customer_tel / product.

② Masukkan 2 baris untuk order Alice (keduanya dengan nomor telepon yang sama 03-1111; produknya Pen dan Note).

③ Jalankan UPDATE yang mengubah nomor telepon Alice menjadi 03-9999 tapi hanya menyentuh satu baris (misalnya dengan kondisi seperti WHERE order_id = 1), lalu jalankan SELECT * FROM flat_anomaly; dan konfirmasi bahwa kedua nomor telepon tidak cocok.

SQL Editor

Jalankan query untuk melihat hasil

1NF sampai 3NF — pecah langkah demi langkah

Tabel diorganisir melalui normalisasi dalam tahapan.

1NF — hilangkan kolom berulang

Buang kolom berulang yang diberi nomor seperti item1 / item2.

Sebelum — kolom berulang item1 / item2

order_idcustomertelitem1item1_qtyitem2item2_qty
1Alice03-1111Pen2Note1
2Bob03-2222Clip5(kosong)NULL

Sesudah — satu baris per produk, dengan primary key komposit `(order_id, product)`

order_id (PK)customertelproduct (PK)qty
1Alice03-1111Pen2
1Alice03-1111Note1
2Bob03-2222Clip5

2NF — pindahkan kolom yang hanya bergantung pada sebagian primary key ke tabel lain

Tabel dari langkah sebelumnya memiliki primary key komposit (order_id, product). Tapi perhatikan: customer dan tel ditentukan oleh `order_id` saja (order_id yang sama selalu punya customer yang sama).

Memindahkan kolom yang bergantung pada hanya sebagian primary key (ketergantungan parsial) ke tabel terpisah adalah 2NF. Kamu memecahnya menjadi header order order_header dan detail order order_line.

Sebelum — customer dan tel diduplikat di setiap baris

order_id (PK)customertelproduct (PK)qty
1Alice03-1111Pen2
1Alice03-1111Note1
2Bob03-2222Clip5

Sesudah — dipecah menjadi 2 tabel

order_header (satu baris per order)

order_id (PK)customertel
1Alice03-1111
2Bob03-2222

order_line (primary key komposit (order_id, product), di mana order_id adalah foreign key ke order_header)

order_id (PK,FK)product (PK)qty
1Pen2
1Note1
2Clip5

3NF — pindahkan kolom yang ditentukan oleh kolom non-kunci ke tabel lain lagi

Melihat order_header dari langkah sebelumnya, tel tidak ditentukan oleh order_id tapi oleh `customer` (Alice yang sama selalu punya 03-1111).

Menghilangkan ketergantungan yang lewat kolom non-kunci lain alih-alih langsung dari primary key (ketergantungan transitif: order_id -> customer -> tel) adalah inti dari 3NF.

Kamu mengekstrak master customer customer dan meninggalkan order_record hanya menyimpan foreign key ke customer.

Sebelum — tel bergantung pada customer

order_id (PK)customertel
1Alice03-1111
2Bob03-2222

Sesudah — dipecah menjadi 3 tabel

customer (master customer)

customer_id (PK)customer_namecustomer_tel
1Alice03-1111
2Bob03-2222

order_record (header order: referensi customer lewat foreign key)

order_id (PK)customer_id (FK)
11
22

order_line (detail order: sama dengan langkah sebelumnya)

order_id (PK,FK)product (PK)qty
1Pen2
1Note1
2Clip5

Setelah dipecah sejauh ini, setiap fakta (nomor telepon customer, hubungan antara order dan customer, kuantitas produk dalam order) ada di tepat satu tempat. Itu menghilangkan anomali update, insert, dan delete.

Struktur tabel sebelum normalisasi vs sesudah 3NF
Sebelum normalisasi1 tabelSesudah 3NF3 tabelorders_flatorder_id / customer / telitem1 / item1_qtyitem2 / item2_qtycustomercustomer_id (PK)customer_namecustomer_telorder_recordorder_id (PK)customer_id (FK)order_lineorder_id (FK), product (PK)qtyPK(order_id, product)pecahFKFK
Tabel orders_flat yang dijejalkan dalam satu dipecah menjadi tiga tabel: master customer, header order, dan detail order. Informasi asli bisa dibangun ulang dengan JOIN lewat foreign key.

Skema 3 lapis dan bentuk akhirnya — customer / order_record / order_line

Skema yang ternormalisasi bisa diorganisir sebagai tiga lapis dengan peran berbeda.

  • Lapis master: entitas yang jarang berubah dan dirujuk oleh yang lain, seperti customer
  • Lapis transaksi: tabel yang mencatat peristiwa bisnis, seperti order_record
  • Lapis detail: tabel yang menyimpan baris-baris rincian dari satu peristiwa, seperti order_line

Dengan mengarahkan sisi referensi ke master lewat foreign key, setiap fakta (seperti nomor telepon customer) berakhir di satu tempat.

Bentuk akhirnya adalah tiga tabel ini:

customer(customer_id primary key, customer_name, customer_tel), order_record(order_id primary key, customer_id -> customer), order_line(order_id -> order_record, product, qty, primary key komposit (order_id, product)).

Nomor telepon ada hanya di satu baris customer, jadi update hanya satu baris; customer tanpa order tetap bisa ditambahkan ke customer (anomali insert terselesaikan); dan menghapus order meninggalkan customer di customer (anomali delete terselesaikan).

Skema 3 lapis — master / transaksi / detail
Lapis mastercustomer(customer_id PK, name, tel)Lapis transaksiorder_record(order_id PK, customer_id FK)Lapis detailorder_line(order_id FK, product, qty, PK(order_id,product))order_record merujukcustomerorder_line merujukorder_recordSatu fakta di satu tempatmemperbaiki 3 anomali
Customer ada di lapis master, order di lapis transaksi, dan detail order di lapis detail, ditautkan bersama dengan foreign key. Setiap fakta berakhir di satu tempat dan ketiga anomali terselesaikan.
-- Manfaat normalisasi (read-only): nomor telepon customer ada hanya di satu baris customer
-- Dalam bentuk orders_flat kamu harus memperbaiki setiap baris order Alice = anomali update
UPDATE customer SET customer_tel = '03-9999'
WHERE customer_name = 'Alice';

-- Perbaiki satu tempat, dan nomor baru mengalir ke setiap order saat JOIN
SELECT o.order_id, c.customer_name, c.customer_tel
FROM order_record o
JOIN customer c ON o.customer_id = c.customer_id
ORDER BY o.order_id;

Bayangkan kebutuhannya: "Normalisasi orders_flat menjadi tiga tabel — customer, order, dan detail order." Rancang skema akhir dengan foreign key yang menautkannya, pindahkan datanya, dan bangun ulang daftar asli dengan JOIN.

① Aktifkan pemeriksaan foreign key, lalu DROP TABLE IF EXISTS dengan urutan order_line -> order_record -> customer (membuang child dulu lebih aman).

CREATE tabel customer (customer_id INTEGER primary key / customer_name TEXT NOT NULL / customer_tel TEXT).

CREATE tabel order_record (order_id INTEGER primary key / customer_id INTEGER NOT NULL, foreign key ke customer).

CREATE tabel order_line (order_id INTEGER, foreign key ke order_record / product TEXT NOT NULL / qty INTEGER NOT NULL / primary key komposit (order_id, product)).

⑤ Masukkan Alice(1, 03-1111) dan Bob(2, 03-2222) ke customer; order 1 -> customer 1 dan order 2 -> customer 2 ke order_record; Pen(order 1, 2) / Note(order 1, 1) / Clip(order 2, 5) ke order_line.

⑥ Join 3 tabel di order_id dan customer_id dan pilih order_id / customer_name / customer_tel / product / qty dengan urutan menaik order_id dan product untuk mengonfirmasi kamu bisa membangun ulang daftar asli.

SQL Editor

Jalankan query untuk melihat hasil

Memakai tabel customer / order_record / order_line dari Latihan 2, konfirmasi satu per satu bahwa anomali update, insert, dan delete yang mengganggu tabel tidak ternormalisasi tidak lagi terjadi. Ini latihan terakhir di artikel.

Anomali update terselesaikan: Ubah nomor telepon Alice menjadi '03-9999'. Konfirmasi bahwa itu adalah UPDATE satu baris pada tabel customer.

Anomali insert terselesaikan: INSERT customer baru Charlie (customer_id 3 / customer_name 'Charlie' / customer_tel '03-3333') ke customer. Konfirmasi kamu bisa mendaftarkan customer tanpa order.

Anomali delete terselesaikan: Hapus order 1. Hapus dari child order_line dulu, lalu dari order_record. Jalankan SELECT * FROM customer ORDER BY customer_id; dan konfirmasi baris Alice masih ada di customer.

SQL Editor

Jalankan query untuk melihat hasil

Tips — Selalu pasang primary key di setiap tabel

Baik sudah dinormalisasi atau belum, rancang setiap tabel dengan primary key (`PRIMARY KEY`). Primary key memberi kamu tiga hal:

- Identifikasi baris unik: tidak ada dua baris yang bisa memiliki kunci yang sama, jadi UPDATE / DELETE dengan andal menargetkan tepat satu baris lewat WHERE pk = ...

- Bisa dirujuk dari tabel lain: foreign key membutuhkan primary key (atau UNIQUE) di sisi lain. Ini fondasi untuk menautkan tabel yang kamu pecah lewat normalisasi

- Pengindeksan otomatis: kolom primary key mendapat indeks B-Tree secara otomatis, mempercepat pencarian kesetaraan dan JOIN

Kalau tidak ada satu kolom yang unik sendirian, pakai primary key komposit seperti PRIMARY KEY (order_id, product). Melewatkannya dengan "saya akan menambahkannya nanti" membuat baris duplikat tidak bisa dibedakan dan menyerahkan ketiga manfaat di atas.

QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Nomor telepon customer diduplikat di banyak baris order, jadi mengubah nomor telepon memerlukan update setiap baris tanpa terlewat — kalau tidak nilainya tidak akan cocok. Apa nama masalah ini?

Soal 2Operasi mana yang membawa tabel ke 1NF?

Soal 3Setelah normalisasi, orders_flat dipecah menjadi tiga tabel customer / order_record / order_line. Bagaimana kamu mendapatkan kembali daftar gabungan asli?