Kita sudah menjelajahi integer, pecahan, string, waktu, boolean, JSON, hingga biner — masing-masing dengan rentang dan perilakunya. Sekarang tibalah pertanyaan yang sesungguhnya: ketika mendesain tabel nyata, tipe mana yang kita tulis untuk setiap kolom? Keputusan ini sulit diubah kemudian karena ALTER TABLE pada tabel besar memakan waktu dan risiko, dan tipe yang salah diam-diam memakan ruang, memperlambat query, atau gagal menyimpan data. Pada Chapter ini kita merangkum seluruh section menjadi satu kerangka keputusan berbentuk matriks, aturan praktis yang mudah diingat, dan skema toko kecil yang teruji dari CREATE TABLE sampai JOIN.

Matriks Keputusan Kasus ke Tipe #

Tabel berikut memetakan kasus kolom yang paling sering muncul langsung ke pilihan tipe yang tepat. Gunakan sebagai peta cepat saat menulis CREATE TABLE:

Kasus kolomTipe yang dipilihAlasan singkat
Harga, saldo, nominal uangDECIMAL(10,2) s.d. DECIMAL(14,2)Pecahan eksak; FLOAT/DOUBLE bisa melenceng
Jumlah, stok, kuantitas kecilTINYINT / SMALLINT UNSIGNEDNon-negatif; pilih ukuran sesuai batas wajar
ID baris tabelINT / BIGINT UNSIGNED AUTO_INCREMENTPenomoran otomatis; BIGINT untuk tabel transaksi besar
Status dengan daftar tetapENUM(...) atau TINYINT UNSIGNEDValidasi gratis; ENUM terbaca manusia, TINYINT fleksibel
Ya/tidakBOOLEANAlias TINYINT(1); bisa direkap dengan SUM()
Nama, judul, label pendekVARCHAR(n)Panjang variabel; tetapkan n yang wajar
Deskripsi, artikel, isi bebasTEXTTanpa batas panjang yang perlu kita tebak
Tanggal lahir, jadwal, event masa laluDATE / DATETIMERentang sampai 9999; tidak terganggu zona waktu
Waktu dibuat/diubah serverTIMESTAMPOtomatis UTC; hemat 4 byte; rentang 1970–2038
Hasil pengukuran, koordinatDOUBLEPresisi 15 digit; galat biner tak masalah
Hash, token, byte tetapBINARY(n) / VARBINARY(n)Byte mentah; HEX()/UNHEX() untuk membaca
Atribut yang beragam antarbarisJSONSemi-terstruktur; tervalidasi server
Foto, lampiranMEDIUMBLOB atau sistem fileBLOB bila harus atomik dengan barisnya

Dua baris waktu pada matriks tersebut layak digarisbawahi. TIMESTAMP dipakai untuk kejadian yang dicatat server sendiri (login, insert, update) karena nilainya otomatis dikonversi ke UTC sehingga aman dipindah antarzona waktu. DATETIME dipakai untuk event yang merupakan fakta dunia nyata — jadwal kereta, tanggal lahir, jam mulai konser — karena nilai seperti “12 Oktober 2019 pukul 08:00 WIB” memang tidak boleh bergeser mengikuti zona waktu pembaca. Event masa lalu hampir selalu DATETIME karena TIMESTAMP tidak bisa menyimpan apa pun sebelum tahun 1970.

Aturan Praktis yang Mudah Diingat #

Matriks di atas bisa dipadatkan menjadi lima kalimat yang cukup menutupi mayoritas keputusan sehari-hari. Pertama, uang selalu DECIMAL — tanpa kecuali, sekecil apa pun nominalnya. Kedua, status kecil dan stabil memakai ENUM, sedangkan status yang mungkin bertambah memakai TINYINT dengan tabel referensi. Ketiga, teks yang panjangnya tidak bisa kita tebak memakai TEXT, bukan VARCHAR(99999). Keempat, waktu server TIMESTAMP, waktu dunia nyata DATETIME. Kelima, kolom ID memakai INT UNSIGNED AUTO_INCREMENT untuk tabel referensi (pelanggan, produk) dan BIGINT UNSIGNED AUTO_INCREMENT untuk tabel yang tumbuh paling cepat (pesanan, log), karena pesanan bisa mencapai miliaran baris lebih dulu.

Tiga prinsip tambahan menjaga kualitas skema. Pilih selalu tipe terkecil yang masih menampung nilai maksimal yang masuk akal — tabel besar membayar mahal setiap byte ekstra. Tambatkan atribut UNSIGNED pada kolom yang secara bisnis tidak pernah negatif supaya nilai jahat tertolak di gerbang. Dan jaga kolom relasi bertipe sama persis dengan kolom kunci yang dirujuknya — INT UNSIGNED berpasangan dengan INT UNSIGNED — supaya index bekerja optimal saat nanti kita pasang foreign key.

Contoh Skema Teruji: Toko Kecil #

Mari kita terapkan seluruh kerangka pada skema toko kecil dengan tiga tabel. Perhatikan bahwa setiap kolom kini punya alasan yang bisa kita jelaskan:

