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:
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:
id integer PRIMARY KEY: kolomidmenyimpan bilangan bulat dan diangkat menjadi primary key — identitas resmi baris. Server menjamin nilainya unik dan tidak boleh NULL, jadi dua pelanggan tidak akan pernah berbagiidyang sama.nama text NOT NULL: kolomnamamenyimpan teks sepanjang apa pun.NOT NULLmelarang nilai kosong — tanpa nama, sebuah baris pelanggan memang tidak masuk akal.email text UNIQUE: teks yang dijamin berbeda di tiap baris. Perhatikan bahwaUNIQUEtidak sama denganNOT NULL: kolom ini boleh kosong untuk pelanggan tanpa email, tetapi begitu diisi, nilainya tidak boleh kembar.kota text DEFAULT 'Jakarta': bila baris baru tidak menyebut nilaikota, 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:
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:
| Elemen | Peran | Contoh |
|---|---|---|
| Tipe data | Menentukan jenis nilai yang bisa disimpan | integer, text, numeric(10, 2) |
PRIMARY KEY | Identitas baris: unik dan tidak NULL | id integer PRIMARY KEY |
NOT NULL | Melarang nilai kosong | nama text NOT NULL |
UNIQUE | Melarang nilai kembar antar baris | email text UNIQUE |
CHECK (...) | Menolak nilai yang gagal syarat | CHECK (harga >= 0) |
DEFAULT ... | Nilai pengisi bila kolom tak disebut | DEFAULT '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:
psql:tabel-duplikat.sql:3: ERROR: relation "pelanggan" already existsPerhatikan 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:
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 berkasCREATE 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 berkasCREATE TABLE IF NOT EXISTS pelanggan (
id integer,
catatan text
);
SELECT id, nama, email, kota
FROM pelanggan
ORDER BY id;
tabel-duplikat.sql
Unduh berkasCREATE TABLE pelanggan (
id integer PRIMARY KEY
);
tabel-produk.sql
Unduh berkasCREATE 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;