Jawaban:
Apa yang Anda butuhkan adalah dua pemicu untuk menangkap kondisi usia tidak valid
Berikut ini didasarkan pada metode perangkap kesalahan jerry-rigged untuk MySQL Triggers dari Bab 11, Halaman 254-256 buku Pemrograman Prosedur yang Disimpan MySQL di bawah judul 'Validasi Data dengan Pemicu' :
drop table mytable;
create table mytable (
id smallint unsigned AUTO_INCREMENT,
age tinyint not null,
primary key(id)
);
DELIMITER $$
CREATE TRIGGER checkage_bi BEFORE INSERT ON mytable FOR EACH ROW
BEGIN
DECLARE dummy,baddata INT;
SET baddata = 0;
IF NEW.age > 20 THEN
SET baddata = 1;
END IF;
IF NEW.age < 1 THEN
SET baddata = 1;
END IF;
IF baddata = 1 THEN
SELECT CONCAT('Cannot Insert This Because Age ',NEW.age,' is Invalid')
INTO dummy FROM information_schema.tables;
END IF;
END; $$
CREATE TRIGGER checkage_bu BEFORE UPDATE ON mytable FOR EACH ROW
BEGIN
DECLARE dummy,baddata INT;
SET baddata = 0;
IF NEW.age > 20 THEN
SET baddata = 1;
END IF;
IF NEW.age < 1 THEN
SET baddata = 1;
END IF;
IF baddata = 1 THEN
SELECT CONCAT('Cannot Update This Because Age ',NEW.age,' is Invalid')
INTO dummy FROM information_schema.tables;
END IF;
END; $$
DELIMITER ;
insert into mytable (age) values (10);
insert into mytable (age) values (15);
insert into mytable (age) values (20);
insert into mytable (age) values (25);
insert into mytable (age) values (35);
select * from mytable;
insert into mytable (age) values (5);
select * from mytable;
Inilah hasilnya:
mysql> drop table mytable;
Query OK, 0 rows affected (0.03 sec)
mysql> create table mytable (
-> id smallint unsigned AUTO_INCREMENT,
-> age tinyint not null,
-> primary key(id)
-> );
Query OK, 0 rows affected (0.06 sec)
mysql> DELIMITER $$
mysql> CREATE TRIGGER checkage_bi BEFORE INSERT ON mytable FOR EACH ROW
-> BEGIN
-> DECLARE dummy,baddata INT;
-> SET baddata = 0;
-> IF NEW.age > 20 THEN
-> SET baddata = 1;
-> END IF;
-> IF NEW.age < 1 THEN
-> SET baddata = 1;
-> END IF;
-> IF baddata = 1 THEN
-> SELECT CONCAT('Cannot Insert This Because Age ',NEW.age,' is Invalid')
-> INTO dummy FROM information_schema.tables;
-> END IF;
-> END; $$
Query OK, 0 rows affected (0.08 sec)
mysql> CREATE TRIGGER checkage_bu BEFORE UPDATE ON mytable FOR EACH ROW
-> BEGIN
-> DECLARE dummy,baddata INT;
-> SET baddata = 0;
-> IF NEW.age > 20 THEN
-> SET baddata = 1;
-> END IF;
-> IF NEW.age < 1 THEN
-> SET baddata = 1;
-> END IF;
-> IF baddata = 1 THEN
-> SELECT CONCAT('Cannot Update This Because Age ',NEW.age,' is Invalid')
-> INTO dummy FROM information_schema.tables;
-> END IF;
-> END; $$
Query OK, 0 rows affected (0.07 sec)
mysql> DELIMITER ;
mysql> insert into mytable (age) values (10);
Query OK, 1 row affected (0.06 sec)
mysql> insert into mytable (age) values (15);
Query OK, 1 row affected (0.05 sec)
mysql> insert into mytable (age) values (20);
Query OK, 1 row affected (0.04 sec)
mysql> insert into mytable (age) values (25);
ERROR 1172 (42000): Result consisted of more than one row
mysql> insert into mytable (age) values (35);
ERROR 1172 (42000): Result consisted of more than one row
mysql> select * from mytable;
+----+-----+
| id | age |
+----+-----+
| 1 | 10 |
| 2 | 15 |
| 3 | 20 |
+----+-----+
3 rows in set (0.00 sec)
mysql> insert into mytable (age) values (5);
Query OK, 1 row affected (0.07 sec)
mysql> select * from mytable;
+----+-----+
| id | age |
+----+-----+
| 1 | 10 |
| 2 | 15 |
| 3 | 20 |
| 4 | 5 |
+----+-----+
4 rows in set (0.00 sec)
mysql>
Harap perhatikan juga bahwa nilai kenaikan otomatis tidak sia-sia atau hilang.
Cobalah !!!
PERIKSA kendala tidak diterapkan di MySQL. Dari CREATE TABLE
Klausa CHECK diuraikan tetapi diabaikan oleh semua mesin penyimpanan. Lihat Bagian 12.1.17, “CREATE TABLE Syntax”. Alasan untuk menerima tetapi mengabaikan klausa sintaksis adalah untuk kompatibilitas, untuk membuatnya lebih mudah untuk port code dari server SQL lain, dan untuk menjalankan aplikasi yang membuat tabel dengan referensi. Lihat Bagian 1.8.5, “Perbedaan MySQL dari SQL Standar”.
Itu juga merupakan bug yang dilaporkan selama hampir 8 tahun ...
Selain solusi pemicu yang bagus dari @Rolando, ada solusi lain dari masalah ini di MySQL (sampai CHECK
kendala diimplementasikan).
Cara meniru beberapa CHECK
kendala di MySQL
Jadi, jika Anda lebih suka kendala integritas referensial dan ingin menghindari pemicu (karena masalah di MySQL ketika Anda memiliki keduanya di tabel Anda), Anda dapat menggunakan tabel referensi kecil lainnya:
CREATE TABLE age_allowed
( age TINYINT UNSIGNED NOT NULL
, PRIMARY KEY (age)
) ENGINE = InnoDB ;
Isi dengan 20 baris:
INSERT INTO age_allowed
(age)
VALUES
(0), (1), (2), (3), ..., (19) ;
Maka meja Anda adalah:
CREATE TABLE test
( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT
, age TINYINT UNSIGNED NOT NULL
, PRIMARY KEY (id)
, CONSTRAINT age_allowed__in__test
FOREIGN KEY (age)
REFERENCES age_allowed (age)
) ENGINE = InnoDB ;
Anda harus menghapus akses tulis ke age_allowed
tabel, untuk menghindari penambahan atau penghapusan baris yang tidak disengaja.
Trik ini tidak akan berfungsi dengan FLOAT
kolom tipe data, sayangnya (terlalu banyak nilai di antara 0.0
dan 20.0
).
Bagaimana cara meniru CHECK
batasan arbitrer di MySQL (5.7) dan MariaDB (dari 5.2 hingga 10.1)
Karena MariaDB menambahkan kolom yang dihitung dalam versi 5.2 mereka (rilis GA: 2010-11-10 ) dan MySQL di 5,7 (rilis GA: 2015-10-21 ) - yang mereka sebut VIRTUAL
dan GENERATED
masing-masing - yang dapat bertahan, yaitu disimpan dalam tabel - mereka memanggil mereka PERSISTENT
dan STORED
masing-masing - kita dapat menggunakannya untuk menyederhanakan solusi di atas dan bahkan lebih baik, memperluasnya untuk meniru / menegakkan CHECK
batasan sewenang-wenang ):
Seperti di atas, kita akan membutuhkan tabel bantuan tetapi dengan satu baris kali ini yang akan bertindak sebagai tabel "jangkar". Bahkan lebih baik, tabel ini dapat digunakan untuk sejumlah CHECK
kendala.
Kami kemudian menambahkan kolom yang dihitung yang mengevaluasi ke salah satu TRUE
/ FALSE
/ UNKNOWN
, persis seperti CHECK
batasan - - kolom ini memiliki FOREIGN KEY
kendala untuk tabel jangkar kami. Jika kondisi / kolom dievaluasi menjadiFALSE
untuk beberapa baris, baris ditolak, karena FK.
Jika kondisi / kolom mengevaluasi ke TRUE
atau UNKNOWN
( NULL
), baris tidak ditolak, persis seperti yang seharusnya terjadi dengan CHECK
kendala:
CREATE TABLE truth
( t BIT NOT NULL,
PRIMARY KEY (t)
) ENGINE = InnoDB ;
-- Put a single row:
INSERT INTO truth (t)
VALUES (TRUE) ;
-- Then your table would be:
-- (notice the change to `FLOAT`, to prove that we don't need)
-- (to restrict the solution to a small type)
CREATE TABLE test
( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
age FLOAT NOT NULL,
age_is_allowed BIT -- GENERATED ALWAYS
AS (age >= 0 AND age < 20) -- our CHECK constraint
STORED,
PRIMARY KEY (id),
CONSTRAINT check_age_must_be_non_negative_and_less_than_20
FOREIGN KEY (age_is_allowed)
REFERENCES truth (t)
) ENGINE = InnoDB ;
Contohnya adalah untuk versi MySQL 5.7. Di MariaDB (versi 5.2+ hingga 10.1), kita hanya perlu memodifikasi sintaks dan mendeklarasikan kolom sebagai PERSISTENT
gantinya STORED
. Dalam versi 10.2 yangSTORED
kata kunci ditambahkan juga, jadi contoh di atas berfungsi dalam kedua rasa (MySQL dan MariaDB) untuk versi terbaru.
Jika kita ingin menegakkan banyak CHECK
kendala (yang umum dalam banyak desain), kita hanya perlu menambahkan kolom yang dihitung dan kunci asing untuk masing-masing dari mereka. Kami hanya perlu satu truth
tabel dalam database. Seharusnya satu baris dimasukkan dan kemudian semua akses tulis dihapus.
Namun dalam MariaDB terbaru, kita tidak perlu melakukan semua akrobat ini lagi, karena CHECK
kendala telah diterapkan di versi 10.2.1 (rilis alpha: 2016-Jul-04)!
Versi 10.2.2 saat ini masih merupakan versi beta tetapi tampaknya fitur tersebut akan tersedia dalam rilis stabil pertama dari seri MariaDB 10.2.
Seperti yang saya jelaskan dalam artikel ini , dimulai dengan versi 8.0.16, MySQL telah menambahkan dukungan untuk batasan LIHAT kustom:
ALTER TABLE topic
ADD CONSTRAINT post_content_check
CHECK (
CASE
WHEN DTYPE = 'Post'
THEN
CASE
WHEN content IS NOT NULL
THEN 1
ELSE 0
END
ELSE 1
END = 1
);
ALTER TABLE topic
ADD CONSTRAINT announcement_validUntil_check
CHECK (
CASE
WHEN DTYPE = 'Announcement'
THEN
CASE
WHEN validUntil IS NOT NULL
THEN 1
ELSE 0
END
ELSE 1
END = 1
);
Sebelumnya, ini hanya tersedia menggunakan BEFORE INSERT dan BEFORE UPDATE trigger:
CREATE
TRIGGER post_content_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Post'
THEN
IF NEW.content IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Post content cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER post_content_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Post'
THEN
IF NEW.content IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Post content cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER announcement_validUntil_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Announcement'
THEN
IF NEW.validUntil IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Announcement validUntil cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER announcement_validUntil_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Announcement'
THEN
IF NEW.validUntil IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Announcement validUntil cannot be NULL';
END IF;
END IF;
END;
Untuk detail lebih lanjut tentang meniru batasan LIHAT menggunakan pemicu basis data untuk versi MySQL sebelum 8.0.16, kemudian periksa artikel ini .