πŸ“Š Tipe Data & Penggunaannya

Referensi detail untuk Programmer dan DBA β€” diperkaya dengan pola penggunaan nyata, studi kasus industri, dan penjelasan mendalam kapan dan mengapa setiap tipe dipilih.

🎯 Mengapa Pemilihan Tipe Data Sangat Penting?

Tipe data bukan sekadar formalitas deklarasi. Keputusan ini memengaruhi:

  • Performa Query & Indeks β€” Indeks pada BIGINT lebih lambat daripada INT; VARCHAR tanpa batas membuat key length besar.
  • Penyimpanan & Biaya Cloud β€” 1 miliar baris TINYINT vs INT menghemat ~3 GB.
  • Integritas Data β€” DECIMAL mencegah kesalahan pembulatan uang; DATE memvalidasi rentang otomatis.
  • Kompatibilitas API β€” Serialisasi JSON/XML bergantung pada tipe (misal: angka vs string).
TIPE DATA β†’ Primitif / Skalar β†’ Numerik Karakter Boolean | Komposit / Non-Primitif β†’ Array Struct Object Collection

πŸ”’ 1. Tipe Data Primitif (Skalar) β€” Detail & Praktik Terbaik

Tipe primitif adalah atom penyusun aplikasi. Setiap sub-bagian kini dilengkapi dengan **konteks penggunaan sehari-hari** dan **pertimbangan desain yang sering muncul di production**.

1.1 Integer β€” Kapan Memilih Ukuran Tertentu?

TipeByteRentang SignedPenggunaan Sangat Umum
TINYINT1-128..127Status kode (0-5), flag boolean, rating bintang (1-5)
SMALLINT2-32.768..32.767Tahun (cukup), jumlah item keranjang, usia (0-120)
INT4Β±2,1 milyarID internal pengguna, jumlah view, foreign key di sistem menengah
BIGINT8Β±9,2 Γ— 10¹⁸Primary key di sistem besar (Twitter: snowflake ID), timestamp epoch ms
πŸ’‘ Pola Desain: Untuk primary key di aplikasi skala global, gunakan BIGINT (sering auto-increment) atau UUID. Namun, jika Anda memilih INT karena alasan performa, pastikan Anda memiliki strategi migrasi jika melewati 2 miliar baris (misal: sharding lebih awal).
Java / Go / SQL// Java: Entity ID standar
        public class User {
        private Long id;       // BIGINT di DB
        private Short age;     // SMALLINT
        private Byte status;   // TINYINT
        }

        -- MySQL: DDL efisien
        CREATE TABLE orders (
        order_id BIGINT AUTO_INCREMENT PRIMARY KEY,
        user_id  INT NOT NULL,                -- asumsi user < 2M
        quantity SMALLINT NOT NULL DEFAULT 1
        );

1.2 Floating Point & Decimal β€” Studi Kasus Keuangan

Detail: FLOAT/DOUBLE menggunakan basis 2, sehingga pecahan desimal seperti 0.1 tidak dapat direpresentasikan secara eksak. Dalam aplikasi fintech, sekali transaksi mungkin aman, tetapi akumulasi jutaan transaksi menyebabkan selisih (drift) yang tidak dapat ditoleransi.

SkenarioTipe SalahTipe BenarAlasan
Saldo rekening / e-walletFLOATDECIMAL(18,2)Presisi absolut, tanpa pembulatan biner
Harga saham (5 digit desimal)DOUBLEDECIMAL(15,5)Regulasi bursa menuntut presisi eksak
Koordinat GPSDECIMALDOUBLE / FLOAT8Butuh rentang besar, presisi ~15 digit cukup
Persentase diskonDOUBLEDECIMAL(5,2)0.00 - 100.00, tidak butuh floating point
⚠️ Pengalaman Nyata: Banyak startup fintech awal menggunakan DOUBLE untuk saldo karena lebih sederhana. Setelah audit, mereka harus melakukan migrasi besar-besaran ke NUMERIC. Mulailah dengan benar sejak awal.
Python & SQL# Python: JANGAN
        saldo = 1000.50 + 2000.30   # float, berisiko

        # LAKUKAN
        from decimal import Decimal
        saldo = Decimal('1000.50') + Decimal('2000.30')

        -- PostgreSQL: tipe uang khusus
        CREATE TABLE dompet (
        user_id INT,
        balance NUMERIC(18, 2) NOT NULL CHECK (balance >= 0)
        );

