πŸ“Š Data Modelling
πŸŽ“ Pertemuan
Pertemuan 9: Physical Data Model & Implementasi Skema Database

MODUL PERTEMUAN 9

PHYSICAL DATA MODEL, SQL DML, DAN SQL VIEWS


A. INFORMASI UMUM MATA KULIAH

ItemKeterangan
Mata KuliahData Modelling
Kode MKSSD1019
Bobot3 SKS (Praktikum)
Semester4 (Empat)
Program StudiSains Data
FakultasEkonomi dan Bisnis Islam
UniversitasUIN K.H. Abdurrahman Wahid Pekalongan
Dosen PengampuMohammad Reza Maulana, M.Kom

B. INFORMASI PERTEMUAN

B.1 Sub-CPMK Pertemuan 9

Sub-CPMK 6.1: Mahasiswa mampu menerapkan teknik pemodelan data fisik menggunakan alat bantu (tools) sesuai standar industri, termasuk menentukan tipe data yang tepat, mendefinisikan constraint, merancang strategi pengindeksan, menghasilkan SQL DDL script yang dapat dieksekusi, menulis SQL DML (SELECT dengan JOIN, INSERT, UPDATE, DELETE) terhadap skema yang dibuat, serta mendefinisikan SQL Views sebagai bagian dari physical model yang mendukung pipeline analitik.

B.2 Tujuan Pembelajaran (Learning Objectives)

Setelah mengikuti pertemuan ini, mahasiswa akan mampu:

  1. Menjelaskan perbedaan antara logical data model dan physical data model beserta komponen tambahan yang muncul di level fisik (C2 - Memahami)
  2. Memilih tipe data MySQL yang paling tepat untuk setiap atribut berdasarkan karakteristik data dan kebutuhan bisnis (C3 - Mengaplikasikan)
  3. Mendefinisikan semua jenis constraint (PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK, DEFAULT) dalam sintaks DDL MySQL yang benar (C3 - Mengaplikasikan)
  4. Menganalisis kebutuhan pengindeksan berdasarkan pola query yang diantisipasi dan menentukan strategi index MySQL yang tepat (C4 - Menganalisis)
  5. Menulis SQL DDL script MySQL lengkap yang dapat dieksekusi dari logical model yang diberikan (C3 - Mengaplikasikan)
  6. Menggunakan tools data modelling (MySQL Workbench dan Draw.io) untuk menghasilkan physical data model dan DDL secara otomatis (C3 - Mengaplikasikan)
  7. Menulis SQL DML (SELECT, INSERT, UPDATE, DELETE dengan JOIN dan filter) untuk berinteraksi dengan skema database yang telah dibuat (C3 - Mengaplikasikan)
  8. Mendefinisikan SQL Views sebagai lapisan abstraksi dan komponen physical model yang mendukung query analitik dan keamanan data (C3 - Mengaplikasikan)
  9. Menjelaskan konsep stored procedure, trigger, dan transaction (ACID) serta kapan masing-masing digunakan dalam konteks physical model (C2 - Memahami)

B.3 Kompetensi yang Dikembangkan

DomainKompetensi
KognitifMemahami tiga level model (C2), Memilih tipe data tepat (C3), Menganalisis strategi indeks (C4), Menganalisis kebutuhan DML query (C4), Memahami SP/Trigger/ACID (C2)
AfektifMengembangkan ketelitian dalam penulisan DDL/DML; menghargai naming convention sebagai standar profesional; membangun kesadaran bahwa data dirancang untuk DIGUNAKAN bukan hanya disimpan
PsikomotorikMenggunakan tools data modelling secara hands-on; menulis dan mengeksekusi DDL + DML script di DBMS nyata; membuat SQL View yang dapat digunakan pipeline analitik

B.4 Indikator Pencapaian

Setelah mengikuti pertemuan ini, mahasiswa diharapkan mampu:

  1. Mengidentifikasi minimal 8 perbedaan antara logical model dan physical model
  2. Menentukan tipe data MySQL yang tepat untuk 15 skenario atribut yang berbeda
  3. Menulis DDL script CREATE TABLE MySQL yang benar dan lengkap untuk minimal 4 tabel terhubung dengan semua constraint
  4. Mengeksekusi DDL script MySQL tanpa error
  5. Menghasilkan DDL script menggunakan MySQL Workbench atau Draw.io
  6. Menulis minimal 5 query SELECT dengan JOIN (INNER, LEFT) dan filter WHERE/GROUP BY/ORDER BY terhadap skema yang dibuat
  7. Menulis query INSERT, UPDATE, DELETE dengan kondisi yang tepat dan aman
  8. Membuat minimal 2 SQL View: satu view untuk laporan customer dan satu view analitik (ringkasan agregat)
  9. Menjelaskan perbedaan antara stored procedure dan trigger, serta memberikan contoh kapan masing-masing digunakan
  10. Menjelaskan keempat properti ACID dan memberikan contoh skenario yang membutuhkan transaction

B.5 Alokasi Waktu

NoKegiatanDurasiKeterangan
1Pembukaan & Jembatan Pasca-UTS5 menitKoneksi ke proyek kelompok
2Aktivitas Pemantik: "Logical ke Physical: Apa yang Berubah?"10 menitAnalisis perbandingan dua dokumen
3Materi 1: Logical vs Physical Data Model15 menitCeramah + tabel perbandingan
4Materi 2: Tipe Data DBMS - Panduan Pemilihan20 menitCeramah + latihan klasifikasi
5Materi 3: Constraint dan Integritas Data15 menitCeramah + contoh DDL
6Materi 4: Strategi Pengindeksan10 menitCeramah + analisis kasus
7Materi 5: Naming Convention Wajib Semester10 menitPenetapan standar snake_case
8Break10 menit-
9Materi 6: SQL DML - Menggunakan Skema yang Dibuat20 menitSELECT JOIN, INSERT, UPDATE, DELETE
10Materi 7: SQL Views dalam Physical Model15 menitCREATE VIEW + use cases
11Demo Tools + Praktikum Terbimbing (DDL + DML)20 menitDemo MySQL Workbench + hands-on
12Review + Bridging ke P10 + Tugas10 menitDiskusi, penjelasan tugas
Total160 menit

C. MATERI PEMBELAJARAN

C.1 Aktivitas Pemantik β€” "Logical ke Physical: Apa yang Berubah?"

Instruksi (10 menit): Dosen menampilkan dua dokumen berdampingan β€” skema logical dan DDL hasil implementasinya. Mahasiswa mengamati dan menjawab pertanyaan.

Dokumen 1 β€” Logical Model:

PELANGGAN(id_pelanggan, nama, email, tgl_lahir, alamat_kota)
PRODUK(id_produk, nama_produk, harga, stok, id_kategori*)
KATEGORI(id_kategori, nama_kategori)
PESANAN(id_pesanan, tgl_pesanan, status, id_pelanggan*)
ITEM_PESANAN(id_pesanan*, id_produk*, qty, harga_saat_pesan)

Dokumen 2 β€” Physical Model (DDL):

