Memilih Tipe Data yang Tepat — Database

Mengapa Tipe Data Penting? Memilih tipe data yang tepat memengaruhi storage, performa, dan integritas data. Tipe data yang salah bisa menyebabkan bug halus…

Tipe data adalah jenis isi yang boleh disimpan sebuah kolom: angka bulat, angka desimal, teks, waktu, atau ya/tidak. Bayangin dapur yang rapi: telur di wadah telur, minyak di botol, gula di toples. Semuanya memang bisa dimasukkan ke satu kantong plastik, tapi telurnya pecah dan gulanya tumpah. Tipe yang tepat bikin database menolak isi yang salah, menyimpan dengan hemat, dan menghitung dengan benar.

🧪 Tebakan cepat: kolom price di SQLite bertipe INTEGER. Form admin Kedai Senja mengirim harga Roti Bakar '20rb'. Apa yang terjadi: error, atau masuk begitu saja? Tebak dulu, jawabannya ada di bagian "SQLite: tipe cuma saran".

Satu tabel, satu tipe per kolom

Ini tabel produk Kedai Senja kalau ditulis untuk PostgreSQL. Tiap kolom dapat tipe yang paling pas dengan isinya:

CREATE TABLE products (
  id BIGINT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,         -- nama produk
  description TEXT,                   -- deskripsi panjang
  country_code CHAR(2) NOT NULL,      -- asal biji kopi: 'ID', 'BR'
  price DECIMAL(12, 2) NOT NULL,      -- harga, angka pasti
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Uang: kenapa bukan FLOAT

FLOAT, REAL dan DOUBLE menyimpan angka dalam bentuk biner yang mendekati, bukan pasti. Angka seperti 0,1 gak bisa ditulis tepat dalam biner, sama seperti 1/3 gak bisa ditulis tepat sebagai 0,333... Kedai Senja juga mulai menjual produk kemasan ke luar negeri, dalam dolar. Ini yang terjadi di Lab kalau harganya disimpan sebagai REAL:

CREATE TABLE export_items (id INTEGER PRIMARY KEY, name TEXT, price_usd REAL);
INSERT INTO export_items (name, price_usd) VALUES ('Teh Tarik Sachet', 1.10), ('Gula Aren 500 g', 2.20);
SELECT SUM(price_usd) AS total, SUM(price_usd) = 3.30 AS pas FROM export_items;
total pas
3.3000000000000003 0

Ada dua cara yang benar. Di MySQL dan PostgreSQL, pakai DECIMAL(10, 2), yang menyimpan angka desimal persis. Atau simpan dalam satuan terkecil sebagai angka bulat: price_cents INTEGER berisi 110 dan 220, totalnya tepat 330, dan = 330 bernilai 1. Untuk rupiah yang gak pakai sen, BIGINT berisi rupiah utuh juga pas.

Teks dan waktu

SQLite: tipe cuma saran

SQLite, database yang dipakai Lab dan Playground, jauh lebih longgar. Tipe kolom di SQLite cuma saran (namanya type affinity): dia mencoba mengubah nilai ke tipe kolomnya, dan kalau gagal, nilainya disimpan apa adanya.

CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER);
INSERT INTO products (name, price) VALUES ('Kopi Susu', 18000);
INSERT INTO products (name, price) VALUES ('Kopi Susu Aren', 22000);
INSERT INTO products (name, price) VALUES ('Roti Bakar', '20rb');
SELECT name, price, typeof(price) FROM products;
name price typeof(price)
Kopi Susu 18000 integer
Kopi Susu Aren 22000 integer
Roti Bakar 20rb text

'20rb' masuk tanpa error, sebagai teks. Itu jawaban tebakannya. typeof() menunjukkan tipe yang benar-benar tersimpan. Teks '20000' (dengan tanda kutip) masih diubah jadi angka 20000, tapi '20rb' gak bisa, jadi tetap teks. Akibatnya kacau diam-diam:

Mau SQLite seketat database lain? Tambahkan STRICT di akhir CREATE TABLE ... ) STRICT;. Sekarang INSERT yang sama ditolak: cannot store TEXT value in INTEGER column products.price. Tabel STRICT cuma menerima tipe INT, INTEGER, REAL, TEXT, BLOB dan ANY. SQLite juga gak punya DECIMAL yang pasti, kolom DECIMAL tetap menyimpan pecahan sebagai REAL, jadi di SQLite uang selalu disimpan sebagai angka bulat sen.

📌 MySQL, PostgreSQL dan Laravel

MySQL dan PostgreSQL menolak isi yang gak cocok dengan tipenya. Ini jawaban PostgreSQL untuk INSERT yang sama:

ERROR:  invalid input syntax for type integer: "20rb"

MySQL juga menolaknya dengan error, karena strict mode yang nyala secara bawaan sejak versi 5.7. Kalau strict mode dimatikan, MySQL menyimpan 20 dan cuma memberi warning. Batas VARCHAR(2) juga dijaga: PostgreSQL menjawab value too long for type character varying(2), sedangkan SQLite menyimpan teks yang lebih panjang tanpa protes. TIMESTAMPTZ cuma ada di PostgreSQL, di MySQL pakai TIMESTAMP (disimpan sebagai UTC) atau DATETIME. BOOLEAN di MySQL sebenarnya TINYINT(1).

Di migration Laravel: $table->string('name', 100), $table->text('description'), $table->char('country_code', 2), $table->decimal('price', 12, 2), $table->boolean('is_active'), $table->timestampTz('created_at').

⚠️ Jebakan yang sering ketemu

TL;DR: Pilih tipe sesuai isi: VARCHAR(n) untuk nama, TEXT untuk tulisan panjang, CHAR(n) untuk kode yang panjangnya tetap, DECIMAL(p, s) untuk harga, BOOLEAN untuk aktif/nonaktif, TIMESTAMPTZ untuk waktu dibuat. Jangan pakai FLOAT untuk uang: 1,10 + 2,20 jadi 3,3000000000000003. Simpan dalam DECIMAL atau angka bulat sen. SQLite cuma menganggap tipe sebagai saran ('20rb' masuk ke kolom INTEGER sebagai teks) kecuali tabelnya STRICT, sedangkan MySQL dan PostgreSQL menolaknya.

Yang akan kamu pelajari