1.3 Boolean β€” Penggunaan di API dan Database

Pola sering dipakai: Flag seperti is_active, is_verified, is_deleted. Di REST API, boolean direpresentasikan sebagai true/false JSON. Di database, gunakan BOOLEAN (PostgreSQL) atau TINYINT(1) (MySQL). Jangan gunakan CHAR(1) 'Y'/'N' kecuali Anda di Oracle.

πŸ’‘ Indeks Boolean: Indeks pada kolom boolean saja jarang menguntungkan karena kardinalitas rendah (hanya 2-3 nilai). Namun, indeks komposit seperti (is_active, last_login) bisa sangat efektif untuk query "pengguna aktif yang terakhir login > 30 hari".

1.4 String & Teks β€” Panduan Ukuran dan Encoding

Detail penting: VARCHAR(255) sering dipakai karena dulu batas maksimum indeks MySQL, namun kini tidak relevan. Selalu pilih ukuran berdasarkan domain data:

DataTipe RekomendasiAlasan
EmailVARCHAR(254)RFC 5321, bagian lokal 64 + @ + domain 255
UsernameVARCHAR(30)Twitter 15, GitHub 39, ambil tengahnya
No. TeleponVARCHAR(20)Maks internasional 15 digit + prefix '+'
Alamat lengkapTEXTBisa > 255, jarang di-search, tidak usah diindeks penuh
Slug URLVARCHAR(100)Judul artikel, biasanya < 100 karakter
Kode verifikasiCHAR(6)Fixed-length, lebih cepat dibanding VARCHAR

Encoding: Gunakan UTF-8 (utf8mb4 di MySQL) untuk mendukung emoji dan karakter internasional. Perhatikan bahwa CHAR(10) dengan utf8mb4 bisa memakan 40 byte! Di PostgreSQL, cukup gunakan VARCHAR atau TEXT (encoding UTF-8 otomatis).

