Tabel adalah tempat data benar-benar hidup, dan mendefinisikannya dengan benar adalah keputusan yang paling lama terasa: struktur yang baik membuat aplikasi nyaman bertahun-tahun, struktur yang asal jadi membuat setiap perubahan kecil jadi mahal. Pada chapter ini kita menyusun tabel pelanggan dan produk untuk Toko Maju Jaya dari nol — menentukan kolom, memilih tipe data dasar, memasang constraint seperti PRIMARY KEY dan NOT NULL, mengatur nilai default, lalu menghadapi dua skenario nyata: nama tabel yang sudah terpakai dan perlindungan IF NOT EXISTS.

Anatomi CREATE TABLE #

Bentuk dasarnya: CREATE TABLE nama_tabel (daftar kolom beserta aturannya). Tabel pertama kita menyimpan pelanggan Toko Maju Jaya dengan empat kolom:

CREATE TABLE pelanggan (
    id     integer PRIMARY KEY,
    nama   text NOT NULL,
    email  text UNIQUE,
    kota   text DEFAULT 'Jakarta'
);

INSERT INTO pelanggan (id, nama, email)
VALUES (1, 'Budi', '[email protected]');

INSERT INTO pelanggan (id, nama, email, kota)
VALUES (2, 'Siti', '[email protected]', 'Bandung');

SELECT id, nama, email, kota
FROM pelanggan
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 1
INSERT 0 1
 id | nama |      email       |  kota   
----+------+------------------+---------
  1 | Budi | [email protected] | Jakarta
  2 | Siti | [email protected] | Bandung
(2 rows)

Mari kita bedah definisi tabel tersebut baris demi baris:

  1. id integer PRIMARY KEY: kolom id menyimpan bilangan bulat dan diangkat menjadi primary key — identitas resmi baris. Server menjamin nilainya unik dan tidak boleh NULL, jadi dua pelanggan tidak akan pernah berbagi id yang sama.
  2. nama text NOT NULL: kolom nama menyimpan teks sepanjang apa pun. NOT NULL melarang nilai kosong — tanpa nama, sebuah baris pelanggan memang tidak masuk akal.
  3. email text UNIQUE: teks yang dijamin berbeda di tiap baris. Perhatikan bahwa UNIQUE tidak sama dengan NOT NULL: kolom ini boleh kosong untuk pelanggan tanpa email, tetapi begitu diisi, nilainya tidak boleh kembar.
  4. kota text DEFAULT 'Jakarta': bila baris baru tidak menyebut nilai kota, kolom ini otomatis terisi 'Jakarta' alih-alih NULL.

Dua INSERT di file itu memperlihatkan dua perilaku tersebut sekaligus: baris Budi tidak menyebut kota sehingga menerima nilai default 'Jakarta', sedangkan baris Siti menyebut 'Bandung' secara eksplisit sehingga defaultnya diabaikan.

Tipe Data Dasar dan Constraint CHECK #

Tabel kedua kita menyimpan produk. Pilihan tipenya menunjukkan keputusan yang umum diambil: integer untuk hitungan, text untuk nama bebas, dan numeric(10, 2) untuk uang — tipe desimal pasti dengan sepuluh digit total dan dua digit di belakang koma, bebas galat pembulatan yang menimpa real saat menjumlahkan rupiah:

CREATE TABLE produk (
    id     integer PRIMARY KEY,
    nama   text NOT NULL,
    harga  numeric(10, 2) NOT NULL CHECK (harga >= 0),
    stok   integer NOT NULL DEFAULT 0
);

INSERT INTO produk (id, nama, harga)
VALUES
    (1, 'Buku Tulis', 4500),
    (2, 'Kertas HVS A4', 55000);

SELECT id, nama, harga, stok
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 2
 id |     nama      |  harga   | stok 
----+---------------+----------+------
  1 | Buku Tulis    |  4500.00 |    0
  2 | Kertas HVS A4 | 55000.00 |    0
(2 rows)

Dua hal baru muncul di tabel ini. Pertama, CHECK (harga >= 0): constraint yang menolak nilai yang tidak lolos pemeriksaan — harga negatif akan ditolak server dengan error, bukan diterima diam-diam. Kedua, stok integer NOT NULL DEFAULT 0: kombinasi yang lazim untuk kolom berhitung — wajib berisi, dan bila tidak disebut saat INSERT, diisi angka nol. Baris Buku Tulis dan Kertas HVS di atas sama-sama tidak menyebut stok, dan keduanya menerima 0.

Rangkaian tipe dan constraint yang kita pakai di dua tabel ini ringkasnya begini:

