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:

IstilahArtinya 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
DomainHimpunan 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:

sql buat-tabel.sql
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:

text
CREATE TABLE
CREATE TABLE

Mari kita bedah definisi kedua tabel baris per baris:

  1. Baris 2-5 (CREATE TABLE penulis ...): tabel penulis punya tiga kolom. id bertanda PRIMARY KEY — unik dan wajib diisi. nama wajib diisi (NOT NULL). email juga wajib diisi dan ditambah UNIQUE sehingga dua penulis tidak boleh berbagi satu alamat email.
  2. Baris 7-13 (CREATE TABLE buku ...): tabel buku menyimpan judul, harga bertipe numeric(10, 2) untuk rupiah, tahun_terbit, dan kolom relasi penulis_id.
  3. Baris 12 (penulis_id ... REFERENCES penulis (id)): inilah foreign key. Mulai detik tabel ini dibuat, PostgreSQL menjaga setiap nilai penulis_id tetap merujuk baris penulis yang sungguh ada. Constraint seperti ini menerima nama otomatis, misalnya buku_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:

sql isi-data.sql
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:

text
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:

sql join-buku.sql
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:

text
 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:

  1. Baris 1 (SELECT b.id, ... p.nama AS penulis): kolom hasil diambil dari dua tabel sekaligus; alias AS penulis memberi nama kolom hasil gabungan.
  2. Baris 2 (FROM buku b): tabel buku menjadi titik awal, diberi alias pendek b.
  3. Baris 3 (JOIN penulis p ON p.id = b.penulis_id): untuk setiap baris buku, PostgreSQL mencari baris penulis yang id-nya sama dengan b.penulis_id. Syarat penyambungan inilah relasi yang kita rancang lewat foreign key, kini terlihat sebagai query.
  4. 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:

text
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 berkas
sql
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)
);

isi-data.sql

Unduh berkas
sql
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;

join-buku.sql

Unduh berkas
sql
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;

uji-foreign-key.sql

Unduh berkas
sql
INSERT INTO buku (id, judul, harga, tahun_terbit, penulis_id)
VALUES (4, 'Buku Tanpa Penulis', 50000.00, 2026, 99);