Deklarasi Tepat-- MySQL / MariaDB
        CREATE TABLE users (
        email     VARCHAR(254) NOT NULL UNIQUE,
        phone     VARCHAR(20) NULL,
        otp_code  CHAR(6) NULL,
        bio       TEXT NULL        -- tidak diindeks langsung
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1.5 Tanggal & Waktu β€” Zona Waktu, Epoch, dan Query Rentang

Penggunaan paling sering: created_at, updated_at, deleted_at, birth_date. Dua pola utama:

πŸ’‘ Query Rentang Waktu: Indeks pada created_at sangat umum. Gunakan tipe TIMESTAMP (4 byte) jika rentang mencukupi (1970-2038), atau DATETIME/TIMESTAMPTZ untuk cakupan lebih luas. Hindari menyimpan waktu sebagai string VARCHAR β€” Anda akan kehilangan semua fungsi temporal.

🧩 2. Tipe Data Komposit β€” Struktur Data dalam Aplikasi Modern

Setiap tipe komposit memiliki peran spesifik. Berikut penjelasan berdasarkan kapan mereka muncul di kode produksi.

πŸ“‹ Array / Slice

Penggunaan: Daftar tags, kategori, gambar produk. Di PostgreSQL INTEGER[] untuk menyimpan banyak foreign key tanpa tabel pivot (normalisasi vs performa).

Detail: Array di database menghilangkan kebutuhan JOIN, namun menyulitkan referential integrity. Gunakan jika data jarang berubah dan tidak perlu di-query secara relasional.

πŸ—‚οΈ JSON/JSONB (Object)

Paling sering: Konfigurasi fitur, metadata produk, payload dari webhook eksternal yang skemanya tidak stabil.

Detail: PostgreSQL JSONB mendukung indexing GIN sehingga query @> (contains) sangat cepat. Jangan gunakan JSONB untuk data yang selalu di-join ke tabel lain.

πŸ”— Map / Dictionary

Penggunaan: Cache in-memory (Redis hash), konfigurasi key-value per pengguna. Di Python, dict adalah tulang punggung semua ORM dan framework.

πŸ“¦ Struct / Dataclass

Penggunaan: DTO (Data Transfer Object), model domain. Di Go, struct digunakan untuk decoding JSON request dengan aman.

Detail: Di database PostgreSQL, Anda bisa membuat COMPOSITE TYPE sendiri, misal address_type (street, city, zip) dan menggunakannya sebagai tipe kolom.

Go & PostgreSQL Composite// Go struct untuk request API
        type CreateOrderReq struct {
        UserID  int64   `json:"user_id"`
        Items   []string `json:"items"`
        Total   float64 `json:"total"`  // seharusnya pakai decimal
        }

        -- PostgreSQL composite type
        CREATE TYPE alamat AS (
        jalan VARCHAR(100),
        kota  VARCHAR(50),
        kode_pos VARCHAR(5)
        );

        CREATE TABLE kantor (
        id INT,
        lokasi alamat
        );

2.1 Array β€” Lebih dari Sekadar Daftar

Array adalah struktur data paling fundamental untuk menyimpan koleksi elemen bertipe sama. Penggunaannya sangat luas mulai dari logika bisnis hingga komunikasi API. Pemahaman mendalam tentang array di berbagai bahasa dan database akan menghindari jebakan desain.

πŸ“‹ Array di Bahasa Pemrograman β€” Perbandingan Fitur

BahasaDeklarasiSifatOperasi Kunci
Pythonlist = [1, 2, 3]Dinamis, heterogenappend(), slice, list comprehension
Javaint[] arr = new int[5]; atau ArrayList<Integer>Fixed-size (array), dinamis (List)Arrays.copyOf, stream()
Goarr := [3]int{1,2,3}Fixed-size (array), slice = referensi dinamisappend(), copy(), slice capacity
JavaScriptlet arr = [1,2,3];Dinamis, sparse diperbolehkanmap(), filter(), reduce()
C#int[] arr = new int[3]; atau List<int>Fixed atau dinamisLINQ, List.Add()

Penggunaan nyata:

πŸ—„οΈ Array di PostgreSQL β€” Query dan Manipulasi

PostgreSQL menyediakan tipe data array native. Ini sangat berguna untuk menyimpan data yang jarang di-relasikan secara penuh, misalnya tag produk, daftar permission, atau koordinat.

PostgreSQL ArrayCREATE TABLE artikel (
        id SERIAL PRIMARY KEY,
        judul VARCHAR(200),
        tags TEXT[]   -- array of text
        );
        INSERT INTO artikel (judul, tags) VALUES
        ('Belajar SQL', ARRAY['sql','database','pemula']);

        -- Query: cari artikel yang punya tag 'database'
        SELECT * FROM artikel WHERE 'database' = ANY(tags);

        -- Menambah tag baru
        UPDATE artikel SET tags = array_append(tags, 'latihan') WHERE id = 1;

        -- Indeks GIN untuk pencarian array
        CREATE INDEX idx_tags ON artikel USING GIN (tags);
πŸ’‘ Kapan Array di DB vs Tabel Pivot: Gunakan array jika elemen jarang di-update dan Anda hanya butuh query ANY atau CONTAINS. Jika Anda perlu JOIN ke tabel lain, rekomendasi foreign key, atau agregasi per tag, lebih baik pakai tabel pivot (normalisasi).

🧩 Array di Bahasa Modern β€” Studi Kasus: Transformasi Data

Dalam microservices, data sering dikirim sebagai array JSON. Di backend, kita melakukan transformasi: filter, map, reduce. Contoh di Python:

Python List Manipulation# Data dari API eksternal
        orders = [
        {"product": "Buku", "price": 15000},
        {"product": "Pulpen", "price": 3000},
        {"product": "Buku", "price": 20000}
        ]

        # Total pendapatan dari buku saja
        buku_revenue = sum(o['price'] for o in orders if o['product'] == 'Buku')
        

Di Go, dengan slice dan struct:

Go Slicetype Order struct { Product string; Price int }
        orders := []Order{{"Buku", 15000}, {"Pulpen", 3000}, {"Buku", 20000}}
        var total int
        for _, o := range orders {
        if o.Product == "Buku" { total += o.Price }
        }
πŸ“¦ Array vs Slice di Go: Array memiliki panjang tetap, slice adalah view fleksibel di atas array. Selalu gunakan slice untuk parameter fungsi, karena lebih idiomatis dan aman dari kopi data besar.

πŸ—„οΈ 3. Tipe Data Database β€” Panduan DBA dengan Contoh Nyata

Tambahan penting: bagaimana tipe data dipilih dalam siklus desain database, beserta dampaknya pada storage engine.

3.1 Memilih Auto-Increment vs UUID sebagai Primary Key

πŸ”’ Auto-Increment BIGINT

Kelebihan: Cepat untuk insert berurutan, indeks rapat, mudah dibaca manusia.

Kekurangan: Bocor informasi (ID user terlihat), tidak cocok untuk sistem terdistribusi tanpa koordinasi.

Penggunaan: Hampir semua aplikasi monolitik, ERP internal.

πŸ†” UUID / ULID

Kelebihan: Dapat di-generate di client, unik global, aman diekspos.

Kekurangan: Indeks lebih besar (16 byte vs 8 byte BIGINT), fragmentasi halaman jika tidak diurutkan (gunakan ULID untuk temporal ordering).

Penggunaan: Microservices, aplikasi mobile dengan offline capability, public API.

PostgreSQL ULID vs UUIDCREATE EXTENSION IF NOT EXISTS "uuid-ossp";
        CREATE TABLE orders (
        id       UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
        -- atau ULID (sebagai CHAR(26))
        ulid     CHAR(26) PRIMARY KEY
        );

3.2 JSONB di PostgreSQL β€” Kapan dan Bagaimana

Studi kasus umum: Marketplace menyimpan spesifikasi produk yang berbeda per kategori (baju: ukuran, warna; laptop: RAM, CPU). Daripada membuat puluhan tabel, simpan atribut dalam JSONB dengan indeks GIN. Query: "cari laptop dengan RAM 16GB" tetap cepat.

Index GIN untuk JSONBCREATE INDEX idx_produk_spec ON produk USING GIN (data jsonb_path_ops);
        SELECT * FROM produk
        WHERE data @> '{"spesifikasi": {"ram": 16}}';
⚠️ Anti-pattern: Jangan simpan seluruh payload API eksternal mentah di JSONB lalu melakukan query berat setiap saat. Ekstrak field yang sering di-query ke kolom biasa, sisanya di JSONB.

3.3 Tipe Data Spesial: ENUM, BIT, dan Domain

Penggunaan: ENUM di MySQL/PostgreSQL untuk status yang terdefinisi jelas (misal: ENUM('pending','shipped','delivered')). Keuntungan: penyimpanan efisien (1-2 byte), validasi otomatis. Kerugian: sulit diubah jika perlu menambah status baru tanpa ALTER TABLE.

Alternatif modern: Gunakan SMALLINT dengan tabel referensi (lookup table) untuk fleksibilitas lebih, atau CHECK constraint di PostgreSQL.

3.4 AUTO_INCREMENT / SERIAL / IDENTITY β€” Jantung Surrogate Key

Auto-increment adalah mekanisme database untuk menghasilkan nilai integer unik secara otomatis, biasanya digunakan sebagai primary key pengganti (surrogate key). Setiap RDBMS memiliki sintaks berbeda, namun prinsipnya sama: nilai bertambah secara atomik setiap kali baris baru dimasukkan.

RDBMSSintaks StandarTipe Data PendukungCatatan
MySQL / MariaDBINT AUTO_INCREMENTTINYINT, SMALLINT, INT, BIGINTHanya satu kolom AUTO_INCREMENT per tabel; nilai dapat di-reset dengan ALTER TABLE.
PostgreSQLSERIAL / BIGSERIALINTEGER (SERIAL), BIGINT (BIGSERIAL)SERIAL adalah makro yang membuat SEQUENCE dan menetapkan DEFAULT nextval().
SQL ServerINT IDENTITY(1,1)TINYINT, SMALLINT, INT, BIGINTIDENTITY(seed, increment) – seed nilai awal, increment langkah.
OracleTidak ada AUTO_INCREMENT asli; gunakan SEQUENCE + trigger, atau IDENTITY di Oracle 12c+.

πŸ“ Memilih Tipe Data untuk Auto-Increment

⚠️ Jebakan Overflow: Jika Anda memilih INT untuk tabel log atau event sourcing yang tumbuh cepat, suatu hari sistem akan mogok. Gunakan BIGINT sejak awal untuk tabel yang akan terus tumbuh.
Contoh Auto-Increment di Berbagai DB-- MySQL
        CREATE TABLE transactions (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        amount DECIMAL(12,2)
        );

        -- PostgreSQL
        CREATE TABLE transactions (
        id BIGSERIAL PRIMARY KEY,
        amount NUMERIC(12,2)
        );

        -- SQL Server
        CREATE TABLE transactions (
        id BIGINT IDENTITY(1,1) PRIMARY KEY,
        amount DECIMAL(12,2)
        );

3.5 JOIN β€” Fondasi Query Relasional yang Kuat

JOIN adalah operasi fundamental yang menggabungkan baris dari dua atau lebih tabel berdasarkan kondisi relasi. Pemahaman yang mendalam tentang berbagai tipe JOIN, dampak tipe data, dan strategi optimasi sangat penting untuk performa database.

πŸ”— Jenis-Jenis JOIN dan Kapan Menggunakannya

Tipe JOINDeskripsiHasilContoh Use Case
INNER JOINHanya baris yang cocok di kedua tabelIrisanDapatkan user yang sudah melakukan order
LEFT JOINSemua baris dari tabel kiri, cocokan dari kanan (NULL jika tak ada)Kiri + irisanDaftar semua user beserta order terakhirnya (jika ada)
RIGHT JOINSemua baris dari tabel kananKanan + irisanJarang dipakai; biasanya setara LEFT JOIN dengan urutan tabel dibalik
FULL OUTER JOINGabungan semua baris dari kedua tabelGabunganMencari data yang tidak cocok di kedua sisi (rekonsiliasi)
CROSS JOINProduk Kartesiann Γ— m barisGenerate matrix kombinasi (hati-hati ledakan baris)

πŸ“Œ Penggunaan JOIN dalam Praktik β€” Multi-Table, Self-Join, Non-Equal

1. Multi-Table JOIN: Menggabungkan lebih dari dua tabel untuk laporan kompleks.

Multi-Table JOINSELECT u.name, o.order_date, p.product_name
        FROM users u
        INNER JOIN orders o ON u.id = o.user_id
        INNER JOIN order_items oi ON o.id = oi.order_id
        INNER JOIN products p ON oi.product_id = p.id
        WHERE o.order_date >= CURRENT_DATE - 7;

2. Self-Join: Menghubungkan tabel dengan dirinya sendiri, misal untuk hierarki karyawan-manajer.

Self-JoinSELECT e.name AS employee, m.name AS manager
        FROM employees e
        LEFT JOIN employees m ON e.manager_id = m.id;

3. Non-Equi JOIN: Kondisi selain kesamaan (=), misal rentang atau <.

Non-Equi JOINSELECT a.name, b.name
        FROM products a
        JOIN products b ON a.price < b.price AND a.category_id = b.category_id;

πŸ’£ Masalah Klasik: Tipe Data Kolom JOIN Tidak Cocok

Bayangkan users.id bertipe BIGINT, sedangkan orders.user_id bertipe INT. Saat Anda melakukan JOIN, database mungkin harus mengonversi tipe secara implisit. Akibatnya:

Contoh Masalah JOIN karena Tipe Tidak Cocok-- Tabel users (id BIGINT)
        CREATE TABLE users (
        id BIGINT PRIMARY KEY,
        name VARCHAR(100)
        );
        -- Tabel orders (user_id INT) ❌
        CREATE TABLE orders (
        order_id INT PRIMARY KEY,
        user_id  INT,   -- harusnya BIGINT
        amount   DECIMAL(10,2)
        );
        -- Query JOIN β€” lambat!
        EXPLAIN SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id;
        -- MySQL akan menunjukkan 'Using where; Using index' atau full scan.
⚠️ Perbaikan: Ubah orders.user_id menjadi BIGINT agar cocok dengan users.id. Ini akan mengembalikan penggunaan indeks dan performa optimal.

βœ… Aturan Emas JOIN dan Tipe Data

🎯 Primary Key = Foreign Key

Selalu gunakan tipe data yang persis sama (termasuk signed/unsigned, panjang VARCHAR, charset) untuk primary key dan foreign key yang merujuknya.

πŸ” Indeks Kolom Foreign Key

Buat indeks pada setiap foreign key. Tanpa indeks, operasi JOIN atau ON DELETE CASCADE bisa menjadi bencana performa.

🧹 Hindari JOIN pada Kolom Terkalkulasi

Jangan: ON UPPER(a.name) = UPPER(b.name) β€” indeks tidak akan terpakai. Simpan data dalam bentuk yang sudah dinormalisasi.

πŸ“ VARCHAR JOIN: Perhatikan Charset & Collation

Dua kolom VARCHAR(50) tetapi dengan collation berbeda (utf8mb4_general_ci vs latin1_swedish_ci) tidak bisa di-JOIN tanpa konversi. Pastikan charset identik.

Praktik Benar: JOIN dengan Tipe Cocok-- Perbaikan: orders.user_id BIGINT
        CREATE TABLE orders_new (
        order_id BIGINT PRIMARY KEY,
        user_id  BIGINT NOT NULL,
        amount   DECIMAL(10,2),
        INDEX idx_user (user_id),   -- indeks foreign key
        FOREIGN KEY (user_id) REFERENCES users(id)
        );

        SELECT u.name, o.amount
        FROM users u
        JOIN orders_new o ON u.id = o.user_id;  -- menggunakan indeks

πŸ“Š Visualisasi: Performa JOIN dengan Tipe Cocok vs Tidak Cocok

Pada tabel users 10 juta baris dan orders 50 juta baris, JOIN dengan tipe BIGINT cocok membutuhkan ~2 detik menggunakan indeks. Jika tidak cocok (konversi implisit), query bisa memakan waktu > 2 menit dan membebani CPU.

πŸ’‘ Pro Tip: Gunakan EXPLAIN (MySQL/PostgreSQL) atau SET STATISTICS IO ON (SQL Server) untuk memeriksa apakah JOIN menggunakan indeks. Cari kata "Using index", "Index Scan", vs "Seq Scan" / "Full Table Scan".

πŸ”„ JOIN dengan JSON dan Array di PostgreSQL

Anda bisa melakukan JOIN antara kolom array dengan tabel lain menggunakan UNNEST atau antara data JSONB dengan kolom biasa.

JOIN Array dengan UNNEST-- Tabel produk dengan array kategori
        CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name VARCHAR(50),
        category_ids INT[]
        );
        SELECT p.name, c.category_name
        FROM products p
        CROSS JOIN UNNEST(p.category_ids) AS cat_id
        JOIN categories c ON c.id = cat_id;

πŸ”„ 4. Konversi Tipe Data & Bahaya Implicit Coercion

Detail skenario yang sering menyebabkan bug di production:

KasusContohAkibat
String + Int di JavaScript"5" + 2 β†’ "52"Kesalahan logika total
Perbandingan long vs intJava: Long(128) == Long(128) false (cache -128..127)Bug caching tidak terduga
SQL VARCHAR banding INTWHERE id = '10' (id INT) β€” MySQL mengonversi string ke int, indeks tetap jalan, tapi jika string berisi teks bisa lambatPerforma menurun drastis
Float ke DecimalDecimal(0.1) menghasilkan Decimal('0.1000000000000000055511...')Presisi rusak

Solusi: Selalu eksplisit. Di JavaScript gunakan Number(), parseInt() dengan radix. Di SQL, gunakan CAST atau ::. Di bahasa strongly-typed, percayakan compiler.

πŸ† 5. Best Practice β€” Dari Pengalaman Industri

Berikut adalah kumpulan aturan yang sudah teruji di berbagai sistem production skala besar.

πŸ“ 1. Right-sizing: Hemat Penyimpanan = Hemat Biaya

Di cloud, IOPS dan storage mahal. Memilih INT (4 byte) untuk kolom yang hanya menampung 0-100 adalah pemborosan 3 byte per baris. Untuk 1 miliar baris, itu berarti ~3 GB ruang + backup + replikasi.

πŸ”’ 2. Keuangan: Always DECIMAL

Di sistem perbankan, DECIMAL(18,4) adalah standar. Bahkan untuk e-commerce, gunakan DECIMAL(12,2) untuk harga. Jangan pernah FLOAT.

πŸ“… 3. Waktu: UTC + TIMESTAMPTZ adalah Teman

Aplikasi global (SaaS) wajib menyimpan waktu dalam UTC. PostgreSQL TIMESTAMPTZ menyimpan UTC, menampilkan sesuai sesi timezone. Hindari DATETIME polos tanpa zona.

πŸ”— 4. Foreign Key: Cocokkan Tipe Persis

Jika users.id adalah BIGINT, maka orders.user_id harus BIGINT. Ketidakcocokan menyebabkan full table scan saat join, bahkan jika indeks ada.

πŸ“ 5. String: Jangan VARCHAR(MAX) sembarangan

Batas yang masuk akal mencegah data sampah dan mempercepat indeks. Email 254, nama 100, URL 2048. Gunakan TEXT hanya jika benar-benar perlu.

πŸ—œοΈ 6. JSONB untuk Fleksibilitas, Bukan Pengganti Relasional

Gunakan JSONB untuk atribut yang bervariasi antar entitas (spesifikasi produk). Data yang selalu ada dan di-join (user, order) tetap di tabel normal.

