Kita sudah tahu bahwa PostgreSQL menyimpan data dalam tabel, tetapi belum membahas mengapa bentuk tabel dipilih sebagai fondasi. Model relational lahir dari masalah nyata: ketika data tersebar di banyak file terpisah, informasi yang sama tersalin berkali-kali, dan satu perubahan kecil bisa membuat salinan-salinan itu saling bertentangan. Pada chapter ini kita bedah cara kerja model relational — tabel, baris, kolom, primary key, foreign key — lalu membuktikannya dengan dua tabel kecil yang saling berelasi di PostgreSQL.
Model Relational: Tabel, Baris, dan Kolom #
Model relational, dirumuskan oleh Edgar F. Codd pada tahun 1970, menyimpan data sebagai kumpulan relation — dalam praktiknya kita mengenalnya sebagai tabel. Setiap tabel menggambarkan satu jenis entitas: penulis, buku, pelanggan, atau pesanan. Sebelum istilah-istilahnya beredar lebih jauh, mari kita sepakati kosakatanya:
| Istilah | Artinya dalam tabel |
|---|---|
| Tabel (relation) | Wadah untuk satu jenis entitas, misalnya data semua penulis |
| Kolom (attribute) | Satu properti entitas, misalnya nama atau email, lengkap dengan tipe datanya |
| Baris (tuple) | Satu entitas nyata, misalnya satu penulis bernama Budi Santoso |
| Domain | Himpunan nilai yang sah untuk satu kolom, misalnya bilangan bulat untuk id |
Setiap kolom memiliki tipe data yang tetap — teks, bilangan, tanggal — sehingga server bisa menolak nilai yang tidak masuk akal sejak awal. Kita tidak menyimpan angka harga di kolom teks dan berdoa semua baris konsisten; janji tipe ini adalah baris pertama pertahanan integritas data.
Satu kekuatan lain yang jarang disadari pemula: SQL adalah bahasa deklaratif. Kita menyatakan data apa yang ingin didapat (SELECT judul FROM buku WHERE harga < 100000), dan PostgreSQL yang memutuskan cara mengambilnya. Detail bagaimana — urutan baca, pemakaian index, penyortiran — menjadi urusan server, bukan program kita.
Primary Key dan Foreign Key: Merajut Relasi #
Tabel terpisah baru berguna kalau bisa saling terhubung. Dua constraint mengatur hubungan itu. Primary key adalah kolom (atau gabungan kolom) yang menjadi identitas resmi satu baris: nilainya unik, tidak boleh NULL, dan satu tabel hanya punya satu primary key. Foreign key adalah kolom di tabel kedua yang merujuk primary key tabel pertama, sehingga baris kedua “milik” baris pertama.
Relasi paling umum adalah one-to-many: satu penulis boleh menulis banyak buku, tetapi satu buku ditulis oleh satu penulis (dalam contoh kita). Tabel penulis menjadi sisi “satu”, tabel buku menjadi sisi “banyak”, dan kolom buku.penulis_id menjadi jembatannya. Skema seperti ini biasa disebut normalisasi: data penulis disimpan sekali saja di tabelnya, lalu dirujuk dari mana-mana — tidak ada nama yang tersalin di setiap baris buku.
Membangun Dua Tabel Berrelasi #
Saatnya membuktikan teori di atas dengan kode. File berikut membuat dua tabel untuk katalog buku: penulis berdiri sendiri, sedangkan buku merujuk ke penulis lewat foreign key:
CREATE TABLE penulis (
id integer PRIMARY KEY,
nama text NOT NULL,
email text NOT NULL UNIQUE
);
CREATE TABLE buku (
id integer PRIMARY KEY,
judul text NOT NULL,
harga numeric(10, 2) NOT NULL,
tahun_terbit integer NOT NULL,
penulis_id integer NOT NULL REFERENCES penulis (id)
);Output yang Dihasilkan:
CREATE TABLE
CREATE TABLEMari kita bedah definisi kedua tabel baris per baris:
- Baris 2-5 (
CREATE TABLE penulis ...): tabelpenulispunya tiga kolom.idbertandaPRIMARY KEY— unik dan wajib diisi.namawajib diisi (NOT NULL).emailjuga wajib diisi dan ditambahUNIQUEsehingga dua penulis tidak boleh berbagi satu alamat email. - Baris 7-13 (
CREATE TABLE buku ...): tabelbukumenyimpanjudul,hargabertipenumeric(10, 2)untuk rupiah,tahun_terbit, dan kolom relasipenulis_id. - Baris 12 (
penulis_id ... REFERENCES penulis (id)): inilah foreign key. Mulai detik tabel ini dibuat, PostgreSQL menjaga setiap nilaipenulis_idtetap merujuk barispenulisyang sungguh ada. Constraint seperti ini menerima nama otomatis, misalnyabuku_penulis_id_fkey.
Mengisi Data dan Menyambungkannya dengan JOIN #
Tabel kosong belum memperlihatkan relasi apa pun. File berikut mengisi dua penulis dan tiga buku, lalu menampilkan isi tabel penulis sebagai pemeriksaan:
INSERT INTO penulis (id, nama, email) VALUES
(1, 'Budi Santoso', '[email protected]'),
(2, 'Siti Rahayu', '[email protected]');
INSERT INTO buku (id, judul, harga, tahun_terbit, penulis_id) VALUES
(1, 'Belajar Database untuk Pemula', 89000.00, 2023, 1),
(2, 'Pola Desain Aplikasi Modern', 120000.00, 2024, 1),
(3, 'Analisis Data dengan SQL', 95000.00, 2025, 2);
SELECT id, nama, email
FROM penulis
ORDER BY id;Output yang Dihasilkan:
INSERT 0 2
INSERT 0 3
id | nama | email
----+--------------+------------------
1 | Budi Santoso | [email protected]
2 | Siti Rahayu | [email protected]
(2 rows)Perhatikan kolom penulis_id pada data buku: buku 1 dan 2 merujuk 1 (Budi Santoso), buku 3 merujuk 2 (Siti Rahayu). Nomor-nomor ini tidak bermakna bagi pembaca manusia — maknanya muncul ketika kedua tabel disambungkan. Tugas penyambungan itu dipegang oleh JOIN:
SELECT b.id, b.judul, b.harga, p.nama AS penulis
FROM buku b
JOIN penulis p ON p.id = b.penulis_id
ORDER BY b.id;Output yang Dihasilkan:
id | judul | harga | penulis
----+-------------------------------+-----------+--------------
1 | Belajar Database untuk Pemula | 89000.00 | Budi Santoso
2 | Pola Desain Aplikasi Modern | 120000.00 | Budi Santoso
3 | Analisis Data dengan SQL | 95000.00 | Siti Rahayu
(3 rows)Hasilnya satu tabel gabungan: setiap buku tampil lengkap dengan nama penulisnya, persis seperti yang pembaca laporan harapkan. Bedah singkatnya:
- Baris 1 (
SELECT b.id, ... p.nama AS penulis): kolom hasil diambil dari dua tabel sekaligus; aliasAS penulismemberi nama kolom hasil gabungan. - Baris 2 (
FROM buku b): tabelbukumenjadi titik awal, diberi alias pendekb. - Baris 3 (
JOIN penulis p ON p.id = b.penulis_id): untuk setiap barisbuku, PostgreSQL mencari barispenulisyangid-nya sama denganb.penulis_id. Syarat penyambungan inilah relasi yang kita rancang lewat foreign key, kini terlihat sebagai query. - Baris 4 (
ORDER BY b.id): hasil diurutkan agar output stabil dan mudah dibaca.
Integritas yang Menjaga Dirinya Sendiri #
Relasi di atas terlihat rapi karena datanya kita isi dengan benar. Pertanyaannya: apa yang terjadi kalau aplikasi mencoba menyisipkan buku dengan penulis_id yang tidak ada? File berikut sengaja melakukannya — dan kita biarkan gagal:
INSERT INTO buku (id, judul, harga, tahun_terbit, penulis_id)
VALUES (4, 'Buku Tanpa Penulis', 50000.00, 2026, 99);Output yang Dihasilkan:
psql:uji-foreign-key.sql:2: ERROR: insert or update on table "buku" violates foreign key constraint "buku_penulis_id_fkey"
DETAIL: Key (penulis_id)=(99) is not present in table "penulis".Dua bagian pesan galat itu layak dibaca pelan-pelan. Baris ERROR menyebut constraint yang dilanggar — buku_penulis_id_fkey, nama otomatis dari foreign key kolom penulis_id. Baris DETAIL menyebut persis pasangan yang bermasalah: nilai 99 pada kolom penulis_id tidak ada di tabel penulis. Aplikasi yang menerima galat ini tahu tepat apa yang harus diperbaiki; tidak ada data hantu yang lolos ke penyimpanan.
Mengapa Model Relational Masih Dominan #
Lebih dari lima puluh tahun sejak paper Codd, model relational tetap menjadi default penyimpanan data terstruktur — bukan karena tidak ada pesaing, melainkan karena tiga hal: integritas yang ditegakkan di dalam engine (tipe, primary key, foreign key), bahasa query deklaratif yang standar (SQL), dan fleksibilitas skema yang cukup untuk hampir semua domain. PostgreSQL menambahkan tipe data modern seperti JSONB dan array di atas fondasi yang sama, sehingga data semi-terstruktur pun ikut menikmati jaminan integritas.
Sekarang fondasi teoretisnya sudah kokoh. Pertanyaan praktis berikutnya yang biasanya muncul: kalau MySQL juga relational dan open source, kenapa memilih PostgreSQL? Chapter PostgreSQL vs MySQL menjawabnya secara adil.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkasCREATE TABLE penulis (
id integer PRIMARY KEY,
nama text NOT NULL,
email text NOT NULL UNIQUE
);
CREATE TABLE buku (
id integer PRIMARY KEY,
judul text NOT NULL,
harga numeric(10, 2) NOT NULL,
tahun_terbit integer NOT NULL,
penulis_id integer NOT NULL REFERENCES penulis (id)
);
isi-data.sql
Unduh berkasINSERT INTO penulis (id, nama, email) VALUES
(1, 'Budi Santoso', '[email protected]'),
(2, 'Siti Rahayu', '[email protected]');
INSERT INTO buku (id, judul, harga, tahun_terbit, penulis_id) VALUES
(1, 'Belajar Database untuk Pemula', 89000.00, 2023, 1),
(2, 'Pola Desain Aplikasi Modern', 120000.00, 2024, 1),
(3, 'Analisis Data dengan SQL', 95000.00, 2025, 2);
SELECT id, nama, email
FROM penulis
ORDER BY id;
join-buku.sql
Unduh berkasSELECT b.id, b.judul, b.harga, p.nama AS penulis
FROM buku b
JOIN penulis p ON p.id = b.penulis_id
ORDER BY b.id;
uji-foreign-key.sql
Unduh berkasINSERT INTO buku (id, judul, harga, tahun_terbit, penulis_id)
VALUES (4, 'Buku Tanpa Penulis', 50000.00, 2026, 99);