sql
DROP DATABASE IF EXISTS belajar_tipe;
CREATE DATABASE belajar_tipe;
USE belajar_tipe;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE TABLE pelanggan (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(60) NOT NULL,
  email VARCHAR(120),
  aktif BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE TABLE produk (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(80) NOT NULL,
  harga DECIMAL(12,2) NOT NULL,
  stok SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  deskripsi TEXT
);

CREATE TABLE pesanan (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  pelanggan_id INT UNSIGNED NOT NULL,
  produk_id INT UNSIGNED NOT NULL,
  jumlah TINYINT UNSIGNED NOT NULL,
  total DECIMAL(14,2) NOT NULL,
  status ENUM('baru', 'dibayar', 'dikirim', 'selesai') NOT NULL DEFAULT 'baru',
  dibuat_pada TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Mari kita bedah keputusan tipe pada skema di atas:

  1. id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY pada pelanggan dan produk: dua tabel referensi ini tumbuh lambat, jadi INT (maksimal 4,29 miliar) lebih dari cukup. Klausa AUTO_INCREMENT membuat server mengisi nomor berikutnya otomatis saat kita INSERT tanpa id.
  2. id BIGINT UNSIGNED AUTO_INCREMENT pada pesanan: tabel transaksi tumbuh paling cepat — satu toko besar bisa mencatat ratusan ribu pesanan per hari — maka kita siapkan langit-langit 18 kuintiliun sejak awal.
  3. harga DECIMAL(12,2) dan total DECIMAL(14,2): aturan uang. total dibuat lebih lebar karena hasil kali harga × jumlah bisa melampaui harga satuannya.
  4. stok SMALLINT UNSIGNED dan jumlah TINYINT UNSIGNED: stok per produk hingga 65.535 unit; jumlah barang dalam satu pesanan hampir pasti di bawah 255.
  5. status ENUM(...): empat nilai yang terkendali, plus DEFAULT 'baru' supaya pesanan baru otomatis berstatus benar.
  6. dibuat_pada TIMESTAMP DEFAULT CURRENT_TIMESTAMP: kejadian yang dicatat server sendiri — pas dengan TIMESTAMP, dan nilai bawaannya terisi jam server saat INSERT.

Sekarang kita isi datanya. Kolom id sengaja tidak kita sebut pada INSERT sehingga penomoran otomatis bekerja, sementara dibuat_pada kita isi literal tetap agar contoh ini bisa diuji ulang kapan pun:

sql
INSERT INTO pelanggan (nama, email, aktif) VALUES
  ('Budi Santoso', '[email protected]', TRUE),
  ('Siti Aminah', '[email protected]', TRUE);

INSERT INTO produk (nama, harga, stok, deskripsi) VALUES
  ('Kaos Polos Merah', 45000.00, 120, 'Katun combed 30s, ukuran S hingga XL.'),
  ('Topi Belajar', 27500.00, 85, 'Topi bordir logo, warna hitam.');

INSERT INTO pesanan (pelanggan_id, produk_id, jumlah, total, status, dibuat_pada) VALUES
  (1, 2, 2, 55000.00, 'dikirim', '2026-01-15 10:05:00'),
  (2, 1, 1, 45000.00, 'baru', '2026-01-16 08:30:00');

SELECT id, nama FROM pelanggan ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

+----+--------------+
| id | nama         |
+----+--------------+
|  1 | Budi Santoso |
|  2 | Siti Aminah  |
+----+--------------+
2 rows in set (0.00 sec)

Kolom id pelanggan terisi 1 dan 2 otomatis — angka yang kita pakai sebagai pelanggan_id pada INSERT pesanan. Skema dan datanya kini siap dibaca lintas tabel:

sql
SELECT p.nama AS pelanggan,
       pr.nama AS produk,
       ps.jumlah,
       ps.total,
       ps.status,
       ps.dibuat_pada
FROM pesanan ps
JOIN pelanggan p ON p.id = ps.pelanggan_id
JOIN produk pr ON pr.id = ps.produk_id
ORDER BY ps.id;

Output yang Dihasilkan:

text
+--------------+------------------+--------+----------+---------+---------------------+
| pelanggan    | produk           | jumlah | total    | status  | dibuat_pada         |
+--------------+------------------+--------+----------+---------+---------------------+
| Budi Santoso | Topi Belajar     |      2 | 55000.00 | dikirim | 2026-01-15 10:05:00 |
| Siti Aminah  | Kaos Polos Merah |      1 | 45000.00 | baru    | 2026-01-16 08:30:00 |
+--------------+------------------+--------+----------+---------+---------------------+
2 rows in set (0.00 sec)

Query JOIN tersebut berhasil dibaca dari tiga tabel — kolom id di tiap tabel saling terhubung lewat pelanggan_id dan produk_id yang bertipe sama persis dengan kunci yang dirujuknya. Tipe data yang seragam di kedua sisi relasi membuat pembanding p.id = ps.pelanggan_id sederhana dan efisien; pemasangan foreign key formalnya akan kita bahas pada Section Table Design.

Terakhir, kita bereskan database latihan:

sql
DROP DATABASE belajar_tipe;

Output yang Dihasilkan:

text
Query OK, 3 rows affected (0.00 sec)

Referensi #

Kerangka keputusan kini lengkap di tangan kita. Satu topik kecil tapi berdampak besar masih tersisa: atribut SIGNED/UNSIGNED — bagaimana membaca rentangnya dan mengapa pengurangan sederhana pada kolom UNSIGNED bisa memicu galat yang mengejutkan. Chapter penutup section ini membedahnya tuntas.