Topik ini membahas constraint, yaitu aturan yang ditegakkan server pada setiap penulisan data, sehingga data yang tidak sah ditolak tanpa bergantung pada kedisiplinan aplikasi. Pembahasan mencakup NOT NULL, UNIQUE, PRIMARY KEY, CHECK, penamaan constraint, dan satu kejutan semantik NULL pada CHECK yang berperilaku berbeda dari dugaan.
Aturan yang menegakkan dirinya sendiri#
Constraint berbeda dari validasi di aplikasi karena berlaku bagi setiap jalur penulisan, baik INSERT dari aplikasi, UPDATE dari skrip pemeliharaan, maupun data impor massal. Satu aturan yang dideklarasikan sekali bekerja untuk seluruh masa hidup tabel, sedangkan validasi aplikasi hanya setebal jalurnya yang paling rajin.
Setiap constraint dapat diberi nama eksplisit melalui kata CONSTRAINT, dan pemberian nama ini disarankan karena pesan error serta migrasi selanjutnya merujuk namanya. Nama yang dibuat otomatis seperti anggota_email_key sah, tetapi tidak menceritakan maksudnya.
NOT NULL, UNIQUE, dan PRIMARY KEY#
NOT NULL memaksa kolom terisi pada setiap baris, UNIQUE melarang dua baris bernilai sama, dan PRIMARY KEY menggabungkan keduanya lalu menyiapkan index untuk rujukan cepat. UNIQUE pada beberapa kolom menegakkan keunikan gabungan, bukan keunikan per kolom, sehingga cocok untuk aturan seperti satu kursi satu gedung.
# keunikan gabungan dua kolom
psql -U postgres -d perpustakaan <<'SQL'
CREATE TABLE kursi (
id SERIAL PRIMARY KEY,
gedung TEXT NOT NULL,
nomor INTEGER NOT NULL,
CONSTRAINT kursi_tempat_unik UNIQUE (gedung, nomor)
);
INSERT INTO kursi (gedung, nomor) VALUES ('Gedung A', 1), ('Gedung B', 1);
SELECT gedung, nomor FROM kursi ORDER BY gedung;
INSERT INTO kursi (gedung, nomor) VALUES ('Gedung A', 1);
DROP TABLE kursi;
SQLCREATE TABLE
INSERT 0 2
gedung | nomor
----------+-------
Gedung A | 1
Gedung B | 1
(2 rows)
ERROR: duplicate key value violates unique constraint "kursi_tempat_unik"
DETAIL: Key (gedung, nomor)=(Gedung A, 1) already exists.
DROP TABLEDua nomor yang sama diterima karena gedungnya berbeda, sedangkan pasangan yang persis sama ditolak. Pesan errornya menyebut nama constraint kursi_tempat_unik, manfaat langsung dari penamaan eksplisit.
CHECK untuk aturan domain#
CHECK menegakkan ekspresi boolean atas satu baris, misalnya rentang tahun terbit atau nilai yang tidak boleh negatif. Aturan ini ditulis sekali pada definisi tabel dan menolak setiap baris yang melanggarnya.
# CHECK menolak tahun terbit di luar rentang
psql -U postgres -d perpustakaan <<'SQL'
CREATE TABLE buku_uji (
id SERIAL PRIMARY KEY,
judul TEXT NOT NULL,
tahun INTEGER NOT NULL CONSTRAINT tahun_wajar CHECK (tahun BETWEEN 1500 AND 2100)
);
INSERT INTO buku_uji (judul, tahun) VALUES ('Koleksi Lama', 1543);
INSERT INTO buku_uji (judul, tahun) VALUES ('Koleksi Masa Depan', 2201);
DROP TABLE buku_uji;
SQLCREATE TABLE
INSERT 0 1
ERROR: new row for relation "buku_uji" violates check constraint "tahun_wajar"
DETAIL: Failing row contains (2, Koleksi Masa Depan, 2201).
DROP TABLESatu kejutan semantik perlu digarisbawahi, yaitu CHECK yang diberi nilai NULL dianggap lolos. Ekspresi tahun > 1500 terhadap NULL menghasilkan NULL, dan server memperlakukannya sebagai bukan pelanggaran, sehingga aturan rentang yang serius selalu berpasangan dengan NOT NULL pada kolom yang sama.
Menambahkan constraint pada tabel yang berisi data#
Constraint juga ditambahkan belakangan melalui ALTER TABLE, dan PostgreSQL menyediakan mode NOT VALID untuk tabel besar, yaitu aturan yang langsung berlaku pada penulisan baru sementara pemeriksaan data lama dijalankan terpisah dengan VALIDATE CONSTRAINT. Pola ini menambahkan perlindungan tanpa menahan tabel dalam waktu lama, dan rinciannya menyusul pada topik ALTER TABLE.
Apabila ada baris lama yang melanggar aturan baru, penambahannya gagal dan server menyebut baris pelanggarnya, sehingga pembersihan data dilakukan lebih dahulu. Urutan yang sehat adalah membersihkan data, menambahkan constraint, lalu membiarkan server menjaga seterusnya.
Kesalahan umum#
Kesalahan pertama adalah mengandalkan validasi aplikasi tanpa constraint, sehingga satu skrip pemeliharaan yang lalai cukup untuk mencemari tabel. Kesalahan kedua adalah membiarkan CHECK tanpa pasangan NOT NULL, karena NULL diam-diam lolos dari aturan rentang seperti dijelaskan di atas. Kesalahan ketiga adalah menulis CHECK yang merujuk kolom tabel lain, yang tidak diperbolehkan CHECK, sedangkan aturan lintas tabel adalah wilayah foreign key atau trigger.
Kesalahan keempat adalah menonaktifkan constraint demi kecepatan impor tanpa rencana validasi ulang, sehingga tabel menampung data yang tidak sah tanpa alarm. Impor massal yang benar memasukkan data dengan constraint tetap aktif, atau menonaktifkan sementara lalu memvalidasi ulang sebelum tabel dibuka kembali.
Ringkasan#
- Constraint ditegakkan server pada setiap jalur penulisan, dan namanya dideklarasikan eksplisit agar error dan migrasi menceritakan maksudnya.
- NOT NULL, UNIQUE, dan PRIMARY KEY menjaga kehadiran serta keunikan, dengan keunikan gabungan untuk aturan kombinasi.
- CHECK menegakkan ekspresi boolean per baris, dan CHECK terhadap NULL dianggap lolos sehingga selalu berpasangan dengan NOT NULL.
- Mode NOT VALID dengan VALIDATE CONSTRAINT menambahkan aturan pada tabel besar tanpa menahannya lama.
Referensi#
- Constraints — PostgreSQL Documentation — Bab resmi check, unique, primary key, dan foreign key.
- CREATE TABLE — PostgreSQL Documentation — Sintaks deklarasi constraint pada pembuatan tabel.
- ALTER TABLE — PostgreSQL Documentation — Penambahan constraint dan opsi NOT VALID.