PostgreSQL Data Types — Database

Tipe Data Unik PostgreSQL PostgreSQL memiliki tipe data yang lebih kaya dibandingkan database lain. Fitur-fitur ini membuatnya sangat powerful. JSONB…

PostgreSQL punya tipe data bawaan yang gak dimiliki semua database: JSONB, array, UUID, ENUM, range dan timestamptz. Bayangin rak bumbu. Garam, kecap dan minyak bisa saja dimasukkan ke kantong plastik semua, tapi menuang kecap dari kantong plastik itu berantakan. Di database, kantong plastik itu TEXT: muat apa saja, tapi database gak bisa memeriksa isinya, mencari di dalamnya, atau memasang index yang pas. Tipe yang tepat itu wadah yang pas.

🧪 Tebakan cepat: aplikasi Kedai Senja tanpa sengaja mengirim {"topping": "keju", "topping": "cokelat"}, dengan key yang sama dua kali. Teks ini disimpan ke kolom JSON dan ke kolom JSONB. Saat dibaca lagi, apa isi masing-masing? Tebak dulu, jawabannya ada di bagian "JSON dan JSONB".

JSON dan JSONB

Kolom metadata menyimpan detail menu yang beda-beda bentuknya: minuman punya suhu dan gula, roti punya topping.

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  metadata JSONB
);
INSERT INTO products (name, metadata) VALUES
  ('Kopi Susu', '{"suhu":"dingin",  "gula": "normal"}'),
  ('Kopi Susu Aren', '{"suhu": "dingin", "gula": "aren"}'),
  ('Teh Tarik', '{"gula": "normal", "suhu": "panas"}'),
  ('Roti Bakar', '{"topping": "keju", "topping": "cokelat"}');

PostgreSQL punya dua tipe untuk JSON, dan bedanya ada di cara menyimpan:

JSON JSONB
Disimpan sebagai teks mentah, persis seperti dikirim biner, sudah diurai
Spasi dan urutan key dipertahankan dirapikan, key diurutkan
Key kembar dua-duanya disimpan yang terakhir saja
Operator @> dan index GIN gak bisa bisa

Jadi kolom JSON menyimpan Roti Bakar persis {"topping": "keju", "topping": "cokelat"}, sedangkan JSONB menyimpan {"topping": "cokelat"}. Itu jawaban tebakannya. Kopi Susu di kolom JSONB jadi {"gula": "normal", "suhu": "dingin"}. JSONB sedikit lebih lambat saat ditulis (harus diurai dulu), tapi jauh lebih cepat saat di-query.

CREATE INDEX products_metadata_idx ON products USING GIN (metadata);

SELECT name, metadata ->> 'gula' AS gula
FROM products
WHERE metadata @> '{"suhu": "dingin"}';
-- Kopi Susu | normal
-- Kopi Susu Aren | aren

Di kolom JSON, query yang sama ditolak: ERROR: operator does not exist: json @> unknown.

Array

Kolom bisa menyimpan daftar nilai sekaligus. TEXT[] artinya array berisi teks:

ALTER TABLE products ADD COLUMN tags TEXT[];
UPDATE products SET tags = ARRAY['kopi', 'susu', 'aren'] WHERE name = 'Kopi Susu Aren';

SELECT name, tags FROM products WHERE 'susu' = ANY(tags);
SELECT name, tags FROM products WHERE tags @> ARRAY['kopi', 'aren'];

Array pas untuk daftar pendek yang gak butuh data tambahan, misalnya tag atau peran (roles). Kalau tiap item punya kolom sendiri (harga, jumlah), buat tabel terpisah.

UUID

UUID adalah id 128 bit, ditulis sebagai 36 karakter seperti 3f8a2c1e-9b4d-4e7a-8c2f-1d5b6a7e9f00. PostgreSQL punya tipe UUID asli yang menyimpannya dalam 16 byte, sedangkan teks yang sama di VARCHAR(36) butuh 37 byte dan lebih lambat dibandingkan.

CREATE TABLE orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id INTEGER NOT NULL,
  total INTEGER NOT NULL
);

ENUM dan range

ENUM adalah tipe dengan daftar nilai tetap:

CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped');
ALTER TABLE orders ADD COLUMN status order_status DEFAULT 'pending';
-- status = 'dikirim'  ->  ERROR:  invalid input value for enum order_status: "dikirim"

Pas untuk status dan kategori yang jarang berubah. Menambah nilai gampang (ALTER TYPE order_status ADD VALUE 'delivered';), menghapusnya gak bisa langsung.

Range menyimpan rentang dalam satu kolom, misalnya tstzrange untuk rentang waktu. Pas untuk booking dan jadwal, karena PostgreSQL bisa menolak rentang yang bertabrakan:

CREATE EXTENSION btree_gist;  -- supaya table_id WITH = bisa masuk index GIST

CREATE TABLE bookings (
  id SERIAL PRIMARY KEY,
  table_id INTEGER NOT NULL,
  during TSTZRANGE NOT NULL,
  EXCLUDE USING GIST (table_id WITH =, during WITH &&)
);

Booking meja yang sama di jam yang bertabrakan ditolak dengan conflicting key value violates exclusion constraint.

📌 timestamp dan timestamptz

timestamp menyimpan tanggal dan jam tanpa zona: 2026-10-08 19:00:00 itu jam 7 malam di mana? Gak ada yang tahu. timestamptz menyimpan satu titik waktu (dalam UTC) dan menampilkannya di zona waktu sesi: pesanan jam 19.00 WIB tampil 2026-10-08 19:00:00+07 di Jakarta, 2026-10-08 20:00:00+08 dengan SET TIME ZONE 'Asia/Makassar', dan 2026-10-08 12:00:00+00 di UTC. Untuk waktu kejadian (pesanan dibuat, pembayaran masuk), pakai timestamptz.

Memilih tipe

Kebutuhan Tipe
settings, metadata JSONB
tags, roles ARRAY
public id, distributed UUID
status, kategori ENUM
booking, schedule RANGE
lainnya, misalnya nama depan VARCHAR

⚠️ Jebakan yang sering ketemu

TL;DR: JSONB menyimpan JSON dalam bentuk biner yang sudah diurai (spasi dirapikan, key kembar tinggal yang terakhir), bisa di-query dengan ->> dan @>, dan bisa diberi index GIN; JSON cuma teks mentah. Array (TEXT[]) untuk daftar pendek seperti tags dan roles, dicari dengan ANY dan @>. UUID adalah id 128 bit, disimpan 16 byte, gak bisa ditebak, dibuat dengan gen_random_uuid() (atau uuidv7() di PostgreSQL 18). ENUM untuk daftar nilai tetap seperti status, range untuk booking dan jadwal, dan timestamptz untuk waktu kejadian. Sisanya, VARCHAR atau TEXT biasa.

Yang akan kamu pelajari