Primary key menjaga identitas, UNIQUE menjaga keunikan, dan NOT NULL menjaga keterisian — tetapi semuanya diam terhadap nilai yang salah secara logika bisnis: harga -2500, stok minus, atau diskon yang lebih besar dari harga itu sendiri. Validasi semacam itu biasa diletakkan di aplikasi, namun aplikasi bisa punya banyak pintu masuk (form web, import CSV, skrip perbaikan data) yang masing-masing harus ingat aturannya. CHECK menempelkan validasi ke tabel itu sendiri: server menolak setiap baris yang melanggar kondisi yang kita rumuskan. Pada chapter ini kita akan menegakkan harga dan stok non-negatif dengan CHECK, membedakan penempatannya di level kolom dan level tabel, lalu mengenal batasannya — fungsi nondeterministik dan subquery tidak boleh masuk ke dalam ekspresi check.
Menegakkan Nilai yang Wajar #
Sejak MySQL 8.0.16, CHECK benar-benar ditegakkan (enforced): pelanggaran berarti galat dan baris ditolak. Kita buktikan pada tabel produk toko alat tulis:
CREATE DATABASE belajar_check;
USE belajar_check;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE produk (
id INT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
harga DECIMAL(10,2) NOT NULL CHECK (harga >= 0),
stok INT NOT NULL DEFAULT 0 CHECK (stok >= 0)
);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)Mari kita bedah dua baris constraint tersebut:
- Baris 4 (
harga DECIMAL(10,2) NOT NULL CHECK (harga >= 0)): KlausaCHECK (harga >= 0)menempel langsung pada definisi kolom — disebut column-level check. Setiap baris baru harus lolos ujiharga >= 0sebelum tersimpan. - Baris 5 (
stok INT NOT NULL DEFAULT 0 CHECK (stok >= 0)): Pola yang sama untuk stok, digabung denganNOT NULLdanDEFAULTdari chapter sebelumnya — constraint boleh bertumpuk pada satu kolom.
Karena kedua check ini tidak kita beri nama, server menamainya otomatis produk_chk_1 dan produk_chk_2 — nama itu akan muncul dalam pesan galat. Baris yang benar tentu saja lolos:
INSERT INTO produk (id, nama, harga, stok) VALUES (1, 'Buku Tulis 58', 4500.00, 10);Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Pelanggaran CHECK Ditolak #
Sekarang barang kedua dikirim dengan harga negatif:
-- @error
INSERT INTO produk (id, nama, harga, stok) VALUES (2, 'Pensil Kayu 2B', -2500.00, 5);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 3819 (HY000): Check constraint 'produk_chk_1' is violated.
ByeServer menolak baris tersebut dengan galat ERROR 3819 (HY000): Check constraint 'produk_chk_1' is violated — nama constraint menunjuk pada check harga >= 0 yang kita pasang pertama. Statement gagal seluruhnya, sehingga baris dengan harga janggal tidak pernah menyentuh tabel. Perhatikan nomor galatnya: 3819 adalah kode khusus pelanggaran check, berbeda dari 1062 (duplikat) maupun 1048 (NULL pada kolom wajib).
CHECK Level Tabel dan Constraint Bernama #
Aturan yang melibatkan lebih dari satu kolom tidak bisa menempel pada satu kolom — ia dinyatakan sebagai table-level check. Kita buat tabel paket diskon yang membandingkan dua kolom sekaligus:
CREATE TABLE paket_diskon (
id INT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
harga DECIMAL(10,2) NOT NULL,
diskon DECIMAL(10,2) NOT NULL,
CONSTRAINT diskon_wajar CHECK (diskon <= harga)
);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)Bedahnya:
- Baris 5 (
CONSTRAINT diskon_wajar CHECK (diskon <= harga)): Klausa ini berada di daftar definisi tabel, sejajar dengan definisi kolom — bukan menempel pada satu kolom. Ekspresinya boleh menyebut kolom mana pun pada tabel yang sama. - Nama
diskon_wajar: Nama constraint pilihan kita sendiri, menggantikan nama otomatis sepertipaket_diskon_chk_1. Nama yang deskriptif membuat pesan galat langsung terbaca: pelanggar aturan diskon langsung tahu aturan mana yang dia langgar.
Paket yang wajar tentu lolos, dan paket yang diskonnya melampaui harga ditolak:
INSERT INTO paket_diskon (id, nama, harga, diskon) VALUES (1, 'Paket Hemat', 10000.00, 2500.00);Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)-- @error
INSERT INTO paket_diskon (id, nama, harga, diskon) VALUES (2, 'Paket Sultan', 10000.00, 20000.00);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 3819 (HY000): Check constraint 'diskon_wajar' is violated.
ByeGalat 3819 kembali muncul, kali ini menyebut nama diskon_wajar yang kita pilih sendiri. Bentuk final kedua constraint tersebut bisa diperiksa lewat SHOW CREATE TABLE:
SHOW CREATE TABLE paket_diskon\GOutput yang Dihasilkan:
*************************** 1. row ***************************
Table: paket_diskon
Create Table: CREATE TABLE `paket_diskon` (
`id` int NOT NULL,
`nama` varchar(100) NOT NULL,
`harga` decimal(10,2) NOT NULL,
`diskon` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `diskon_wajar` CHECK ((`diskon` <= `harga`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)Batasan Ekspresi CHECK #
Ekspresi CHECK harus bisa dievaluasi ulang kapan saja untuk baris mana pun, sehingga isinya dibatasi ketat. Dua larangan utamanya:
- Fungsi nondeterministik dilarang — fungsi yang hasilnya berubah waktu seperti
NOW(),CURDATE(), atauRAND()tidak boleh masuk ekspresi check, karena hasil pemeriksaan akan bergantung jam server. - Subquery dilarang — check hanya boleh membaca kolom baris yang sedang diuji, bukan mengambil data dari tabel lain.
Larangan pertama bisa kita saksikan langsung. Tabel berikut mencoba memaksa agenda hanya menerima tanggal hari ini ke atas:
-- @error
CREATE TABLE agenda (id INT PRIMARY KEY, judul VARCHAR(100) NOT NULL, tanggal DATE NOT NULL CHECK (tanggal >= CURDATE()));Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 3814 (HY000): An expression of a check constraint 'agenda_chk_1' contains disallowed function: curdate.
ByePembuatan tabel itu sendiri ditolak dengan galat ERROR 3814 (HY000): An expression of a check constraint ... contains disallowed function — server menolaknya sejak CREATE TABLE, bukan saat baris dicek. Untuk kebutuhan seperti “tanggal tidak boleh lampau”, validasinya memang sebaiknya diletakkan di aplikasi atau trigger, karena check tidak diizinkan membaca waktu saat evaluasi.
Referensi #
- CREATE TABLE and CHECK Constraints — MySQL 8.4 Reference Manual
- Constraints — MySQL 8.4 Reference Manual
- CREATE TABLE Statement — MySQL 8.4 Reference Manual
Harga negatif, stok minus, dan diskon janggal kini ditolak server sebelum menyentuh data. Satu pola kunci masih tersisa: bagaimana bila keunikan justru melekat pada pasangan dua kolom — pesanan dan produk — bukan satu kolom? Chapter COMPOSITE KEY membahasnya. Sebelum itu, kita bereskan database latihan:
DROP DATABASE IF EXISTS belajar_check;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)Dua tabel — produk dan paket_diskon — ikut terbuang.