ElemenPeranContoh
Tipe dataMenentukan jenis nilai yang bisa disimpaninteger, text, numeric(10, 2)
PRIMARY KEYIdentitas baris: unik dan tidak NULLid integer PRIMARY KEY
NOT NULLMelarang nilai kosongnama text NOT NULL
UNIQUEMelarang nilai kembar antar barisemail text UNIQUE
CHECK (...)Menolak nilai yang gagal syaratCHECK (harga >= 0)
DEFAULT ...Nilai pengisi bila kolom tak disebutDEFAULT 'Jakarta'

Daftar tipe data PostgreSQL jauh lebih kaya — tanggal, boolean, JSON, array, dan lainnya — dan tiap-tiap keluarga tipe kita bedah tuntas di Section Tipe Data. Untuk kebutuhan chapter ini, tiga tipe di atas sudah cukup.

Nama Tabel yang Sudah Dipakai #

Sekali sebuah tabel bernama pelanggan ada di schema, nama itu terkunci untuk tabel lain. Mencoba membuat tabel kedua dengan nama yang sama ditolak mentah-mentah — biasanya terjadi ketika script dijalankan dua kali:

CREATE TABLE pelanggan (
    id integer PRIMARY KEY
);

Output yang Dihasilkan:

text
psql:tabel-duplikat.sql:3: ERROR:  relation "pelanggan" already exists

Perhatikan bahwa definisi kolomnya berbeda total dengan tabel pelanggan yang sudah ada — tetapi server bahkan tidak sempat memeriksa isi definisi: penolakan terjadi begitu namanya terbaca, dan error-nya menyebutkan nama relasi yang bentrok. Solusinya ada dua: pilih nama lain, atau gunakan IF NOT EXISTS kalau tujuannya memang “buat bila belum ada”.

IF NOT EXISTS #

Menambahkan IF NOT EXISTS mengubah perilaku CREATE TABLE dari “gagal bila sudah ada” menjadi “lewati dengan tenang bila sudah ada”. Ini berguna untuk script yang sengaja boleh dijalankan berulang kali:

CREATE TABLE IF NOT EXISTS pelanggan (
    id       integer,
    catatan  text
);

SELECT id, nama, email, kota
FROM pelanggan
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
 id | nama |      email       |  kota   
----+------+------------------+---------
  1 | Budi | [email protected] | Jakarta
  2 | Siti | [email protected] | Bandung
(2 rows)

Tabel pelanggan sudah ada, jadi pembuatan tabel dilewati: tag CREATE TABLE tetap tercetak sebagai tanda perintah selesai diproses, dan psql menampilkan NOTICE: relation "pelanggan" already exists, skipping di layar sebagai pemberitahuan bahwa tidak ada tabel baru yang dibuat. Eksekusi file kemudian berlanjut tanpa error. Perhatikan dua hal penting dari SELECT penutup: tabel yang terlewat tidak diubah sama sekali — definisi lamanya (empat kolom lengkap dengan constraint-nya) tetap utuh meski definisi baru di perintah di atas berbeda total; dan IF NOT EXISTS tidak menggantikan DROP TABLE — tabel lama tetap hidup, isinya tetap ada. Perlindungan ini hanya menyatakan “buat kalau belum ada”, bukan “samakan strukturnya”.

Dua tabel kini berdiri lengkap dengan kolom, tipe, dan aturannya. Namun dua minggu kemudian, siapa yang mengingat produk punya kolom harga numeric(10,2) dengan CHECK — atau apakah email boleh NULL? Untuk itu PostgreSQL menyediakan cara memeriksa struktur tabel yang sudah ada, topik chapter Melihat Struktur Tabel.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel-pelanggan.sql

Unduh berkas
sql
CREATE TABLE pelanggan (
    id     integer PRIMARY KEY,
    nama   text NOT NULL,
    email  text UNIQUE,
    kota   text DEFAULT 'Jakarta'
);

INSERT INTO pelanggan (id, nama, email)
VALUES (1, 'Budi', '[email protected]');

INSERT INTO pelanggan (id, nama, email, kota)
VALUES (2, 'Siti', '[email protected]', 'Bandung');

SELECT id, nama, email, kota
FROM pelanggan
ORDER BY id;

if-not-exists.sql

Unduh berkas
sql
CREATE TABLE IF NOT EXISTS pelanggan (
    id       integer,
    catatan  text
);

SELECT id, nama, email, kota
FROM pelanggan
ORDER BY id;

tabel-duplikat.sql

Unduh berkas
sql
CREATE TABLE pelanggan (
    id integer PRIMARY KEY
);

tabel-produk.sql

Unduh berkas
sql
CREATE TABLE produk (
    id     integer PRIMARY KEY,
    nama   text NOT NULL,
    harga  numeric(10, 2) NOT NULL CHECK (harga >= 0),
    stok   integer NOT NULL DEFAULT 0
);

INSERT INTO produk (id, nama, harga)
VALUES
    (1, 'Buku Tulis', 4500),
    (2, 'Kertas HVS A4', 55000);

SELECT id, nama, harga, stok
FROM produk
ORDER BY id;