CREATE TABLE kategori (
    id_kategori   INT UNSIGNED AUTO_INCREMENT,
    nama_kategori VARCHAR(100) NOT NULL,
    CONSTRAINT pk_kategori PRIMARY KEY (id_kategori)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
CREATE TABLE pelanggan (
    id_pelanggan  INT UNSIGNED AUTO_INCREMENT,
    nama          VARCHAR(150) NOT NULL,
    email         VARCHAR(255) NOT NULL,
    tgl_lahir     DATE,
    alamat_kota   VARCHAR(100),
    created_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT pk_pelanggan       PRIMARY KEY (id_pelanggan),
    CONSTRAINT uq_pelanggan_email UNIQUE (email),
    INDEX idx_pelanggan_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
CREATE TABLE produk (
    id_produk     INT UNSIGNED AUTO_INCREMENT,
    nama_produk   VARCHAR(200) NOT NULL,
    harga         DECIMAL(15,2) NOT NULL CHECK (harga >= 0),
    stok          INT UNSIGNED NOT NULL DEFAULT 0,
    id_kategori   INT UNSIGNED NOT NULL,
    CONSTRAINT pk_produk         PRIMARY KEY (id_produk),
    CONSTRAINT fk_produk_kategori FOREIGN KEY (id_kategori)
        REFERENCES kategori (id_kategori)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    INDEX idx_produk_kategori (id_kategori)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
CREATE TABLE pesanan (
    id_pesanan    INT UNSIGNED AUTO_INCREMENT,
    tgl_pesanan   DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    status        ENUM('pending','diproses','dikirim','selesai','dibatalkan')
                  NOT NULL DEFAULT 'pending',
    id_pelanggan  INT UNSIGNED NOT NULL,
    CONSTRAINT pk_pesanan          PRIMARY KEY (id_pesanan),
    CONSTRAINT fk_pesanan_pelanggan FOREIGN KEY (id_pelanggan)
        REFERENCES pelanggan (id_pelanggan)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    INDEX idx_pesanan_pelanggan (id_pelanggan),
    INDEX idx_pesanan_status    (status),
    INDEX idx_pesanan_tgl       (tgl_pesanan)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
CREATE TABLE item_pesanan (
    id_pesanan       INT UNSIGNED NOT NULL,
    id_produk        INT UNSIGNED NOT NULL,
    qty              SMALLINT UNSIGNED NOT NULL CHECK (qty > 0),
    harga_saat_pesan DECIMAL(15,2) NOT NULL CHECK (harga_saat_pesan >= 0),
    CONSTRAINT pk_item_pesanan  PRIMARY KEY (id_pesanan, id_produk),
    CONSTRAINT fk_item_pesanan  FOREIGN KEY (id_pesanan)
        REFERENCES pesanan (id_pesanan)
        ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT fk_detail_produk   FOREIGN KEY (id_produk)
        REFERENCES produk (id_produk)
        ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Pertanyaan Pemantik:

  1. Hitung perbedaan! Sebutkan minimal 6 elemen di DDL yang TIDAK ADA di logical model!
  2. Mengapa harga menggunakan DECIMAL(15,2) bukan FLOAT?
  3. Mengapa tgl_pesanan menggunakan DATETIME bukan DATE?
  4. Apa fungsi ENGINE=InnoDB? Ini keputusan logikal atau fisik?
  5. Mengapa ada INDEX idx_pesanan_tgl? Apa yang dipercepat?
  6. Mengapa tabel dibuat dengan urutan kategori β†’ pelanggan β†’ produk β†’ pesanan β†’ item_pesanan, bukan sebaliknya?

Rangkuman Dosen: "Physical model adalah terjemahan akhir dari desain kita ke bahasa mesin. Di sinilah semua keputusan abstrak bertemu kenyataan teknis: ukuran storage, kecepatan query, batasan DBMS. Pertemuan ini mengisi jembatan antara skema di kertas dengan database yang benar-benar bisa dijalankan."


C.2 Materi 1: Logical vs Physical Data Model

C.2.1 Tiga Level Model β€” Perspektif Implementasi

CONCEPTUAL (Pertemuan 3–4)
  Pertanyaan: "APA yang perlu disimpan?"
  Artefak   : ERD (Chen / Crow's Foot)
  Isi       : Entity, Relationship, Attribute (tanpa detail teknis)
  Independen dari semua DBMS
  Audiens   : Business analyst, stakeholder

         ↓ Transformasi (Pertemuan 5)

LOGICAL (Pertemuan 5–7)
  Pertanyaan: "BAGAIMANA strukturnya?"
  Artefak   : Skema Relasional (Tabel, PK, FK)
  Isi       : Tabel, kolom, PK, FK, normal form
  Independen dari DBMS spesifik (belum ada VARCHAR, INT, ENGINE)
  Audiens   : Data modeller, database architect

         ↓ Implementasi (Pertemuan 9 ← SEKARANG)

PHYSICAL (Pertemuan 9)
  Pertanyaan: "BAGAIMANA diimplementasikan di DBMS ini?"
  Artefak   : DDL Script (CREATE TABLE, CREATE INDEX, ALTER TABLE)
  Isi       : Tipe data spesifik, constraint, index, storage engine,
              character set, naming convention, audit columns
  Spesifik untuk MySQL (DBMS yang kami gunakan di mata kuliah ini)
  Audiens   : DBA, backend developer, data engineer

C.2.2 Komponen Tambahan di Level Physical

Komponen LogicalYang Ditambahkan di Physical
Nama tabelNaming convention + ENGINE + CHARSET
Kolom dengan "tipe konseptual"Tipe data spesifik DBMS: VARCHAR(n), DECIMAL(p,s), ENUM(...)
Primary KeyAUTO_INCREMENT / SERIAL, ukuran integer
Foreign KeyON DELETE / ON UPDATE action konkret
Constraint bisnisCHECK, DEFAULT, UNIQUE dalam sintaks DDL
(tidak ada di logical)INDEX dan strategi pengindeksan
(tidak ada di logical)ENGINE, CHARSET, COLLATION
(tidak ada di logical)Audit columns: created_at, updated_at

C.2.3 Urutan Eksekusi DDL β€” Mengapa Urutan Penting

PRINSIP: FOREIGN KEY hanya bisa dibuat jika tabel yang DIREFERENSI sudah ada!

CONTOH KESALAHAN (akan ERROR):
  CREATE TABLE pesanan (
      id_pelanggan INT,
      FOREIGN KEY (id_pelanggan) REFERENCES pelanggan(id_pelanggan) -- ERROR!
      -- karena tabel pelanggan belum ada!
  );
  CREATE TABLE pelanggan (...); -- dibuat setelah pesanan

ATURAN URUTAN DDL:
  1. Buat tabel yang TIDAK memiliki FK terlebih dahulu (tabel "induk")
  2. Baru buat tabel yang memiliki FK (tabel "anak")
  3. Untuk referensi melingkar: gunakan ALTER TABLE ADD CONSTRAINT setelah semua tabel dibuat

CARA MENENTUKAN URUTAN β€” Topological Sort:
  Gambar dependency graph (panah = FK):
  
  kategori ←── produk ←── item_pesanan
  pelanggan ←── pesanan ←── item_pesanan
  
  Tabel tanpa panah masuk (in-degree = 0) β†’ dibuat pertama:
  1. kategori   (tidak punya FK)
  2. pelanggan  (tidak punya FK)
  3. produk     (FK ke kategori βœ“)
  4. pesanan    (FK ke pelanggan βœ“)
  5. item_pesanan (FK ke pesanan + produk βœ“)

C.3 Materi 2: Tipe Data DBMS β€” Panduan Pemilihan

Memilih tipe data yang tepat adalah keputusan paling berdampak dalam physical modelling. Pilihan yang salah menyebabkan: data terpotong, perhitungan tidak akurat, performa buruk, atau storage boros.

C.3.1 Tipe Numerik

TIPE NUMERIK MYSQL:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Tipe         β”‚ Rentang / Presisi        β”‚ Kapan Digunakan                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ TINYINT      β”‚ -128 s.d. 127           β”‚ Status/flag, usia, boolean (0/1)   β”‚
β”‚              β”‚ UNSIGNED: 0–255         β”‚                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ SMALLINT     β”‚ -32.768 s.d. 32.767     β”‚ Tahun, kode pos, qty kecil         β”‚
β”‚              β”‚ UNSIGNED: 0–65.535      β”‚                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ INT          β”‚ Β±2,1 miliar             β”‚ ID/surrogate key, quantity,        β”‚
β”‚ (INTEGER)    β”‚ UNSIGNED: 0–4,2 miliar  β”‚ counter β€” PILIHAN UMUM             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ BIGINT       β”‚ Β±9,2 Γ— 10^18            β”‚ ID untuk sistem besar (>2 miliar   β”‚
β”‚              β”‚                         β”‚ baris), nomor KTP, Unix timestamp  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ DECIMAL(p,s) β”‚ Presisi EXACT           β”‚ UANG, harga, gaji, persentase      β”‚
β”‚ (NUMERIC)    β”‚ p = total digit         β”‚ JANGAN GUNAKAN FLOAT untuk uang!   β”‚
β”‚              β”‚ s = digit desimal       β”‚                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ FLOAT        β”‚ ~7 digit presisi        β”‚ Nilai ilmiah, koordinat kasar,     β”‚
β”‚ DOUBLE       β”‚ ~15 digit presisi       β”‚ ML features β€” toleran thd rounding β”‚
β”‚              β”‚ (approx β€” TIDAK exact!) β”‚ JANGAN untuk keuangan!             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MENGAPA FLOAT BERBAHAYA UNTUK UANG?
  Coba di MySQL:
    SELECT 0.1 + 0.2;
    β†’ Hasil: 0.30000000000000004  ← BUKAN 0.3!

  Ini karena representasi biner floating-point tidak bisa
  merepresentasikan semua angka desimal secara exact.

  Untuk transaksi keuangan β†’ DECIMAL(15, 2) SELALU βœ“
  Untuk nilai sains/ML    β†’ FLOAT atau DOUBLE OK βœ“

PILIHAN TIPE NUMERIK YANG UMUM DI INDUSTRI:
  ID/PK          : INT UNSIGNED AUTO_INCREMENT
  Sistem besar   : BIGINT UNSIGNED AUTO_INCREMENT
  Uang/harga     : DECIMAL(15, 2)   ← 13 digit sebelum koma, 2 sesudah
  Persentase     : DECIMAL(5, 2)    ← contoh: 99.99%
  Stok/quantity  : INT UNSIGNED atau SMALLINT UNSIGNED
  Rating bintang : TINYINT UNSIGNED CHECK (rating BETWEEN 1 AND 5)
  Koordinat GPS  : DECIMAL(10, 7)   ← presisi ~1cm

C.3.2 Tipe Karakter dan Teks

TIPE STRING UTAMA:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Tipe         β”‚ Karakteristik                 β”‚ Kapan Digunakan              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ CHAR(n)      β”‚ Panjang TETAP n karakter      β”‚ Kode yang selalu sama        β”‚
β”‚              β”‚ Diisi spasi jika kurang       β”‚ panjangnya: NIM(12),         β”‚
β”‚              β”‚ Sedikit lebih cepat untuk     β”‚ kode_mk(8), kode_negara(2),  β”‚
β”‚              β”‚ fixed-length data             β”‚ password hash (60/64 char)   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ VARCHAR(n)   β”‚ Panjang VARIABEL, maks n      β”‚ Nama, email, alamat,         β”‚
β”‚              β”‚ Hanya simpan karakter + 1-2   β”‚ deskripsi pendek, URL        β”‚
β”‚              β”‚ byte overhead                 β”‚ PILIHAN UMUM                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ TEXT         β”‚ Hingga 65.535 karakter        β”‚ Konten artikel, catatan,     β”‚
β”‚              β”‚ Tidak bisa jadi DEFAULT       β”‚ bio panjang                  β”‚
β”‚              β”‚ Tidak bisa diindex penuh      β”‚                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ MEDIUMTEXT   β”‚ Hingga 16 MB                  β”‚ Konten blog panjang          β”‚
β”‚ LONGTEXT     β”‚ Hingga 4 GB                   β”‚ Dokumen, log besar           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ENUM(...)    β”‚ Daftar nilai yang ditentukan  β”‚ Status dengan pilihan        β”‚
β”‚              β”‚ Disimpan sebagai integer      β”‚ terbatas yang stabil:        β”‚
β”‚              β”‚ Lebih hemat storage           β”‚ status, jenis, kategori enum β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ JSON         β”‚ Validasi JSON otomatis        β”‚ Data semi-struktur,          β”‚
β”‚              β”‚ Bisa query field-level        β”‚ atribut yang bervariasi      β”‚
β”‚              β”‚ MySQL 5.7+                     β”‚ Gunakan dengan bijak!        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

PANDUAN UKURAN VARCHAR:
  Nama manusia          : VARCHAR(150)   ← nama + gelar
  Email                 : VARCHAR(255)   ← standar RFC 5321
  URL / link            : VARCHAR(2048)  ← URL bisa sangat panjang
  Deskripsi singkat     : VARCHAR(500)
  Slug (URL-friendly)   : VARCHAR(200)
  Username              : VARCHAR(50)
  Password (hashed)     : CHAR(60) atau CHAR(64) ← sesuai algoritma hash

CHARACTER SET DAN COLLATION (PENTING untuk bahasa Indonesia!):
  MASALAH: Karakter Γ©, Γ±, dan emoji bisa rusak jika charset salah!

  REKOMENDASI:
  charset  : utf8mb4  ← mendukung semua Unicode + emoji
  collation: utf8mb4_unicode_ci  ← case-insensitive

  JANGAN:
  charset  : latin1  ← tidak support huruf non-Latin
  charset  : utf8    ← di MySQL, utf8 hanya 3 byte, emoji tidak support!

  Cara set default di level database:
  CREATE DATABASE nama_db
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

C.3.3 Tipe Tanggal dan Waktu

TIPE DATE/TIME:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Tipe          β”‚ Format / Rentang             β”‚ Kapan Digunakan              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ DATE          β”‚ 'YYYY-MM-DD'                 β”‚ Tanggal lahir, tgl kadaluarsaβ”‚
β”‚               β”‚ 1000-01-01 s.d. 9999-12-31  β”‚ tgl_bergabung β€” tanpa jam    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ TIME          β”‚ 'HH:MM:SS'                   β”‚ Jam buka/tutup, jadwal harianβ”‚
β”‚               β”‚ -838:59:59 s.d. 838:59:59   β”‚ durasi kegiatan              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ DATETIME      β”‚ 'YYYY-MM-DD HH:MM:SS'        β”‚ Waktu transaksi, log event,  β”‚
β”‚               β”‚ 1000 s.d. 9999              β”‚ jadwal spesifik β€” TIDAK      β”‚
β”‚               β”‚ Tidak timezone-aware         β”‚ dipengaruhi timezone server  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ TIMESTAMP     β”‚ 'YYYY-MM-DD HH:MM:SS'        β”‚ created_at, updated_at β€”     β”‚
β”‚               β”‚ 1970-01-01 s.d. 2038-01-19  β”‚ dikonversi UTC, timezone-awareβ”‚
β”‚               β”‚ ← ATTENTION: Y2038 Problem! β”‚ Otomatis update saat INSERT  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ YEAR          β”‚ 1901 s.d. 2155               β”‚ Tahun terbit buku,           β”‚
β”‚               β”‚                             β”‚ tahun akademik, tahun masuk  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

DATETIME vs TIMESTAMP β€” Kapan Mana?

  DATETIME  β†’ Waktu yang "absolut", tidak boleh bergeser karena timezone
              Contoh: "Pertemuan 10 Januari pukul 09:00 WIB"
              β†’ Disimpan dan ditampilkan apa adanya, tidak dikonversi

  TIMESTAMP β†’ Waktu yang "global", perlu sinkroni antar timezone
              Contoh: created_at (kapan baris dibuat)
              β†’ Disimpan sebagai UTC, otomatis konversi ke timezone client

  REKOMENDASI PRAKTIS:
  created_at       : TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  updated_at       : TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                     ON UPDATE CURRENT_TIMESTAMP
  tgl_lahir        : DATE
  tgl_transaksi    : DATETIME  (waktu spesifik tidak boleh bergeser)
  tgl_jatuh_tempo  : DATE

  Y2038 PROBLEM:
  TIMESTAMP hanya sampai 19 Januari 2038 02:14:07 UTC.
  Untuk sistem yang diharapkan hidup melewati 2038:
  β†’ Gunakan DATETIME atau BIGINT (Unix timestamp milidetik)

C.3.4 Tipe-Tipe Lainnya

BOOLEAN (Flag):
  MySQL tidak punya tipe BOOLEAN sejati.
  BOOLEAN = alias untuk TINYINT(1): 0 = FALSE, 1 = TRUE

  Gunakan:
    is_aktif    TINYINT(1) NOT NULL DEFAULT 1
    is_verified TINYINT(1) NOT NULL DEFAULT 0

  MySQL: TINYINT(1) atau BOOL (BOOL adalah alias untuk TINYINT(1)) βœ“

BLOB (Binary Large Object):
  BLOB       : hingga 65 KB   β€” gambar kecil, file kecil
  MEDIUMBLOB : hingga 16 MB   β€” gambar, PDF kecil
  LONGBLOB   : hingga 4 GB    β€” video, file besar

  PERINGATAN:
  Menyimpan file binary di database jarang direkomendasikan!
  Praktik industri: Simpan FILE di object storage (S3, GCS, MinIO),
                    simpan URL-nya di database sebagai VARCHAR.

UUID (Universally Unique Identifier):
  Alternatif untuk AUTO_INCREMENT
  Format : '550e8400-e29b-41d4-a716-446655440000'
  MySQL  : UUID() function, simpan sebagai CHAR(36) atau BINARY(16)
  PgSQL  : UUID type native + gen_random_uuid()

  Keuntungan : Tidak sequential β†’ lebih aman, cocok distributed system
  Kerugian   : Lebih besar dari INT, sedikit lebih lambat untuk JOIN

C.3.5 Pohon Keputusan Pemilihan Tipe Data

DECISION TREE:

Apakah nilainya angka?
β”œβ”€β”€ YA β†’ Apakah perlu angka desimal?
β”‚        β”œβ”€β”€ YA β†’ Apakah nilai keuangan? β†’ YA β†’ DECIMAL(15,2) ← SELALU!
β”‚        β”‚                               β†’ TIDAK β†’ DECIMAL(p,s) atau FLOAT
β”‚        └── TIDAK β†’ Seberapa besar nilainya?
β”‚                    β”œβ”€β”€ 0–255 (flag, rating) β†’ TINYINT UNSIGNED
β”‚                    β”œβ”€β”€ 0–65.535 (qty kecil) β†’ SMALLINT UNSIGNED
β”‚                    β”œβ”€β”€ 0–4,2 miliar (ID umum) β†’ INT UNSIGNED
β”‚                    └── Lebih besar β†’ BIGINT UNSIGNED
β”‚
└── TIDAK β†’ Berkaitan dengan waktu?
            β”œβ”€β”€ YA β†’ Hanya tanggal? β†’ YA β†’ DATE
            β”‚                       β†’ TIDAK β†’ Perlu timezone-aware?
            β”‚                                 β”œβ”€β”€ YA β†’ TIMESTAMP
            β”‚                                 └── TIDAK β†’ DATETIME
            └── TIDAK β†’ Panjang selalu sama (kode)?
                        β”œβ”€β”€ YA β†’ CHAR(n)
                        └── TIDAK β†’ Teks sangat panjang (>500 char)?
                                    β”œβ”€β”€ YA β†’ TEXT / MEDIUMTEXT
                                    └── TIDAK β†’ VARCHAR(n)

C.4 Materi 3: Constraint dan Integritas Data

Constraint adalah aturan yang di-enforce DBMS secara otomatis. Inilah "penjaga" yang memastikan database tidak menerima data tidak valid.

C.4.1 PRIMARY KEY CONSTRAINT

-- Cara 1: Inline (untuk single-column PK)
CREATE TABLE produk (
    id_produk INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ...
);
 
-- Cara 2: Table-level dengan nama eksplisit (DIREKOMENDASIKAN)
-- Terutama untuk PK komposit (weak entity, junction table)
CREATE TABLE item_pesanan (
    id_pesanan INT UNSIGNED NOT NULL,
    id_produk  INT UNSIGNED NOT NULL,
    qty        SMALLINT UNSIGNED NOT NULL,
    CONSTRAINT pk_item_pesanan PRIMARY KEY (id_pesanan, id_produk)
);
 
-- AUTO_INCREMENT (MySQL)
-- MySQL     : id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY

C.4.2 FOREIGN KEY CONSTRAINT dan ON DELETE Action

-- Sintaks lengkap:
CONSTRAINT fk_[tabel_anak]_[tabel_induk]
    FOREIGN KEY (kolom_fk)
    REFERENCES tabel_induk (kolom_pk)
    ON DELETE [CASCADE | SET NULL | RESTRICT | NO ACTION]
    ON UPDATE [CASCADE | RESTRICT]
 
-- Panduan memilih ON DELETE action:
-- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
-- β”‚ Action          β”‚ Kapan Digunakan                                β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ CASCADE         β”‚ Tabel anak tidak bermakna tanpa induk           β”‚
-- β”‚                 β”‚ Contoh: item_pesanan saat pesanan dihapus       β”‚
-- β”‚                 β”‚ Contoh: resep_item saat kunjungan dihapus       β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ SET NULL        β”‚ Anak boleh ada meski tanpa induk (opsional)     β”‚
-- β”‚                 β”‚ Contoh: produk.id_kategori β†’ SET NULL jika      β”‚
-- β”‚                 β”‚ kategori dihapus (produk tetap ada tanpa kat.)  β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ RESTRICT        β”‚ Tolak penghapusan induk jika masih ada anak     β”‚
-- β”‚ (NO ACTION)     β”‚ Contoh: hapus pelanggan yang masih punya order  β”‚
-- β”‚                 β”‚ β†’ ERROR! Harus hapus semua order-nya dulu       β”‚
-- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 
-- Contoh penggunaan:
CONSTRAINT fk_item_pesanan FOREIGN KEY (id_pesanan)
    REFERENCES pesanan (id_pesanan)
    ON DELETE CASCADE  -- hapus pesanan β†’ item ikut terhapus otomatis
    ON UPDATE CASCADE  -- update id_pesanan β†’ FK ikut update
 
CONSTRAINT fk_pesanan_pelanggan FOREIGN KEY (id_pelanggan)
    REFERENCES pelanggan (id_pelanggan)
    ON DELETE RESTRICT  -- tidak boleh hapus pelanggan yg punya riwayat pesanan
    ON UPDATE CASCADE

C.4.3 UNIQUE, NOT NULL, CHECK, DEFAULT

-- UNIQUE CONSTRAINT
-- Memastikan nilai selalu unik; boleh NULL (tapi hanya satu NULL per kolom UNIQUE)
CONSTRAINT uq_pelanggan_email UNIQUE (email)
 
-- Composite UNIQUE (kombinasi harus unik):
CONSTRAINT uq_karyawan_periode UNIQUE (id_karyawan, tahun, bulan)
 
-- NOT NULL β€” kolom tidak boleh kosong
nama    VARCHAR(150) NOT NULL  -- mandatory
no_telp VARCHAR(20)           -- boleh kosong (NULL) = opsional
 
-- Kapan NOT NULL?
-- β†’ Semua FK untuk total participation (wajib punya induk)
-- β†’ Semua atribut yang mandatory secara bisnis
-- β†’ Semua PK (otomatis NOT NULL)
 
-- CHECK CONSTRAINT (MySQL 8.0.16+)
harga         DECIMAL(15,2) NOT NULL CHECK (harga >= 0)
rating        TINYINT UNSIGNED NOT NULL CHECK (rating BETWEEN 1 AND 5)
persentase    DECIMAL(5,2) CHECK (persentase >= 0 AND persentase <= 100)
 
-- Cross-column CHECK:
CONSTRAINT ck_tgl_selesai CHECK (tgl_selesai >= tgl_mulai)
 
-- DEFAULT CONSTRAINT β€” nilai otomatis jika tidak diisi
status      ENUM('aktif','nonaktif') NOT NULL DEFAULT 'aktif'
stok        INT UNSIGNED NOT NULL DEFAULT 0
tgl_daftar  DATE NOT NULL DEFAULT (CURRENT_DATE)
created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updated_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
is_aktif    TINYINT(1) NOT NULL DEFAULT 1

C.4.4 Contoh DDL Lengkap dengan Semua Constraint

-- ============================================================
-- SISTEM PERPUSTAKAAN KAMPUS β€” DDL Script
-- DBMS: MySQL 8.0+ | Dibuat: Februari 2026
-- ============================================================
 
CREATE DATABASE IF NOT EXISTS perpustakaan
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;
 
USE perpustakaan;
 
-- ──────────────────────────────────────────────────────────────
-- Tabel 1: KATEGORI_BUKU (tidak punya FK β€” dibuat pertama)
-- ──────────────────────────────────────────────────────────────
CREATE TABLE kategori_buku (
    id_kategori   INT UNSIGNED     NOT NULL AUTO_INCREMENT,
    nama_kategori VARCHAR(100)     NOT NULL,
    deskripsi     TEXT,
    created_at    TIMESTAMP        NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
    CONSTRAINT pk_kategori_buku PRIMARY KEY (id_kategori),
    CONSTRAINT uq_kategori_nama UNIQUE (nama_kategori)
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='Master data kategori/genre buku';
 
-- ──────────────────────────────────────────────────────────────
-- Tabel 2: BUKU (FK ke kategori_buku)
-- ──────────────────────────────────────────────────────────────
CREATE TABLE buku (
    id_buku        INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    isbn           CHAR(17)        NOT NULL,  -- Format: 978-X-XXXXX-XXX-X
    judul          VARCHAR(300)    NOT NULL,
    penulis        VARCHAR(200)    NOT NULL,
    penerbit       VARCHAR(150),
    tahun_terbit   YEAR            NOT NULL,
    jumlah_halaman SMALLINT UNSIGNED,
    stok_total     TINYINT UNSIGNED NOT NULL DEFAULT 1
                   CHECK (stok_total >= 1),
    id_kategori    INT UNSIGNED    NOT NULL,
 
    CONSTRAINT pk_buku            PRIMARY KEY (id_buku),
    CONSTRAINT uq_buku_isbn       UNIQUE (isbn),
    CONSTRAINT fk_buku_kategori   FOREIGN KEY (id_kategori)
        REFERENCES kategori_buku (id_kategori)
        ON DELETE RESTRICT ON UPDATE CASCADE,
 
    INDEX idx_buku_judul    (judul),
    INDEX idx_buku_penulis  (penulis),
    INDEX idx_buku_kategori (id_kategori)
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='Master data koleksi buku perpustakaan';
 
-- ──────────────────────────────────────────────────────────────
-- Tabel 3: ANGGOTA (tidak punya FK)
-- ──────────────────────────────────────────────────────────────
CREATE TABLE anggota (
    id_anggota     INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    no_anggota     CHAR(10)        NOT NULL,   -- Format: A-XXXXXXXXX
    nama           VARCHAR(150)    NOT NULL,
    email          VARCHAR(255)    NOT NULL,
    no_telepon     VARCHAR(20),
    tgl_lahir      DATE,
    jenis_anggota  ENUM('mahasiswa','dosen','staf','umum')
                   NOT NULL DEFAULT 'mahasiswa',
    tgl_bergabung  DATE            NOT NULL DEFAULT (CURRENT_DATE),
    tgl_kadaluarsa DATE            NOT NULL,
    is_aktif       TINYINT(1)      NOT NULL DEFAULT 1,
    created_at     TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at     TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
                   ON UPDATE CURRENT_TIMESTAMP,
 
    CONSTRAINT pk_anggota         PRIMARY KEY (id_anggota),
    CONSTRAINT uq_anggota_no      UNIQUE (no_anggota),
    CONSTRAINT uq_anggota_email   UNIQUE (email),
    CONSTRAINT ck_anggota_exp     CHECK (tgl_kadaluarsa > tgl_bergabung),
 
    INDEX idx_anggota_email    (email),
    INDEX idx_anggota_jenis    (jenis_anggota),
    INDEX idx_anggota_is_aktif (is_aktif)
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='Data anggota perpustakaan';
 
-- ──────────────────────────────────────────────────────────────
-- Tabel 4: PEMINJAMAN (FK ke anggota + buku)
-- ──────────────────────────────────────────────────────────────
CREATE TABLE peminjaman (
    id_pinjam            INT UNSIGNED    NOT NULL AUTO_INCREMENT,
    id_anggota           INT UNSIGNED    NOT NULL,
    id_buku              INT UNSIGNED    NOT NULL,
    tgl_pinjam           DATE            NOT NULL DEFAULT (CURRENT_DATE),
    tgl_rencana_kembali  DATE            NOT NULL,
    tgl_kembali_aktual   DATE,           -- NULL = belum dikembalikan
    denda                DECIMAL(10,2)   NOT NULL DEFAULT 0.00
                         CHECK (denda >= 0),
    status               ENUM('dipinjam','dikembalikan','terlambat')
                         NOT NULL DEFAULT 'dipinjam',
 
    CONSTRAINT pk_peminjaman        PRIMARY KEY (id_pinjam),
    CONSTRAINT fk_pinjam_anggota    FOREIGN KEY (id_anggota)
        REFERENCES anggota (id_anggota)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT fk_pinjam_buku       FOREIGN KEY (id_buku)
        REFERENCES buku (id_buku)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_pinjam_tgl        CHECK (tgl_rencana_kembali > tgl_pinjam),
    CONSTRAINT ck_kembali_aktual
        CHECK (tgl_kembali_aktual IS NULL
               OR tgl_kembali_aktual >= tgl_pinjam),
 
    INDEX idx_pinjam_anggota      (id_anggota),
    INDEX idx_pinjam_buku         (id_buku),
    INDEX idx_pinjam_status       (status),
    INDEX idx_pinjam_tgl_kembali  (tgl_rencana_kembali),
    INDEX idx_pinjam_buku_status  (id_buku, status) -- composite untuk cek ketersediaan
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='Riwayat peminjaman buku oleh anggota';

C.5 Materi 4: Strategi Pengindeksan

C.5.1 Mengapa Index Penting?

ANALOGI:
  Tanpa index  β†’ membaca buku dari halaman 1 sampai selesai untuk
                 menemukan topik tertentu (full table scan O(n))
  
  Dengan index β†’ langsung lihat daftar indeks di belakang buku,
                 lompat ke halaman yang tepat O(log n)

Di database:
  Tanpa index : DBMS scan SEMUA baris β€” semakin besar tabel semakin lambat
  Dengan index: DBMS gunakan B-Tree untuk pencarian cepat O(log n)

C.5.2 Jenis-Jenis Index

-- 1. PRIMARY KEY INDEX (otomatis, selalu ada)
--    Dibuat saat PK didefinisikan. Data diurutkan sesuai PK (clustered index).
 
-- 2. SECONDARY INDEX (INDEX biasa)
--    Untuk kolom yang sering digunakan dalam WHERE, JOIN, ORDER BY
INDEX idx_produk_kategori (id_kategori)
 
-- Atau setelah tabel dibuat:
CREATE INDEX idx_buku_penulis ON buku (penulis);
 
-- 3. UNIQUE INDEX (otomatis dibuat saat CONSTRAINT UNIQUE dideklarasikan)
CONSTRAINT uq_anggota_email UNIQUE (email)
-- Ini otomatis membuat unique index pada kolom email
 
-- 4. COMPOSITE INDEX (multi-column)
--    Urutan kolom SANGAT penting!
INDEX idx_pinjam_buku_status (id_buku, status)
 
-- "Leftmost prefix rule":
-- WHERE id_buku = X AND status = Y  β†’ PAKAI index βœ“
-- WHERE id_buku = X                 β†’ PAKAI index βœ“ (prefix)
-- WHERE status = Y                  β†’ TIDAK PAKAI index βœ—
 
-- 5. FULL-TEXT INDEX (untuk pencarian teks bebas)
CREATE FULLTEXT INDEX ft_buku_judul ON buku (judul, penulis);
-- Digunakan dengan: WHERE MATCH(judul, penulis) AGAINST('python data science')

C.5.3 Kapan Membuat Index β€” dan Kapan Tidak

BUAT INDEX jika kolom digunakan dalam:
  βœ“ WHERE clause         : WHERE status = 'aktif'
  βœ“ JOIN condition       : ON a.id_kategori = b.id_kategori
  βœ“ ORDER BY             : ORDER BY tgl_pesanan DESC
  βœ“ GROUP BY             : GROUP BY id_kategori
  βœ“ UNIQUE (otomatis)    : email, no_anggota, isbn

JANGAN BUAT INDEX jika:
  βœ— Tabel kecil (< 1.000 baris) β€” full scan justru lebih cepat
  βœ— Kolom yang jarang digunakan dalam query
  βœ— Kolom dengan selektivitas SANGAT rendah (low cardinality)
    Contoh: kolom is_aktif dengan nilai hanya 0 atau 1
    β†’ Index tidak efektif jika 90% baris punya nilai yang sama
  βœ— Tabel yang sangat sering di-INSERT/UPDATE
    (setiap perubahan data β†’ index juga harus diperbarui β†’ lambat)
  βœ— Kolom yang sering berubah nilainya

TRADE-OFF INDEX:
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Aspek            β”‚ Tanpa Index    β”‚ Dengan Index                  β”‚
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  β”‚ Query SELECT     β”‚ Lambat (O(n))  β”‚ Cepat (O(log n))              β”‚
  β”‚ INSERT / UPDATE  β”‚ Cepat          β”‚ Sedikit lebih lambat          β”‚
  β”‚                  β”‚                β”‚ (index harus diperbarui)      β”‚
  β”‚ Storage          β”‚ Lebih kecil    β”‚ Lebih besar (index = space)   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ATURAN EMAS:
  β†’ SELALU buat index pada kolom FK (MySQL tidak membuat otomatis!)
  β†’ EXPLAIN SELECT untuk memeriksa apakah index digunakan
  β†’ Jangan over-index: mulai sedikit, tambah berdasarkan profiling query

C.5.4 Menggunakan EXPLAIN untuk Analisis Query

-- Gunakan EXPLAIN untuk melihat apakah query menggunakan index:
EXPLAIN
SELECT p.id_pinjam, a.nama, b.judul, p.status
FROM peminjaman p
JOIN anggota a ON p.id_anggota = a.id_anggota
JOIN buku b    ON p.id_buku = b.id_buku
WHERE p.status = 'dipinjam'
  AND p.tgl_rencana_kembali < CURRENT_DATE;
 
-- Kolom penting dalam output EXPLAIN:
-- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
-- β”‚ Kolom    β”‚ Artinya                                        β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ type     β”‚ 'ALL' = full table scan (tidak efisien!)       β”‚
-- β”‚          β”‚ 'ref' atau 'range' = menggunakan index (baik)  β”‚
-- β”‚          β”‚ 'const' = sangat cepat, hanya 1 baris          β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ key      β”‚ Nama index yang digunakan (NULL = tidak ada)   β”‚
-- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
-- β”‚ rows     β”‚ Estimasi jumlah baris yang discan              β”‚
-- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

C.6 Materi 5: Naming Convention dan Standar DDL Script

PENETAPAN STANDAR KELAS: Konvensi penamaan berikut adalah WAJIB digunakan di seluruh tugas, praktikum, dan proyek kelompok semester ini. Konsistensi naming convention adalah tanda profesionalisme dan memudahkan kolaborasi tim.

Standar yang Ditetapkan: snake_case - huruf kecil, kata dipisah underscore, tidak ada spasi atau camelCase.

C.6.1 Konvensi Penamaan (Standar Mata Kuliah Ini)

TABEL          : snake_case, kata benda, huruf kecil
                 βœ“ pelanggan, item_pesanan, kategori_buku
                 βœ— tblPelanggan, PELANGGAN, m_pelanggan

KOLOM          : snake_case, deskriptif, huruf kecil
                 βœ“ id_pelanggan, nama_lengkap, tgl_lahir, is_aktif
                 βœ— idPelanggan, NamaLengkap, nama_lngkp

PRIMARY KEY    : id_[nama_tabel]
                 βœ“ id_pelanggan, id_produk, id_pesanan

FOREIGN KEY    : Nama kolom FK = nama kolom PK yang direferensi
(kolom)          βœ“ id_pelanggan di tabel pesanan β†’ REFERENCES pelanggan(id_pelanggan)
                 Ini memudahkan JOIN: ON pesanan.id_pelanggan = pelanggan.id_pelanggan

CONSTRAINT     : [jenis]_[tabel]_[deskripsi]
  PK           : pk_[nama_tabel]             β†’ pk_pesanan
  FK           : fk_[tabel_anak]_[tabel_induk] β†’ fk_pesanan_pelanggan
  UNIQUE       : uq_[tabel]_[kolom]          β†’ uq_anggota_email
  CHECK        : ck_[tabel]_[deskripsi]      β†’ ck_pesanan_total

INDEX          : idx_[tabel]_[kolom]
                 βœ“ idx_produk_kategori
  Composite    : idx_[tabel]_[kolom1]_[kolom2]
                 βœ“ idx_pinjam_buku_status

DATABASE       : huruf kecil, tanpa spasi, deskriptif
                 βœ“ perpustakaan_kampus, ecommerce_dev, akademik_prod

C.6.2 Standar Struktur DDL Script

-- ============================================================
-- TEMPLATE STANDAR DDL SCRIPT
-- ============================================================
 
-- BAGIAN 1: HEADER (WAJIB ADA)
-- ============================================================
-- Nama    : DDL Script β€” [Nama Sistem]
-- Proyek  : [Nama Proyek / Mata Kuliah]
-- DBMS    : MySQL 8.0+
-- Dibuat  : [YYYY-MM-DD]
-- Penulis : [Nama] / [NIM]
-- Versi   : 1.0
-- ============================================================
 
-- BAGIAN 2: SETUP DATABASE
CREATE DATABASE IF NOT EXISTS [nama_database]
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;
USE [nama_database];
 
-- BAGIAN 3: TABEL (dalam topological order)
-- ──────────────────────────────────────────────────────────
-- Tabel: [NAMA_TABEL] β€” [Deskripsi singkat]
-- Dibuat dari: Aturan Mapping [X] β€” [nama aturan]
-- ──────────────────────────────────────────────────────────
CREATE TABLE [nama_tabel] (
 
    -- PK dan identifier
    [pk_kolom]    INT UNSIGNED        NOT NULL AUTO_INCREMENT,
 
    -- Kolom data utama
    [kolom_1]     VARCHAR(150)        NOT NULL,
    [kolom_2]     DECIMAL(15,2)       NOT NULL CHECK ([kolom_2] >= 0),
    [kolom_3]     TEXT,
 
    -- Foreign keys
    [fk_kolom]    INT UNSIGNED        NOT NULL,
 
    -- Status dan flag
    is_aktif      TINYINT(1)          NOT NULL DEFAULT 1,
 
    -- Audit columns (selalu di akhir)
    created_at    TIMESTAMP           NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    TIMESTAMP           NOT NULL DEFAULT CURRENT_TIMESTAMP
                  ON UPDATE CURRENT_TIMESTAMP,
 
    -- Constraints (dikelompokkan di bagian bawah)
    CONSTRAINT pk_[tabel]         PRIMARY KEY ([pk_kolom]),
    CONSTRAINT uq_[tabel]_[kolom] UNIQUE ([kolom_unik]),
    CONSTRAINT fk_[tabel]_[ref]   FOREIGN KEY ([fk_kolom])
        REFERENCES [tabel_ref] ([pk_ref])
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_[tabel]_[desc]  CHECK ([kondisi]),
 
    -- Indexes
    INDEX idx_[tabel]_[kolom] ([fk_kolom])
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='[Deskripsi fungsi tabel ini]';
 
-- ============================================================
-- SELESAI β€” Total: [X] tabel
-- ============================================================

C.7 Studi Kasus: Logical Model Sistem Akademik β†’ DDL Script

Berikut transformasi penuh dari logical model sistem akademik (dibangun di pertemuan 5) menjadi DDL script yang siap dieksekusi.

Logical Model yang Digunakan:

PROGRAM_STUDI(kode_prodi, nama_prodi, jenjang)
DOSEN(nip, nama_dosen, email_dosen, jabatan)
MAHASISWA(nim, nama, tgl_lahir, email, kode_prodi*)
MAHASISWA_HP(nim*, nomor_hp)
MATA_KULIAH(kode_mk, nama_mk, sks, semester_ditawarkan)
MK_PRASYARAT(kode_mk*, kode_mk_prasyarat*)
JADWAL(kode_mk*, nip*, ruangan, hari, jam_mulai, jam_selesai)
PENGAMBILAN(nim*, kode_mk*, semester, tahun_akademik, nilai_huruf)

DDL Script Lengkap:

-- ============================================================
-- DDL Script: Sistem Akademik Kampus
-- DBMS    : MySQL 8.0+
-- Dibuat  : Februari 2026
-- Versi   : 1.0
-- ============================================================
 
CREATE DATABASE IF NOT EXISTS akademik_kampus
    CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE akademik_kampus;
 
-- ──────────────────────────────────────────────────────────
-- Tabel 1: PROGRAM_STUDI (tidak punya FK)
-- ──────────────────────────────────────────────────────────
CREATE TABLE program_studi (
    kode_prodi  CHAR(10)     NOT NULL,
    nama_prodi  VARCHAR(150) NOT NULL,
    jenjang     ENUM('D3','S1','S2','S3') NOT NULL DEFAULT 'S1',
 
    CONSTRAINT pk_program_studi PRIMARY KEY (kode_prodi),
    CONSTRAINT uq_prodi_nama    UNIQUE (nama_prodi)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Master data program studi';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 2: DOSEN (tidak punya FK)
-- ──────────────────────────────────────────────────────────
CREATE TABLE dosen (
    nip          CHAR(18)     NOT NULL,
    nama_dosen   VARCHAR(150) NOT NULL,
    email_dosen  VARCHAR(255) NOT NULL,
    jabatan      VARCHAR(100),
 
    CONSTRAINT pk_dosen        PRIMARY KEY (nip),
    CONSTRAINT uq_dosen_email  UNIQUE (email_dosen)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Master data dosen pengajar';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 3: MAHASISWA (FK ke program_studi)
-- ──────────────────────────────────────────────────────────
CREATE TABLE mahasiswa (
    nim         CHAR(12)     NOT NULL,
    nama        VARCHAR(150) NOT NULL,
    tgl_lahir   DATE,
    email       VARCHAR(255) NOT NULL,
    kode_prodi  CHAR(10)     NOT NULL,
 
    CONSTRAINT pk_mahasiswa        PRIMARY KEY (nim),
    CONSTRAINT uq_mahasiswa_email  UNIQUE (email),
    CONSTRAINT fk_mhs_prodi        FOREIGN KEY (kode_prodi)
        REFERENCES program_studi (kode_prodi)
        ON DELETE RESTRICT ON UPDATE CASCADE,
 
    INDEX idx_mhs_kode_prodi (kode_prodi),
    INDEX idx_mhs_nama       (nama)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Data mahasiswa terdaftar';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 4: MAHASISWA_HP (multivalued attribute, FK ke mahasiswa)
-- ──────────────────────────────────────────────────────────
CREATE TABLE mahasiswa_hp (
    nim       CHAR(12)  NOT NULL,
    nomor_hp  VARCHAR(20) NOT NULL,
 
    CONSTRAINT pk_mahasiswa_hp  PRIMARY KEY (nim, nomor_hp),
    CONSTRAINT fk_hp_mahasiswa  FOREIGN KEY (nim)
        REFERENCES mahasiswa (nim)
        ON DELETE CASCADE ON UPDATE CASCADE
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Nomor HP mahasiswa (multivalued attribute)';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 5: MATA_KULIAH (tidak punya FK)
-- ──────────────────────────────────────────────────────────
CREATE TABLE mata_kuliah (
    kode_mk             CHAR(10)         NOT NULL,
    nama_mk             VARCHAR(200)     NOT NULL,
    sks                 TINYINT UNSIGNED NOT NULL CHECK (sks BETWEEN 1 AND 6),
    semester_ditawarkan TINYINT UNSIGNED NOT NULL CHECK (semester_ditawarkan BETWEEN 1 AND 8),
 
    CONSTRAINT pk_mata_kuliah PRIMARY KEY (kode_mk)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Master data mata kuliah';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 6: MK_PRASYARAT (self-referencing M:N pada mata_kuliah)
-- ──────────────────────────────────────────────────────────
CREATE TABLE mk_prasyarat (
    kode_mk           CHAR(10) NOT NULL,
    kode_mk_prasyarat CHAR(10) NOT NULL,
 
    CONSTRAINT pk_mk_prasyarat    PRIMARY KEY (kode_mk, kode_mk_prasyarat),
    CONSTRAINT fk_prasy_mk        FOREIGN KEY (kode_mk)
        REFERENCES mata_kuliah (kode_mk)
        ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT fk_prasy_mk_prasy  FOREIGN KEY (kode_mk_prasyarat)
        REFERENCES mata_kuliah (kode_mk)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_prasy_no_self   CHECK (kode_mk <> kode_mk_prasyarat),
 
    INDEX idx_prasy_kode_prasyarat (kode_mk_prasyarat)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Relasi prasyarat antar mata kuliah (self-referencing M:N)';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 7: JADWAL (weak entity: FK ke mata_kuliah + dosen)
-- ──────────────────────────────────────────────────────────
CREATE TABLE jadwal (
    kode_mk    CHAR(10)  NOT NULL,
    nip        CHAR(18)  NOT NULL,
    ruangan    VARCHAR(20) NOT NULL,
    hari       ENUM('Senin','Selasa','Rabu','Kamis','Jumat','Sabtu') NOT NULL,
    jam_mulai  TIME      NOT NULL,
    jam_selesai TIME     NOT NULL,
 
    CONSTRAINT pk_jadwal       PRIMARY KEY (kode_mk, nip),
    CONSTRAINT fk_jadwal_mk    FOREIGN KEY (kode_mk)
        REFERENCES mata_kuliah (kode_mk)
        ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT fk_jadwal_dosen FOREIGN KEY (nip)
        REFERENCES dosen (nip)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_jadwal_jam   CHECK (jam_selesai > jam_mulai),
 
    INDEX idx_jadwal_nip  (nip),
    INDEX idx_jadwal_hari (hari)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Jadwal mengajar dosen per mata kuliah';
 
-- ──────────────────────────────────────────────────────────
-- Tabel 8: PENGAMBILAN (junction table MHS Γ— MK)
-- ──────────────────────────────────────────────────────────
CREATE TABLE pengambilan (
    nim             CHAR(12)         NOT NULL,
    kode_mk         CHAR(10)         NOT NULL,
    semester        TINYINT UNSIGNED NOT NULL CHECK (semester BETWEEN 1 AND 14),
    tahun_akademik  CHAR(9)          NOT NULL,  -- Format: 2024/2025
    nilai_huruf     CHAR(2),                    -- NULL = belum ada nilai
 
    CONSTRAINT pk_pengambilan     PRIMARY KEY (nim, kode_mk, semester, tahun_akademik),
    CONSTRAINT fk_ambil_mhs       FOREIGN KEY (nim)
        REFERENCES mahasiswa (nim)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT fk_ambil_mk        FOREIGN KEY (kode_mk)
        REFERENCES mata_kuliah (kode_mk)
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_nilai_huruf
        CHECK (nilai_huruf IN ('A','AB','B','BC','C','D','E') OR nilai_huruf IS NULL),
 
    INDEX idx_ambil_nim     (nim),
    INDEX idx_ambil_kode_mk (kode_mk),
    INDEX idx_ambil_ta_sem  (tahun_akademik, semester)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  COMMENT='Pengambilan mata kuliah mahasiswa per semester';
 
-- ============================================================
-- SELESAI β€” Total: 8 tabel sistem akademik
-- ============================================================

C.8 Pengenalan Tools Data Modelling

C.8.1 MySQL Workbench β€” Forward Engineering

LANGKAH FORWARD ENGINEERING:

1. Buka MySQL Workbench β†’ File β†’ New Model
2. Double-click "Add Diagram" β†’ buat ERD visual
   - Toolbar kiri: tambah tabel, relationship
   - Double-click tabel: atur kolom, tipe data, constraint
3. Forward Engineering: Database β†’ Forward Engineer
   - Pilih opsi: Include all MySQL table options
   - Lihat SQL yang dihasilkan β†’ "Execute" atau "Copy to Clipboard"

LANGKAH REVERSE ENGINEERING (dari DB ke diagram):
1. Database β†’ Reverse Engineer
2. Masukkan koneksi database
3. Pilih schema β†’ MySQL Workbench baca dan buat ERD otomatis
   Berguna: mendokumentasikan database yang sudah ada!

TIPS:
  β†’ Simpan file .mwb secara rutin (File β†’ Save Model)
  β†’ View β†’ Diagram Properties: aktifkan "Show Column Types"
  β†’ Gunakan "Synchronize Model" agar ERD dan DB tetap sinkron

C.8.2 Draw.io β€” Diagram Visual Fleksibel

Draw.io (diagrams.net) menyediakan canvas visual untuk menggambar ER diagram dan physical model.
Gunakan entity shapes, relationship lines, dan crow's foot notation untuk dokumentasi.

FITUR UTAMA:
  βœ“ Web-based atau desktop app
  βœ“ Drag-drop shapes dan connectors
  βœ“ Export ke PNG, PDF, SVG
  βœ“ Simpan ke Google Drive, OneDrive, atau local
  βœ“ Kolaborasi real-time (jika menggunakan Google Drive)
  βœ“ Gratis sepenuhnya

LANGKAH DASAR:
  1. Buka https://draw.io atau https://app.diagrams.net
  2. Pilih "Blank Diagram"
  3. Dari panel kiri, ambil "Crow's Foot" atau general shapes
  4. Gambar entity sebagai rectangle dengan nama dan atribut
  5. Gambar relasi dengan connector dan tambahkan crow's foot notation
  6. Export: File β†’ Export As β†’ PNG/PDF

KEUNGGULAN:
  βœ“ Sangat fleksibel untuk desain custom
  βœ“ Cocok untuk dokumentasi visual
  βœ“ Dapat diintegrasikan ke Google Docs, Sheets, Slides
  βœ“ Gratis dan kolaboratif

KETERBATASAN:
  β†’ Tidak auto-generate SQL (gunakan MySQL Workbench untuk itu)

C.9 Materi 6: SQL DML β€” Menggunakan Skema yang Kita Buat

Mengapa DML Penting di Konteks Data Modelling? Physical model yang kita rancang tidak ada gunanya jika kita tidak tahu cara menggunakannya. DML (Data Manipulation Language) adalah antarmuka antara skema database dan dunia nyata β€” baik untuk aplikasi, analitik, maupun pipeline data science.

C.9.1 INSERT β€” Mengisi Data

-- INSERT satu baris
INSERT INTO kategori (nama_kategori, slug)
VALUES ('Batik Tulis', 'batik-tulis');
 
-- INSERT beberapa baris sekaligus (lebih efisien!)
INSERT INTO pelanggan (nama, email, no_telp, alamat_kota) VALUES
    ('Budi Santoso',  'budi@mail.com',  '08111000001', 'Semarang'),
    ('Siti Rahayu',   'siti@mail.com',  '08111000002', 'Solo'),
    ('Ahmad Fauzi',   'ahmad@mail.com', '08111000003', 'Pekalongan');
 
-- INSERT dari hasil SELECT (salin data)
INSERT INTO arsip_pesanan_lama (id_pesanan, tgl_pesanan, id_pelanggan, status)
SELECT id_pesanan, tgl_pesanan, id_pelanggan, status
FROM pesanan
WHERE status = 'selesai' AND YEAR(tgl_pesanan) < 2023;

Aturan INSERT:

  • Urutan kolom dan VALUES harus sesuai
  • Kolom NOT NULL tanpa DEFAULT WAJIB diisi
  • Kolom AUTO_INCREMENT boleh diabaikan (MySQL yang isi)
  • Kolom dengan DEFAULT boleh diabaikan
  • NULL hanya bisa diisi ke kolom yang NULLABLE

C.9.2 SELECT dengan JOIN β€” Inti Penggunaan Relational Database

-- INNER JOIN β€” hanya baris yang ada pasangannya di KEDUA tabel
SELECT
    p.id_pesanan,
    pl.nama         AS nama_pelanggan,
    p.tgl_pesanan,
    p.status
FROM pesanan p
INNER JOIN pelanggan pl ON p.id_pelanggan = pl.id_pelanggan
WHERE p.status = 'pending'
ORDER BY p.tgl_pesanan DESC;
 
-- LEFT JOIN β€” semua baris dari tabel kiri, NULL jika tidak ada pasangan
-- "Tampilkan semua pelanggan, dan berapa pesanan yang mereka punya"
SELECT
    pl.id_pelanggan,
    pl.nama,
    pl.email,
    COUNT(p.id_pesanan) AS total_pesanan
FROM pelanggan pl
LEFT JOIN pesanan p ON pl.id_pelanggan = p.id_pelanggan
GROUP BY pl.id_pelanggan, pl.nama, pl.email
ORDER BY total_pesanan DESC;
 
-- JOIN 3 TABEL β€” detail pesanan lengkap
SELECT
    p.id_pesanan,
    pl.nama              AS nama_pelanggan,
    pr.nama_produk,
    ip.qty,
    ip.harga_saat_pesan,
    (ip.qty * ip.harga_saat_pesan) AS subtotal
FROM pesanan p
JOIN pelanggan pl   ON p.id_pelanggan  = pl.id_pelanggan
JOIN item_pesanan ip ON p.id_pesanan   = ip.id_pesanan
JOIN produk pr      ON ip.id_produk    = pr.id_produk
WHERE p.id_pesanan = 42;
 
-- Aggregasi: ringkasan per pelanggan
SELECT
    pl.nama,
    COUNT(DISTINCT p.id_pesanan)              AS jumlah_pesanan,
    SUM(ip.qty * ip.harga_saat_pesan)         AS total_belanja,
    AVG(ip.qty * ip.harga_saat_pesan)         AS rata_per_pesanan,
    MAX(p.tgl_pesanan)                        AS pesanan_terakhir
FROM pelanggan pl
JOIN pesanan p      ON pl.id_pelanggan = p.id_pelanggan
JOIN item_pesanan ip ON p.id_pesanan   = ip.id_pesanan
WHERE p.status = 'selesai'
GROUP BY pl.id_pelanggan, pl.nama
HAVING total_belanja > 500000
ORDER BY total_belanja DESC;

Kapan Menggunakan JOIN vs Subquery?

SituasiRekomendasiAlasan
Gabungkan data dari 2+ tabelINNER JOINLebih readable, optimizer MySQL lebih efisien
"Semua X, termasuk yang tidak punya Y"LEFT JOINMenjaga semua baris dari tabel kiri
Filter berdasarkan agregasiJOIN + HAVINGWHERE tidak bisa pakai alias agregasi
Perbandingan dengan satu nilai dari tabel lainSubquery atau JOINTergantung konteks

C.9.3 UPDATE β€” Mengubah Data dengan Aman

-- UPDATE dengan WHERE yang spesifik
UPDATE produk
SET harga = harga * 1.10,  -- naik 10%
    updated_at = CURRENT_TIMESTAMP
WHERE id_kategori = 3
  AND is_aktif = 1;
 
-- UPDATE berdasarkan JOIN (update dari tabel lain)
UPDATE pesanan p
JOIN pelanggan pl ON p.id_pelanggan = pl.id_pelanggan
SET p.status = 'dibatalkan'
WHERE pl.alamat_kota = 'Jakarta'
  AND p.status = 'pending'
  AND p.tgl_pesanan < DATE_SUB(NOW(), INTERVAL 7 DAY);
 
-- WAJIB: Selalu gunakan WHERE pada UPDATE!
-- Tanpa WHERE β†’ UPDATE SEMUA BARIS β†’ bencana data!
-- Sebelum UPDATE, jalankan SELECT dulu untuk verifikasi:
SELECT * FROM produk WHERE id_kategori = 3 AND is_aktif = 1;
-- Jika hasilnya sesuai ekspektasi β†’ baru jalankan UPDATE

Praktek Aman UPDATE:

  • Selalu jalankan SELECT dengan predikat yang sama sebelum UPDATE
  • Gunakan LIMIT untuk membatasi jika tidak yakin
  • Gunakan transaction (BEGIN; ... ROLLBACK;) saat testing

C.9.4 DELETE β€” Menghapus Data

-- DELETE baris tertentu
DELETE FROM ulasan
WHERE tgl_ulasan < '2020-01-01'
  AND rating >= 4;  -- hapus ulasan lama dengan rating tinggi (bersihkan data)
 
-- DELETE dengan JOIN (hapus berdasarkan kondisi dari tabel lain)
DELETE ip
FROM item_pesanan ip
JOIN pesanan p ON ip.id_pesanan = p.id_pesanan
WHERE p.status = 'dibatalkan'
  AND p.tgl_pesanan < DATE_SUB(NOW(), INTERVAL 1 YEAR);
 
-- TRUNCATE vs DELETE
TRUNCATE TABLE temp_log;       -- hapus SEMUA baris, reset AUTO_INCREMENT
DELETE FROM temp_log;          -- hapus SEMUA baris, tapi TIDAK reset AUTO_INCREMENT
DELETE FROM temp_log LIMIT 1000; -- hapus 1000 baris saja
 
-- SOFT DELETE β€” lebih aman di sistem produksi!
-- Daripada benar-benar menghapus, tandai sebagai "deleted"
ALTER TABLE produk ADD COLUMN deleted_at TIMESTAMP NULL;
UPDATE produk SET deleted_at = CURRENT_TIMESTAMP WHERE id_produk = 50;
-- Semua query "aktif" harus pakai: WHERE deleted_at IS NULL

Kapan DELETE vs Soft Delete?

SituasiRekomendasi
Data history penting (audit trail)Soft Delete (tambah kolom deleted_at)
Data jelas tidak diperlukan (log lama, temp data)Hard DELETE dengan LIMIT
Related data (FK lain yang referensi)Cek FK sebelum delete atau gunakan CASCADE
Sistem produksiSelalu Soft Delete untuk keamanan

C.10 Materi 7: SQL Views dalam Physical Model

Apa itu View? View adalah "tabel virtual" yang merupakan hasil dari query SELECT yang disimpan dengan nama. View tidak menyimpan data β€” setiap kali dipanggil, MySQL menjalankan ulang query di baliknya terhadap data aktual.

C.10.1 Mengapa Views Termasuk Physical Model?

Physical model bukan hanya tentang CREATE TABLE. Views adalah bagian dari physical design karena:

AlasanPenjelasan
Abstraksi query kompleksSembunyikan JOIN 5 tabel di balik satu nama view yang sederhana
Keamanan dataBerikan akses ke kolom tertentu saja tanpa expose seluruh tabel
Konsistensi laporanLogika bisnis (formula, filter) ada di satu tempat, bukan tersebar di setiap aplikasi
Pipeline analitikView menjadi "staging layer" yang digunakan notebook ML/analytics
Backward compatibilityGanti struktur tabel tanpa memperbarui semua aplikasi

C.10.2 Sintaks CREATE VIEW

-- VIEW DASAR β€” laporan pesanan aktif
CREATE VIEW v_pesanan_aktif AS
SELECT
    p.id_pesanan,
    p.tgl_pesanan,
    p.status,
    pl.nama    AS nama_pelanggan,
    pl.email,
    COUNT(ip.id_produk)                   AS jumlah_item,
    SUM(ip.qty * ip.harga_saat_pesan)     AS total_harga
FROM pesanan p
JOIN pelanggan pl    ON p.id_pelanggan = pl.id_pelanggan
JOIN item_pesanan ip ON p.id_pesanan   = ip.id_pesanan
WHERE p.status NOT IN ('selesai', 'dibatalkan')
GROUP BY p.id_pesanan, p.tgl_pesanan, p.status,
         pl.nama, pl.email;
 
-- Setelah view dibuat, query menjadi SANGAT SEDERHANA:
SELECT * FROM v_pesanan_aktif WHERE status = 'pending';
SELECT nama_pelanggan, total_harga FROM v_pesanan_aktif ORDER BY total_harga DESC;
 
-- VIEW untuk KEAMANAN β€” hanya tampilkan data public produk
CREATE VIEW v_produk_publik AS
SELECT
    id_produk,
    nama_produk,
    deskripsi,
    harga,
    stok > 0    AS tersedia,  -- boolean dari stok
    slug
FROM produk
WHERE is_aktif = 1 AND deleted_at IS NULL;
-- Kolom: id_kategori, created_at, updated_at TIDAK ditampilkan
 
-- VIEW ANALITIK β€” ringkasan penjualan per produk
CREATE VIEW v_ringkasan_penjualan AS
SELECT
    pr.id_produk,
    pr.nama_produk,
    k.nama_kategori,
    COUNT(DISTINCT ip.id_pesanan)        AS total_transaksi,
    SUM(ip.qty)                          AS total_qty_terjual,
    SUM(ip.qty * ip.harga_saat_pesan)    AS total_pendapatan,
    AVG(u.rating)                        AS rata_rating,
    COUNT(u.id_ulasan)                   AS jumlah_ulasan
FROM produk pr
LEFT JOIN kategori k      ON pr.id_kategori  = k.id_kategori
LEFT JOIN item_pesanan ip ON pr.id_produk    = ip.id_produk
LEFT JOIN pesanan p       ON ip.id_pesanan   = p.id_pesanan AND p.status = 'selesai'
LEFT JOIN ulasan u        ON pr.id_produk    = u.id_produk
WHERE pr.is_aktif = 1
GROUP BY pr.id_produk, pr.nama_produk, k.nama_kategori;

C.10.3 Manajemen Views

-- Menampilkan daftar view yang ada
SHOW FULL TABLES WHERE Table_type = 'VIEW';
 
-- Melihat definisi view
SHOW CREATE VIEW v_pesanan_aktif;
 
-- Update definisi view (CREATE OR REPLACE)
CREATE OR REPLACE VIEW v_pesanan_aktif AS
SELECT ...;  -- definisi baru
 
-- Hapus view
DROP VIEW IF EXISTS v_pesanan_aktif;
 
-- PENTING: View TIDAK menyimpan data!
-- Setiap SELECT ke view = MySQL jalankan ulang query di baliknya
-- Untuk view yang sering diakses dengan data besar:
-- Pertimbangkan MATERIALIZED VIEW (di MySQL: stored procedure + tabel snapshot)

C.10.4 Hubungan Views dengan Data Science Pipeline

PIPELINE ANALITIK DENGAN VIEWS:

Tabel Transaksional (OLTP)
  kategori, produk, pelanggan, pesanan, item_pesanan, ulasan
       |
       | (View Layer)
       v
Views sebagai "Virtual Staging Layer"
  v_produk_publik     β†’ untuk API dan frontend
  v_pesanan_aktif     β†’ untuk dashboard operasional
  v_ringkasan_penjualan β†’ untuk analitik dan laporan
       |
       | (Data Extraction)
       v
Jupyter Notebook / Analytics Tool
  import pandas as pd
  import sqlalchemy
  
  df = pd.read_sql("SELECT * FROM v_ringkasan_penjualan", engine)
  # Langsung dapat DataFrame bersih untuk analisis!
  # Tidak perlu tulis ulang 5-table JOIN di setiap notebook

Praktik Terbaik View Naming:

KonvensiContohKeterangan
Prefix v_v_pesanan_aktifBedakan dari tabel nyata
Deskriptifv_ringkasan_penjualanJelas menggambarkan isinya
Hindari tbl_vw_pesananGunakan v_, bukan vw_

C.11 Materi 8: Stored Procedure β€” Logika Bisnis di Sisi Database

Apa Itu Stored Procedure?

Stored Procedure adalah sekumpulan pernyataan SQL yang disimpan di server database dan dapat dipanggil berulang kali seperti fungsi. Stored Procedure berguna untuk:

  • Enkapsulasi logika bisnis β€” query kompleks cukup ditulis sekali
  • Konsistensi β€” semua aplikasi menggunakan logika yang sama
  • Keamanan β€” user hanya diberi hak EXECUTE, bukan akses langsung ke tabel
  • Performa β€” query di-compile sekali dan di-cache oleh MySQL

Syntax Dasar

-- Membuat stored procedure
DELIMITER //
 
CREATE PROCEDURE sp_pesanan_per_pelanggan(
    IN p_id_pelanggan INT     -- Parameter input
)
BEGIN
    SELECT 
        p.id_pesanan,
        p.tgl_pesanan,
        p.status,
        SUM(ip.qty * ip.harga_saat_pesan) AS total_bayar
    FROM pesanan p
    JOIN item_pesanan ip ON p.id_pesanan = ip.id_pesanan
    WHERE p.id_pelanggan = p_id_pelanggan
    GROUP BY p.id_pesanan, p.tgl_pesanan, p.status
    ORDER BY p.tgl_pesanan DESC;
END //
 
DELIMITER ;

Cara Memanggil

-- Panggil procedure
CALL sp_pesanan_per_pelanggan(1);
 
-- Hapus procedure
DROP PROCEDURE IF EXISTS sp_pesanan_per_pelanggan;

Jenis Parameter

JenisArahKegunaanContoh
INMasukMenerima nilai dari pemanggilIN p_id INT
OUTKeluarMengembalikan nilai ke pemanggilOUT p_total DECIMAL(12,2)
INOUTDua arahMenerima dan mengembalikan nilaiINOUT p_counter INT

Contoh dengan Parameter OUT

DELIMITER //
 
CREATE PROCEDURE sp_total_penjualan_pelanggan(
    IN  p_id_pelanggan INT,
    OUT p_total        DECIMAL(15,2)
)
BEGIN
    SELECT COALESCE(SUM(ip.qty * ip.harga_saat_pesan), 0)
    INTO p_total
    FROM pesanan p
    JOIN item_pesanan ip ON p.id_pesanan = ip.id_pesanan
    WHERE p.id_pelanggan = p_id_pelanggan;
END //
 
DELIMITER ;
 
-- Cara memanggil:
CALL sp_total_penjualan_pelanggan(1, @total);
SELECT @total AS total_pembelian;

Kapan Menggunakan Stored Procedure?

  • Query yang digunakan berulang oleh banyak aplikasi
  • Logika bisnis yang harus konsisten di semua akses (bukan hanya di satu app)
  • Operasi batch (insert data dummy, kalkulasi agregat berkala)
  • Bukan untuk semua query β€” query sederhana cukup langsung di aplikasi

C.12 Materi 9: Trigger β€” Otomatisasi Respons terhadap Perubahan Data

Apa Itu Trigger?

Trigger adalah blok SQL yang dieksekusi otomatis oleh database saat terjadi event tertentu (INSERT, UPDATE, atau DELETE) pada sebuah tabel. Trigger berjalan tanpa dipanggil secara eksplisit β€” ia "menyimak" perubahan data.

Jenis Trigger

TimingEventKapan Dijalankan
BEFORE INSERTSebelum data masukValidasi atau modifikasi data sebelum disimpan
AFTER INSERTSetelah data masukLogging, update tabel lain
BEFORE UPDATESebelum data diubahCek constraint bisnis yang kompleks
AFTER UPDATESetelah data diubahAudit trail, sinkronisasi
BEFORE DELETESebelum data dihapusCek referensi manual, archiving
AFTER DELETESetelah data dihapusLogging penghapusan

Contoh: Trigger Audit Log Perubahan Harga

-- Buat tabel log terlebih dahulu
CREATE TABLE log_perubahan_harga (
    id_log         INT AUTO_INCREMENT PRIMARY KEY,
    id_produk      INT            NOT NULL,
    harga_lama     DECIMAL(12,2)  NOT NULL,
    harga_baru     DECIMAL(12,2)  NOT NULL,
    tgl_perubahan  DATETIME       DEFAULT CURRENT_TIMESTAMP,
    diubah_oleh    VARCHAR(100)   DEFAULT CURRENT_USER()
);
 
-- Buat trigger
DELIMITER //
 
CREATE TRIGGER trg_audit_harga
AFTER UPDATE ON produk
FOR EACH ROW
BEGIN
    IF OLD.harga <> NEW.harga THEN
        INSERT INTO log_perubahan_harga (id_produk, harga_lama, harga_baru)
        VALUES (OLD.id_produk, OLD.harga, NEW.harga);
    END IF;
END //
 
DELIMITER ;

Cara Kerja:

  • OLD.harga = nilai sebelum update
  • NEW.harga = nilai setelah update
  • Trigger hanya mencatat jika harga benar-benar berubah (IF OLD <> NEW)

Contoh: Trigger Validasi Stok Sebelum Pesanan

DELIMITER //
 
CREATE TRIGGER trg_cek_stok
BEFORE INSERT ON item_pesanan
FOR EACH ROW
BEGIN
    DECLARE v_stok INT;
    
    SELECT stok INTO v_stok
    FROM produk
    WHERE id_produk = NEW.id_produk;
    
    IF v_stok < NEW.qty THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Stok tidak mencukupi untuk pesanan ini';
    END IF;
END //
 
DELIMITER ;

⚠️ Masalah Potensial Trigger

MasalahPenjelasan
Hidden logicDeveloper lain mungkin tidak sadar ada trigger yang berjalan β†’ debugging sulit
PerformaTrigger dieksekusi setiap kali event terjadi β†’ batch INSERT 10.000 baris = trigger dipanggil 10.000 kali
Cascading triggerTrigger A mengubah tabel B β†’ trigger di tabel B aktif β†’ potensi loop atau efek tak terduga
Sulit di-testTidak bisa di-unit test secara terpisah dari operasi DML

Best Practice: Gunakan trigger hanya untuk audit/logging dan validasi sederhana. Logika bisnis yang kompleks sebaiknya di stored procedure atau application layer. Selalu dokumentasikan trigger yang dibuat.


C.13 Materi 10: Transaction dan ACID β€” Menjaga Konsistensi Data

Mengapa Transaction Penting?

Bayangkan transfer bank: saldo pengirim dikurangi, tapi sebelum saldo penerima ditambah, sistem crash. Tanpa transaction, data menjadi inkonsisten β€” uang "hilang" dari sistem.

Transaction memastikan sekelompok operasi SQL berjalan sebagai satu kesatuan: semua berhasil, atau semua dibatalkan.

Properti ACID

PropertiArtiContoh
AtomicitySemua operasi dalam transaction berhasil, atau tidak ada yang berhasilTransfer: debit DAN kredit, atau tidak keduanya
ConsistencyDatabase berpindah dari satu state valid ke state valid lainnyaTotal saldo sebelum dan sesudah transfer sama
IsolationTransaction yang berjalan bersamaan tidak saling menggangguDua transfer berbeda tidak "membaca" state setengah jadi
DurabilitySetelah COMMIT, data tersimpan permanen meskipun sistem crashData tidak hilang setelah restart server

Syntax Transaction di MySQL

-- Mulai transaction
START TRANSACTION;
 
-- Operasi 1: Kurangi stok produk
UPDATE produk 
SET stok = stok - 2 
WHERE id_produk = 101;
 
-- Operasi 2: Tambah item pesanan
INSERT INTO item_pesanan (id_pesanan, id_produk, qty, harga_saat_pesan)
VALUES (50, 101, 2, 85000.00);
 
-- Operasi 3: Update total pesanan
UPDATE pesanan
SET total = total + (2 * 85000.00)
WHERE id_pesanan = 50;
 
-- Jika semua berhasil β†’ simpan permanen
COMMIT;
 
-- Jika ada masalah β†’ batalkan semua
-- ROLLBACK;

SAVEPOINT β€” Checkpoint dalam Transaction

START TRANSACTION;
 
-- Tahap 1: Insert header pesanan
INSERT INTO pesanan (id_pelanggan, tgl_pesanan, status)
VALUES (5, NOW(), 'Pending');
 
SAVEPOINT setelah_header;
 
-- Tahap 2: Insert item pesanan (bisa gagal jika stok habis)
INSERT INTO item_pesanan (id_pesanan, id_produk, qty, harga_saat_pesan)
VALUES (LAST_INSERT_ID(), 101, 3, 85000.00);
 
-- Jika tahap 2 gagal, bisa rollback ke savepoint tanpa kehilangan tahap 1
-- ROLLBACK TO setelah_header;
 
COMMIT;

Kapan Menggunakan Transaction?

SkenarioPerlu Transaction?Alasan
INSERT 1 baris ke 1 tabelTidak (auto-commit)MySQL otomatis commit per statement
Transfer dana antar akunYa2+ operasi yang harus atomik
Proses checkout (stok + pesanan + pembayaran)YaMulti-tabel, harus all-or-nothing
SELECT untuk laporanTidakHanya baca, tidak mengubah data
Batch INSERT 1000 barisYaPerforma lebih baik dan bisa rollback jika gagal di tengah

Ringkasan: Transaction + ACID adalah jaminan bahwa database Anda selalu dalam kondisi konsisten, bahkan saat terjadi error atau concurrent access. Dalam konteks data modelling, ini menjelaskan mengapa constraint dan referential integrity penting β€” mereka bekerja bersama transaction untuk menjaga kualitas data.


D. PRAKTIKUM TERBIMBING

D.1 Demonstrasi Tools (15 menit)

Demonstrasi oleh Dosen:

  1. MySQL Workbench (8 menit): Buat dua tabel visual β†’ gambar relasi β†’ setup FK dan constraint β†’ forward engineering β†’ jalankan DDL β†’ tunjukkan EXPLAIN pada contoh query

  2. Draw.io (5 menit): Buka browser β†’ draw.io β†’ pilih database diagram template β†’ gambar tabel PELANGGAN dan PESANAN dengan notasi crow's foot β†’ setup relationship antar tabel β†’ export ke PNG


D.2 Praktikum Mandiri: DDL Script Toko Batik Online (Individual, 20 menit)

Instruksi: Diberikan logical model berikut, tulis DDL script yang lengkap dan valid untuk MySQL 8.0. Sertakan: tipe data tepat, semua constraint dengan nama eksplisit, minimal 1 index per FK column, urutan tabel yang benar.

Logical Model β€” Sistem Toko Batik Online:

KATEGORI(id_kategori, nama_kategori, slug)
PRODUK(id_produk, nama_produk, deskripsi, harga, stok, is_aktif, id_kategori*)
PELANGGAN(id_pelanggan, nama, email, no_telp, alamat_kota)
PESANAN(id_pesanan, tgl_pesanan, status, id_pelanggan*)
ITEM_PESANAN(id_pesanan*, id_produk*, qty, harga_saat_pesan)
  [weak entity dari PESANAN]
ULASAN(id_ulasan, id_pelanggan*, id_produk*, rating, komentar, tgl_ulasan)
  [satu pelanggan hanya boleh memberikan satu ulasan per produk]

Pertanyaan Penuntun:

  1. Urutkan tabel sesuai dependency: mana yang dibuat pertama?
  2. Tipe data apa yang tepat untuk: harga, stok, is_aktif, rating, slug?
  3. Constraint apa yang diperlukan di kolom email PELANGGAN?
  4. Constraint apa yang membatasi satu pelanggan hanya boleh satu ulasan per produk?
  5. ON DELETE action apa yang tepat untuk FK di ITEM_PESANAN β†’ PESANAN?

D.3 Praktikum DML + Views: "Hidupkan" Skema Toko Batik (Individual, 20 menit)

Setelah membuat DDL di D.2, waktunya mengisi dan menggunakan database yang kita buat!

Bagian A β€” INSERT Data Awal (5 menit)

-- Isi 3 kategori
INSERT INTO kategori (nama_kategori, slug) VALUES
    ('Batik Tulis',  'batik-tulis'),
    ('Batik Cap',    'batik-cap'),
    ('Batik Printing','batik-printing');
 
-- Isi 2 pelanggan
INSERT INTO pelanggan (nama, email, no_telp, alamat_kota) VALUES
    ('Dewi Puspita', 'dewi@mail.com', '08111222333', 'Pekalongan'),
    ('Rizki Ananda', 'rizki@mail.com', '08444555666', 'Solo');
 
-- Isi 3 produk (referensi id_kategori yang baru diinsert)
INSERT INTO produk (nama_produk, deskripsi, harga, stok, is_aktif, id_kategori) VALUES
    ('Batik Mega Mendung', 'Motif khas Cirebon', 350000.00, 20, 1, 1),
    ('Batik Kawung Cap',   'Motif kawung modern', 180000.00, 15, 1, 2),
    ('Batik Printing Floral', 'Motif bunga cerah', 95000.00, 50, 1, 3);
 
-- Buat 1 pesanan dan 2 item
INSERT INTO pesanan (tgl_pesanan, status, id_pelanggan) VALUES
    (NOW(), 'pending', 1);
 
INSERT INTO item_pesanan (id_pesanan, id_produk, qty, harga_saat_pesan) VALUES
    (1, 1, 2, 350000.00),
    (1, 3, 3, 95000.00);

Bagian B β€” SELECT dengan JOIN (10 menit)

Tulis query untuk menjawab pertanyaan bisnis berikut:

  1. Tampilkan detail pesanan ID=1: nama pelanggan, nama produk, qty, harga satuan, subtotal (qty*harga)
    Hint: JOIN pesanan, pelanggan, item_pesanan, produk

  2. Total nilai pesanan per pelanggan: tampilkan nama, email, jumlah pesanan, total belanja
    Hint: GROUP BY + SUM

  3. Produk mana yang belum pernah dipesan?
    Hint: LEFT JOIN produk β†’ item_pesanan, filter WHERE IS NULL

Bagian C β€” Buat 1 View Analitik (5 menit)

Buat view v_ringkasan_produk yang menampilkan: id_produk, nama_produk, nama_kategori, harga, total qty terjual (dari item_pesanan), stock tersisa. View ini digunakan untuk dashboard manajer.

-- Contoh jawaban
CREATE VIEW v_ringkasan_produk AS
SELECT
    pr.id_produk,
    pr.nama_produk,
    k.nama_kategori,
    pr.harga,
    pr.stok                          AS stok_tersisa,
    COALESCE(SUM(ip.qty), 0)         AS total_terjual
FROM produk pr
JOIN kategori k          ON pr.id_kategori = k.id_kategori
LEFT JOIN item_pesanan ip ON pr.id_produk  = ip.id_produk
GROUP BY pr.id_produk, pr.nama_produk, k.nama_kategori,
         pr.harga, pr.stok;
 
-- Test view
SELECT * FROM v_ringkasan_produk ORDER BY total_terjual DESC;

E. EVALUASI DAN PENILAIAN

E.1 Kuis Penutup (10 menit, bobot partisipasi)

  1. Jelaskan perbedaan VARCHAR(255) vs CHAR(10) β€” berikan contoh atribut konkret untuk masing-masing!

  2. Mengapa FLOAT berbahaya untuk harga produk? Tipe apa yang seharusnya digunakan?

  3. FK berikut menggunakan ON DELETE CASCADE:

    FOREIGN KEY (id_pesanan) REFERENCES pesanan(id_pesanan) ON DELETE CASCADE

    Apa yang terjadi jika baris dengan id_pesanan = 101 di tabel pesanan dihapus?

  4. Kolom status hanya boleh bernilai 'aktif', 'nonaktif', atau 'pending'. Mana yang lebih baik: VARCHAR(10) atau ENUM(...)? Jelaskan!

  5. Apa perbedaan INNER JOIN dan LEFT JOIN? Kapan menggunakan LEFT JOIN?

  6. Mengapa View (CREATE VIEW) dianggap sebagai bagian dari Physical Model, bukan hanya fitur query biasa? Sebutkan 2 manfaat View di pipeline data science!

  7. Tanpa melihat catatan, sebutkan naming convention yang wajib digunakan di mata kuliah ini untuk: nama tabel, kolom Primary Key, nama constraint Foreign Key.


E.2 Tugas Physical Model Proyek Kelompok

Judul: Implementasi Physical Data Model β€” Proyek Kelompok

Deskripsi: Kelompok mengimplementasikan logical model proyek mereka (yang telah divalidasi di pertemuan 7) menjadi DDL script yang lengkap dan dapat dieksekusi.

Deliverables:

Bagian 1 β€” Physical Data Model Diagram Buat diagram physical model menggunakan MySQL Workbench (untuk DDL automation) atau Draw.io (untuk visual documentation). Tampilkan: nama tabel, semua kolom + tipe data MySQL, PK, FK, constraint. Export sebagai PNG atau PDF.

Bagian 2 β€” DDL Script Tulis DDL script untuk semua tabel proyek. Sertakan: header, comment tabel, semua constraint bernama, semua index yang relevan, urutan tabel yang benar. Script harus dapat dieksekusi tanpa error di MySQL 8.0.

Bagian 3 β€” SQL DML: Gunakan Skema yang Dibuat Tulis 5 SQL query DML terhadap skema proyek yang sudah dibuat:

  • Minimal 2 INSERT statement (data sample bermakna)
  • Minimal 2 SELECT dengan JOIN minimal 3 tabel + filter + sort
  • Minimal 1 SELECT dengan GROUP BY + agregasi (COUNT, SUM, atau AVG)
  • Minimal 1 UPDATE dengan WHERE yang tepat
  • Opsional: 1 DELETE atau soft-delete

Bagian 4 β€” SQL Views Buat 2 SQL View untuk skema proyek:

  • View 1: Data operasional (contoh: daftar transaksi aktif, laporan harian)
  • View 2: View analitik dengan agregasi (contoh: ringkasan per produk/kategori/pelanggan)

Sertakan use case: "View ini akan digunakan oleh ______ untuk _______"

Bagian 5 β€” Justifikasi Keputusan Tipe Data

Tabel.KolomTipe DataAlasanAlternatif yang Dipertimbangkan
pesanan.totalDECIMAL(15,2)Nilai keuangan butuh presisi exactFLOAT (tidak aman untuk uang)
produk.is_aktifTINYINT(1)Boolean MySQL idiomVARCHAR('true','false')
............

Minimal 8 keputusan tipe data dengan justifikasi.

Bagian 6 - Strategi Index

Nama IndexTabelKolomQuery Bisnis yang Dipercepat
idx_pesanan_statuspesananstatusLaporan pesanan pending
............

Minimal 5 index (di luar PK) dengan justifikasi.

Bagian 7 - Bukti Eksekusi Screenshot yang menunjukkan DDL script berhasil dieksekusi tanpa error + output SHOW TABLES; dan DESCRIBE [nama_tabel]; untuk minimal 3 tabel.

Format File:

  • Laporan: PhysicalModel_[KelompokX]_[Domain].pdf (min. 8 halaman)
  • SQL file DDL: ddl_[KelompokX]_[Domain].sql
  • SQL file DML + Views: dml_views_[KelompokX]_[Domain].sql

Deadline: H-1 sebelum pertemuan 10 (dikumpulkan via Ngaji UIN Gusdur)


F. PERSIAPAN PERTEMUAN 10

F.1 Topik Pertemuan 10

Data Quality, Data Integrity, dan Data Governance β€” memastikan bahwa database yang sudah dibangun secara teknis juga menghasilkan data yang dapat dipercaya kualitasnya sepanjang waktu.

Topik yang akan dibahas:

  • Dimensi kualitas data: accuracy, completeness, consistency, timeliness, validity, uniqueness
  • Referential integrity dan strategi menjaganya
  • Metadata dan data dictionary yang profesional
  • Dampak kualitas data buruk terhadap analisis sains data

F.2 Pertanyaan Pemantik untuk Pertemuan 10

Bayangkan tabel PELANGGAN di sistem e-commerce yang berjalan 3 tahun:

  • 15% baris memiliki email tanpa karakter @
  • Nama terisi "aaa", "test", "xxxx"
  • Nomor telepon dengan format berbeda: 081234, +62 812 34, 08-1234-5678

Pikirkan:

  1. Apakah constraint DDL yang sudah dibuat cukup mencegah masalah ini?
  2. Siapa yang bertanggung jawab atas kualitas data seperti ini?
  3. Bagaimana data seperti ini bisa masuk meskipun ada constraint?

G. REFERENSI

G.1 Referensi Utama

  1. Elmasri, R. & Navathe, S. B. (2015). Fundamentals of Database Systems (7th Edition). Pearson.

    • Chapter 17: Database Design Methodology β€” Physical Design (bacaan utama)
  2. Connolly, T. & Begg, C. (2014). Database Systems (6th Edition). Pearson.

    • Chapter 19: Physical Database Design for Relational Databases (very practical)
  3. Hoberman, S. (2009). Data Modeling Made Simple (2nd Edition). Technics Publications.

    • Chapter 10: Physical Data Model

G.2 Dokumentasi Resmi DBMS

  1. MySQL 8.0 Reference Manual. Data Types β€” https://dev.mysql.com/doc/refman/8.0/en/data-types.html (opens in a new tab)
  2. MySQL 8.0 Reference Manual. Constraints β€” https://dev.mysql.com/doc/refman/8.0/en/constraints.html (opens in a new tab)
  3. MySQL 8.0 Reference Manual. Optimization and Indexes β€” https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html (opens in a new tab)

G.3 Tools

  1. MySQL Workbench β€” https://dev.mysql.com/doc/workbench/en/ (opens in a new tab) (ERD design, forward/reverse engineering)
  2. Draw.io / Diagrams.net β€” https://draw.io (opens in a new tab) (Free diagram tool untuk ER dan physical model visualization)

G.4 Referensi Tambahan

  1. Use The Index, Luke! β€” https://use-the-index-luke.com/ (opens in a new tab) (panduan index database terbaik, gratis online)
  2. Database Star. "Database Naming Conventions" β€” https://www.databasestar.com/database-naming-conventions/ (opens in a new tab)

G.5 Video Resources

  1. Corey Schafer β€” "MySQL Tutorial for Beginners" (YouTube)
  2. Academind β€” "MySQL Workbench Tutorial" (YouTube)
  3. CMU Database Group β€” "Database Systems: Storage & Indexes" (YouTube)

H. LAMPIRAN

Lampiran A: Panduan Cepat Tipe Data MySQL 8.0

╔════════════════════════════════════════════════════════════════════╗
β•‘              PANDUAN CEPAT: TIPE DATA MYSQL 8.0                    β•‘
╠═══════════════════╦════════════════════╦═══════════════════════════╣
β•‘ Kebutuhan         β•‘ Tipe Data          β•‘ Contoh Penggunaan         β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ ID / Primary Key  β•‘ INT UNSIGNED       β•‘ id_pelanggan, id_produk   β•‘
β•‘ (sistem besar)    β•‘ BIGINT UNSIGNED    β•‘ id_transaksi bank         β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Uang / Harga      β•‘ DECIMAL(15, 2)     β•‘ harga, gaji, saldo        β•‘
β•‘ JANGAN FLOAT!     β•‘                    β•‘                           β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Persentase        β•‘ DECIMAL(5, 2)      β•‘ diskon, pajak (0–100.00)  β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Stok / Qty kecil  β•‘ SMALLINT UNSIGNED  β•‘ qty_item, qty_keranjang   β•‘
β•‘ Stok / Qty umum   β•‘ INT UNSIGNED       β•‘ stok_produk               β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Nama orang        β•‘ VARCHAR(150)       β•‘ nama_pelanggan, nama_dosenβ•‘
β•‘ Judul / Produk    β•‘ VARCHAR(300)       β•‘ judul_buku, nama_produk   β•‘
β•‘ Email             β•‘ VARCHAR(255)       β•‘ email (standar RFC)       β•‘
β•‘ URL / Link        β•‘ VARCHAR(2048)      β•‘ foto_url, link_produk     β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Kode tetap        β•‘ CHAR(n)            β•‘ NIM(12), kode_mk(8)       β•‘
β•‘ (panjang sama)    β•‘                    β•‘ kode_pos(5), NIP(18)      β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Status/pilihan    β•‘ ENUM(...)          β•‘ status_pesanan, jenjang   β•‘
β•‘ (terbatas, stabil)β•‘                    β•‘ jenis_anggota, hari       β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Teks panjang      β•‘ TEXT               β•‘ deskripsi, catatan, bio   β•‘
β•‘ Konten artikel    β•‘ MEDIUMTEXT         β•‘ isi_artikel, konten_blog  β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Tanggal saja      β•‘ DATE               β•‘ tgl_lahir, tgl_jatuh_tempoβ•‘
β•‘ Tanggal + jam     β•‘ DATETIME           β•‘ tgl_transaksi             β•‘
β•‘ Audit timestamps  β•‘ TIMESTAMP          β•‘ created_at, updated_at    β•‘
β•‘ Tahun saja        β•‘ YEAR               β•‘ tahun_terbit, tahun_masuk β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Boolean/flag      β•‘ TINYINT(1)         β•‘ is_aktif, is_verified     β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Rating (1–5)      β•‘ TINYINT UNSIGNED   β•‘ rating_produk             β•‘
╠═══════════════════╬════════════════════╬═══════════════════════════╣
β•‘ Koordinat GPS     β•‘ DECIMAL(10, 7)     β•‘ latitude, longitude       β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Lampiran B: Checklist Physical Data Model

CHECKLIST PHYSICAL DATA MODEL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[TIPE DATA]
β–‘ Nilai keuangan menggunakan DECIMAL(p,s), BUKAN FLOAT atau DOUBLE
β–‘ ID/surrogate key: INT UNSIGNED AUTO_INCREMENT
β–‘ Kode dengan panjang tetap: CHAR(n)
β–‘ Nama, email, deskripsi: VARCHAR(n) sesuai kebutuhan
β–‘ Tanggal tanpa jam: DATE; dengan jam: DATETIME
β–‘ Audit columns: TIMESTAMP (created_at, updated_at)
β–‘ Boolean/flag: TINYINT(1) NOT NULL DEFAULT 0/1
β–‘ Status terbatas: ENUM(...)
β–‘ CHARACTER SET: utf8mb4, COLLATE: utf8mb4_unicode_ci

[CONSTRAINT]
β–‘ Setiap tabel memiliki tepat satu PRIMARY KEY dengan nama eksplisit
β–‘ Nama constraint eksplisit: pk_, fk_, uq_, ck_
β–‘ FK total participation β†’ NOT NULL
β–‘ FK partial participation β†’ boleh NULL
β–‘ ON DELETE action dipilih dengan logika bisnis:
   β–‘ CASCADE β†’ weak entity dan tabel dependen
   β–‘ RESTRICT β†’ relasi yang harus di-maintain
   β–‘ SET NULL β†’ relasi opsional
β–‘ CHECK constraint untuk validasi domain nilai
β–‘ UNIQUE constraint untuk kolom yang harus unik secara bisnis

[INDEX]
β–‘ Index pada SEMUA kolom FK (tidak otomatis di MySQL!)
β–‘ Index pada kolom WHERE, ORDER BY, GROUP BY yang sering dipakai
β–‘ Kolom ENUM dan boolean biasanya TIDAK perlu diindeks
β–‘ Composite index dibuat jika sering ada multi-column filter
β–‘ Nama index: idx_[tabel]_[kolom]

[SCRIPT]
β–‘ Header script ada (nama, proyek, DBMS, tanggal, penulis)
β–‘ Urutan tabel: dari yang tidak punya FK ke yang punya FK (topological order)
β–‘ COMMENT pada setiap tabel
β–‘ Script dapat dieksekusi tanpa error di MySQL 8.0
β–‘ Naming convention konsisten: snake_case, huruf kecil
β–‘ ENGINE=InnoDB dan charset utf8mb4 ditentukan di setiap tabel

Lampiran C: Template DDL Script Standar

-- ============================================================
-- DDL Script: [Nama Sistem]
-- DBMS    : MySQL 8.0+
-- Proyek  : [Nama Proyek]
-- Dibuat  : [YYYY-MM-DD]
-- Penulis : [Nama] / [NIM]
-- Versi   : 1.0
-- ============================================================
 
CREATE DATABASE IF NOT EXISTS [nama_database]
    CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE [nama_database];
 
-- ──────────────────────────────────────────────────────────
-- Tabel: [NAMA_TABEL] β€” [Deskripsi singkat]
-- Dari  : Aturan Mapping [X]
-- ──────────────────────────────────────────────────────────
CREATE TABLE [nama_tabel] (
 
    -- Identifier
    [pk_kolom]   INT UNSIGNED        NOT NULL AUTO_INCREMENT,
 
    -- Data utama
    [kolom_1]    VARCHAR(150)        NOT NULL,
    [kolom_2]    DECIMAL(15,2)       NOT NULL CHECK ([kolom_2] >= 0),
    [kolom_3]    TEXT,
 
    -- Foreign key
    [fk_kolom]   INT UNSIGNED        NOT NULL,
 
    -- Status
    is_aktif     TINYINT(1)          NOT NULL DEFAULT 1,
 
    -- Audit
    created_at   TIMESTAMP           NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at   TIMESTAMP           NOT NULL DEFAULT CURRENT_TIMESTAMP
                 ON UPDATE CURRENT_TIMESTAMP,
 
    -- Constraints
    CONSTRAINT pk_[tabel]          PRIMARY KEY ([pk_kolom]),
    CONSTRAINT uq_[tabel]_[kolom]  UNIQUE ([kolom_unik]),
    CONSTRAINT fk_[tabel]_[ref]    FOREIGN KEY ([fk_kolom])
        REFERENCES [tabel_ref] ([pk_ref])
        ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT ck_[tabel]_[desc]   CHECK ([kondisi]),
 
    -- Indexes
    INDEX idx_[tabel]_[fk_kolom] ([fk_kolom])
 
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='[Deskripsi fungsi tabel]';

Lampiran D: Lembar Kerja Praktikum

LEMBAR KERJA PRAKTIKUM β€” DDL SCRIPT
Nama  : ___________________________ NIM : _______________
Tanggal: ___________________

LOGICAL MODEL:
KATEGORI(id_kategori, nama_kategori, slug)
PRODUK(id_produk, nama_produk, deskripsi, harga, stok, is_aktif, id_kategori*)
PELANGGAN(id_pelanggan, nama, email, no_telp, alamat_kota)
PESANAN(id_pesanan, tgl_pesanan, status, id_pelanggan*)
ITEM_PESANAN(id_pesanan*, id_produk*, qty, harga_saat_pesan)
ULASAN(id_ulasan, id_pelanggan*, id_produk*, rating, komentar, tgl_ulasan)

LANGKAH 1 β€” Urutan pembuatan tabel (topological order):
1. _______________  (tidak punya FK)
2. _______________  (tidak punya FK)
3. _______________  (FK ke: _______________)
4. _______________  (FK ke: _______________)
5. _______________  (FK ke: _______________)
6. _______________  (FK ke: _______________)

LANGKAH 2 β€” Tipe data untuk kolom penting:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Kolom                β”‚ Tipe Data       β”‚ Alasan                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ harga                β”‚                 β”‚                            β”‚
β”‚ stok                 β”‚                 β”‚                            β”‚
β”‚ is_aktif             β”‚                 β”‚                            β”‚
β”‚ rating               β”‚                 β”‚                            β”‚
β”‚ slug                 β”‚                 β”‚                            β”‚
β”‚ tgl_pesanan          β”‚                 β”‚                            β”‚
β”‚ status (pesanan)     β”‚                 β”‚                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

LANGKAH 3 β€” Constraint khusus yang diperlukan:
ON DELETE action untuk ITEM_PESANAN β†’ PESANAN: ____________
Alasan: ________________________________________________
Constraint di ULASAN untuk mencegah duplikasi review: ____________

LANGKAH 4 β€” Tulis DDL Script (gunakan halaman berikutnya):
[Tulis DDL Script di halaman kosong, ikuti template standar]

PENUTUP

Pertemuan 9 menandai tonggak penting: model data kita kini bukan hanya diagram atau skema di atas kertas - melainkan kode yang bisa dieksekusi, database yang benar-benar bekerja, dan query yang menghasilkan insight dari data yang kita rancang sendiri.

Key Messages Pertemuan 9:

  1. Physical model bukan sekadar "terjemahan" logikal - ada keputusan tambahan yang penting: tipe data, constraint, index, urutan eksekusi, dan standar kode yang semuanya berdampak nyata pada performa dan integritas sistem

  2. DECIMAL untuk uang, selalu - FLOAT dan DOUBLE tidak bisa dipercaya untuk nilai keuangan karena representasi biner yang tidak exact

  3. Constraint adalah "penjaga otomatis" - setiap aturan bisnis yang bisa di-encode sebagai constraint harus di-encode, karena DBMS lebih handal dari application logic dalam menjaga integritas

  4. DDL tanpa DML = schema tanpa jiwa - mahasiswa Data Science harus bisa tidak hanya membuat tabel, tapi juga JOIN antar tabel, aggregasi data, dan memahami hasilnya secara bisnis

  5. Views adalah komponen Physical Model - bukan hanya fitur query, tapi lapisan desain yang memberikan abstraksi, keamanan, dan kenyamanan bagi pengguna data (analytics, ML engineer, aplikasi)

  6. snake_case adalah STANDAR WAJIB - konsistensi naming convention mencerminkan profesionalisme dan wajib diterapkan seluruh semester

Koneksi ke Proyek Semester: Tugas DDL + DML + Views pertemuan ini adalah Tahap 6 dari proyek bertahap β€” Physical Model Implementation. Output berupa file ddl_...sql dan dml_views_...sql yang akan terus digunakan di pertemuan 10 (data quality) dan menjadi bagian utama dari laporan akhir proyek kelompok.


Disusun oleh: Mohammad Reza Maulana, M.Kom Program Studi Sains Data Fakultas Ekonomi dan Bisnis Islam UIN K.H. Abdurrahman Wahid Pekalongan

Revisi: Februari 2026