Menu

Constraint CHECK di SQLite: Validasi Data di Tingkat Tabel

Cara memakai constraint CHECK di SQLite untuk menegakkan aturan pada nilai kolom: pengecekan satu kolom, pengecekan banyak kolom, constraint bernama, dan jebakannya.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Constraint CHECK Adalah Aturan yang Harus Dipenuhi Setiap Baris

Constraint CHECK adalah ekspresi boolean yang kamu pasang pada tabel. SQLite mengevaluasinya pada setiap INSERT dan UPDATE, dan kalau hasil ekspresinya false, operasinya gagal. Ini cara menanamkan aturan bisnis, seperti "harga tidak boleh negatif" atau "status harus salah satu dari tiga nilai ini", langsung ke dalam skema.

Dua baris pertama berhasil masuk. Baris ketiga memunculkan CHECK constraint failed dan ditolak, sehingga tabel tidak pernah melihatnya. Constraint menegakkan aturan untuk setiap penulis, entah itu aplikasimu, skrip migrasi, atau seseorang yang sedang mengutak-atik di CLI.

Tingkat Kolom vs Tingkat Tabel

Kamu bisa menulis CHECK di dua tempat: setelah definisi kolom (tingkat kolom) atau setelah semua kolom (tingkat tabel). Perilakunya sama, bedanya hanya mana yang lebih enak dibaca.

Booking pertama berhasil dimasukkan. Yang kedua gagal karena tanggal akhirnya sebelum tanggal mulai. Aturan satu kolom lebih enak dibaca di tingkat kolom, dan apa pun yang membandingkan dua kolom atau lebih lebih enak dibaca di tingkat tabel.

Membatasi Nilai ke Sebuah Daftar

Pemakaian yang umum adalah memaksa sebuah kolom hanya berisi salah satu nilai dari kumpulan yang tetap. SQLite tidak punya tipe enum bawaan, jadi CHECK ... IN (...) adalah cara yang lazim:

Baris ketiga gagal karena 'pending' tidak ada di daftar yang diizinkan. Kalau suatu saat kamu perlu menambah status baru, kamu harus membangun ulang tabelnya (dibahas di bawah), jadi pikirkan sedikit sebelum mengunci daftarnya. Tapi untuk kosakata yang memang tetap, seperti nama peran atau status pesanan, inilah constraint yang kamu butuhkan.

Memberi Nama pada Constraint

Secara bawaan, constraint tidak punya nama. Pesan error-nya hanya bilang "CHECK constraint failed" beserta ekspresinya. Itu tidak masalah kalau tabel hanya punya satu CHECK, tapi membingungkan kalau ada lima. Tambahkan nama dengan CONSTRAINT:

Sekarang pesan kegagalannya menyertakan nama constraint, jadi kamu langsung tahu aturan mana yang dilanggar. Memberi nama hanya butuh beberapa karakter tambahan, dan manfaatnya langsung terasa saat pertama kali ada yang rusak di production.

CHECK dan NULL: Jebakannya

CHECK lolos kalau ekspresinya bernilai true atau NULL. Ia hanya gagal kalau hasilnya jelas false. Kedengarannya aneh sampai kamu ingat bahwa hampir semua perbandingan dengan NULL menghasilkan NULL, bukan true atau false.

Baris NULL masuk tanpa masalah: NULL >= 0 bernilai NULL, bukan false, jadi CHECK tidak gagal. Kalau kamu memang ingin melarang angka negatif dan nilai yang kosong, gabungkan NOT NULL dengan CHECK:

Sekarang insert gagal karena constraint NOT NULL, bahkan sebelum CHECK dijalankan. Kedua constraint bekerja sama: NOT NULL menangani nilai yang tidak ada, CHECK menangani bentuk nilainya.

Fungsi Bawaan yang Berguna di Dalam CHECK

Ekspresinya bisa memakai sebagian besar fungsi bawaan SQLite. Beberapa yang sering dipakai:

Ada tiga kegagalan: bentuk email yang salah, username yang terlalu pendek, dan kode negara dengan huruf kecil. LIKE menangani pola sederhana, dan length(), upper(), lower(), serta aritmetika semuanya boleh dipakai. Pastikan saja ekspresinya deterministik. Memakai sesuatu seperti random() atau current_timestamp di dalam CHECK membuat aturan yang hasilnya bisa berubah-ubah antarbaris, dan itu jarang yang kamu inginkan.