βœ… 7. Constraint Database > Validasi Aplikasi

Tambahkan CHECK (umur >= 0), UNIQUE, FOREIGN KEY di database sebagai lapisan pertahanan terakhir. Aplikasi bisa saja di-bypass.

🧹 8. Hindari NULL yang Tidak Perlu

NULL memperumit agregasi dan logika. Gunakan NOT NULL DEFAULT ... untuk kolom seperti is_active (default true). NULL hanya untuk data yang benar-benar opsional.

πŸ“‹ Cheat Sheet Praktis: Tipe Data untuk Kebutuhan Umum

KebutuhanTipe IdealCatatan
ID unik globalUUID atau BIGSERIALUUID untuk distributed, Serial untuk monolith
Harga produkNUMERIC(12,2)2 desimal cukup untuk sebagian besar mata uang
Stok barangINT atau SMALLINTJangan pakai BIGINT kecuali stok > 2M
Rating (1-5)TINYINT1 byte, validasi CHECK (rating BETWEEN 1 AND 5)
Nama depanVARCHAR(50)Nama terpanjang di dunia ~40 karakter
Alamat emailVARCHAR(254)Indeks unique, lowercase simpan
Log aktivitasTEXTHindari indeks penuh, cukup indeks timestamp
Token JWTVARCHAR(500)Token bisa panjang, jangan CHAR
Gambar (path)VARCHAR(500)Jangan simpan blob di DB, simpan di object storage
Status pesananENUM atau SMALLINT + lookupENUM lebih ketat, lookup lebih fleksibel

βœ… Kesimpulan: Fondasi Aplikasi Kokoh Dimulai dari Tipe Data

Pemilihan tipe data adalah keputusan arsitektural, bukan sekadar teknis. Dengan mengikuti panduan yang telah diperkaya studi kasus di atas, Anda akan menghasilkan sistem yang:

  • Performa optimal sejak hari pertama β€” indeks ramping, query efisien
  • Biaya infrastruktur lebih rendah β€” penyimpanan dan memori lebih hemat
  • Integritas data terjamin β€” tidak ada selisih saldo atau bug zona waktu
  • Mudah di-scale β€” tipe data yang tepat memudahkan sharding dan replikasi
  • Kode yang mendokumentasikan diri sendiri β€” tipe eksplisit menjelaskan domain

β€œDetail kecil dalam tipe data adalah pembeda antara sistem yang bertahan 10 tahun dan yang harus ditulis ulang setelah 6 bulan.”