Uang: Jangan Pernah Pakai float
SELECT 0.1::float8 + 0.2::float8 = 0.3::float8; -- false SELECT 0.1::numeric + 0.2::numeric = 0.3::numeric; -- true
Tipe real dan double precision adalah bilangan biner mengambang yang tidak bisa merepresentasikan pecahan desimal secara tepat. Untuk uang, gunakan numeric(15,2) — atau, yang sering lebih baik, simpan sebagai bilangan bulat dalam satuan terkecil memakai bigint. Cara kedua menghilangkan seluruh pertanyaan pembulatan.
Waktu: timestamptz, Selalu
| Tipe | Menyimpan zona waktu? | Pakai untuk |
|---|---|---|
timestamp | Tidak | Hampir tidak pernah |
timestamptz | Ya (dinormalisasi ke UTC) | Semua stempel waktu |
date | — | Tanggal lahir, tanggal jatuh tempo |
interval | — | Durasi |
timestamptz tidak menyimpan zona waktunya. Ia mengonversi nilai masuk ke UTC, menyimpannya, lalu mengonversi kembali ke zona waktu sesi saat dibaca. Itulah perilaku yang hampir selalu Anda inginkan. Sedangkan timestamp polos menyimpan angka apa adanya tanpa konteks — dua sistem di zona berbeda akan menafsirkannya berbeda, dan bug seperti ini baru ketahuan saat pergantian waktu musim panas.
SET timezone = 'Asia/Jakarta'; SELECT now(); -- 2026-07-28 14:30:00+07 SELECT now() AT TIME ZONE 'UTC'; -- 2026-07-28 07:30:00 -- Rentang waktu yang BENAR (hindari BETWEEN untuk timestamp) WHERE dibuat_pada >= '2026-07-01' AND dibuat_pada < '2026-08-01'
Kunci Utama: bigint atau UUID?
bigint identity | uuid v4 | uuid v7 | |
|---|---|---|---|
| Ukuran | 8 byte | 16 byte | 16 byte |
| Bisa dibuat klien | Tidak | Ya | Ya |
| Bisa ditebak | Ya | Tidak | Sebagian (waktu) |
| Lokalitas indeks | Sangat baik | Buruk | Sangat baik |
UUID v4 sepenuhnya acak, sehingga setiap penyisipan mendarat di posisi acak dalam B-tree. Akibatnya halaman indeks terus-menerus terpecah dan cache menjadi tidak efektif — pada tabel besar, penyisipan bisa jauh lebih lambat daripada kunci berurutan.
UUID v7 menyelesaikan ini dengan menaruh stempel waktu di bagian depan, sehingga nilainya menaik seperti bilangan berurutan namun tetap sulit ditebak. Bila butuh ID yang dibuat di sisi klien, pilih v7.
-- Kunci berurutan gaya modern
CREATE TABLE pesanan (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
...
);
-- Lebih baik daripada serial: mengikuti standar SQL
-- dan tidak bisa ditimpa nilai manual secara tidak sengaja.
Teks
-- Ketiganya disimpan dengan cara yang SAMA di PostgreSQL text -- gunakan ini secara bawaan varchar(n) -- hanya bila batas panjang memang aturan bisnis char(n) -- jangan pakai: dipadding spasi
Di PostgreSQL, varchar(255) tidak lebih cepat atau lebih hemat daripada text. Batas panjang hanya menambahkan pemeriksaan constraint. Pakai text, lalu tambahkan CHECK (length(kolom) <= 100) bila memang ada aturan bisnisnya — constraint semacam itu jauh lebih mudah diubah daripada tipe kolom.
JSONB
CREATE TABLE peristiwa (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
jenis text NOT NULL,
muatan jsonb NOT NULL
);
CREATE INDEX idx_muatan ON peristiwa USING gin (muatan);
SELECT * FROM peristiwa WHERE muatan @> '{"status":"gagal"}';
SELECT muatan->>'pengguna_id' FROM peristiwa;
-- Indeks pada satu jalur saja: jauh lebih kecil
CREATE INDEX idx_pengguna ON peristiwa ((muatan->>'pengguna_id'));
JSONB sangat berguna untuk data yang benar-benar tidak berstruktur: muatan webhook, atribut produk yang berbeda-beda per kategori, konfigurasi. Ia bukan pengganti kolom untuk data yang strukturnya Anda ketahui. Kolom biasa memberi Anda constraint, tipe yang ditegakkan, statistik perencana yang akurat, dan indeks yang jauh lebih efisien.
Tipe Lain yang Sering Terlupakan
-- Rentang: sangat berguna untuk jadwal & harga berjangka
CREATE TABLE pemesanan (
kamar_id int,
masa tstzrange,
EXCLUDE USING gist (kamar_id WITH =, masa WITH &&)
);
-- Constraint di atas MENCEGAH pemesanan yang bertumpang tindih
-- di tingkat basis data — tidak mungkin ada balapan.
-- Array
tag text[]
WHERE 'promo' = ANY(tag)
-- Alamat jaringan
alamat inet, jaringan cidr
-- Enum: menambah nilai mudah, MENGHAPUS sulit
CREATE TYPE status_pesanan AS ENUM ('menunggu','dibayar','dikirim');
Enum hemat ruang dan cepat, tetapi mengubah atau menghapus nilainya merepotkan. Tabel referensi dengan foreign key lebih fleksibel: nilai bisa ditambah, dinonaktifkan, diberi label tampilan dan urutan. Untuk himpunan yang benar-benar stabil, enum cukup; untuk yang mungkin berkembang, tabel referensi lebih aman.