CHECK vs Trigger

CHECK dan trigger sama-sama bisa menolak data yang buruk, dan pemula sering bingung harus memilih yang mana. Patokannya:

  • CHECK kalau aturannya hanya bergantung pada baris yang sedang ditulis. "Kolom ini dibandingkan dengan kolom itu," "nilai ini berada dalam rentang tertentu," "string ini cocok dengan sebuah pola."
  • Trigger (khususnya trigger BEFORE INSERT/UPDATE yang memanggil RAISE) kalau aturannya bergantung pada baris lain, tabel lain, atau butuh sesuatu yang lebih rumit dari satu ekspresi boolean.

CHECK lebih cepat, lebih sederhana, dan terlihat di skema: siapa pun yang membaca CREATE TABLE bisa melihat aturannya. Pakai trigger hanya kalau CHECK tidak bisa mengungkapkan yang kamu butuhkan.

Kamu Tidak Bisa Menghapus CHECK dengan ALTER

Inilah satu kekurangannya. SQLite tidak punya ALTER TABLE ... DROP CONSTRAINT. Untuk menghapus atau mengubah CHECK, kamu membangun ulang tabelnya:

BEGIN;

CREATE TABLE products_new (
    id    INTEGER PRIMARY KEY,
    name  TEXT NOT NULL,
    price REAL NOT NULL CHECK (price >= 0 AND price <= 1000000)
);

INSERT INTO products_new SELECT * FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;

COMMIT;

Bungkus semuanya dalam transaksi, supaya kegagalan di tengah jalan tidak mengubah database. Kalau ada tabel lain yang punya foreign key ke tabel yang sedang kamu bangun ulang, langkahnya jadi lebih panjang: nonaktifkan foreign_keys, bangun ulang, aktifkan lagi, lalu periksa ulang. Kita akan membahasnya di dokumentasi migrasi nanti.

Berikutnya: Constraint UNIQUE

CHECK memvalidasi bentuk nilai di dalam satu baris. Constraint berikutnya, UNIQUE, memvalidasi hubungan antarbaris, dengan menjamin tidak ada dua baris yang punya nilai sama di satu kolom atau sekumpulan kolom. Itu yang berikutnya.

Pertanyaan yang sering diajukan

Apa itu constraint CHECK di SQLite?

Constraint CHECK adalah ekspresi boolean yang dipasang pada tabel dan harus dipenuhi oleh setiap baris. SQLite mengevaluasinya pada setiap INSERT atau UPDATE, dan menolak perubahan kalau ekspresinya bernilai false. Ini cara paling sederhana untuk menegakkan aturan seperti 'harga harus positif' tanpa menulis kode aplikasi.

Bisakah constraint CHECK di SQLite merujuk ke beberapa kolom?

Bisa. Tulis sebagai constraint tingkat tabel, bukan dipasang pada satu kolom. Misalnya, CHECK (start_date <= end_date) yang dideklarasikan setelah daftar kolom bisa merujuk kedua kolom itu. Secara teknis, pengecekan tingkat kolom juga bisa merujuk kolom lain, tapi tingkat tabel lebih mudah dibaca kalau lebih dari satu kolom terlibat.

Kenapa constraint CHECK di SQLite saya tidak bereaksi terhadap NULL?

CHECK lolos kalau ekspresinya bernilai true atau NULL, dan hanya gagal kalau ekspresinya jelas bernilai false. Jadi CHECK (age >= 0) menerima age yang NULL, karena NULL >= 0 bernilai NULL, bukan false. Kalau kamu juga ingin melarang NULL, tambahkan constraint NOT NULL di samping CHECK.

Bisakah saya menghapus atau mengubah constraint CHECK di SQLite?

Tidak secara langsung. SQLite tidak mendukung ALTER TABLE ... DROP CONSTRAINT. Untuk mengubah CHECK, kamu bisa mengedit sqlite_schema dengan PRAGMA writable_schema (tingkat lanjut, berisiko) atau membangun ulang tabel: buat tabel baru dengan constraint yang diinginkan, salin datanya, hapus tabel lama, lalu ganti namanya. Memberi nama pada constraint membuat skrip pembangunan ulang lebih mudah dibaca.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai