periksa kendala tidak berfungsi?

23

Saya memiliki tabel berikut.

create table test (
   id smallint unsigned AUTO_INCREMENT,
   age tinyint not null,
   primary key(id),
   check (age<20)
);

Masalahnya adalah CHECKkendala tidak berfungsi pada kolom usia. Misalnya, ketika saya memasukkan 222 untuk bidang usia, MySQL menerimanya.

ALH
sumber

Jawaban:

16

Apa yang Anda butuhkan adalah dua pemicu untuk menangkap kondisi usia tidak valid

  • SEBELUM MASUK
  • SEBELUM PEMBARUAN

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 !!!

RolandoMySQLDBA
sumber
19

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 ...

gbn
sumber
13

Selain solusi pemicu yang bagus dari @Rolando, ada solusi lain dari masalah ini di MySQL (sampai CHECKkendala diimplementasikan).

Cara meniru beberapa CHECKkendala 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 FLOATkolom tipe data, sayangnya (terlalu banyak nilai di antara 0.0dan 20.0).


Bagaimana cara meniru CHECKbatasan 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 VIRTUALdan GENERATEDmasing-masing - yang dapat bertahan, yaitu disimpan dalam tabel - mereka memanggil mereka PERSISTENTdan STOREDmasing-masing - kita dapat menggunakannya untuk menyederhanakan solusi di atas dan bahkan lebih baik, memperluasnya untuk meniru / menegakkan CHECKbatasan 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 CHECKkendala.

Kami kemudian menambahkan kolom yang dihitung yang mengevaluasi ke salah satu TRUE/ FALSE/ UNKNOWN, persis seperti CHECKbatasan - - kolom ini memiliki FOREIGN KEYkendala untuk tabel jangkar kami. Jika kondisi / kolom dievaluasi menjadiFALSE untuk beberapa baris, baris ditolak, karena FK.

Jika kondisi / kolom mengevaluasi ke TRUEatau UNKNOWN( NULL), baris tidak ditolak, persis seperti yang seharusnya terjadi dengan CHECKkendala:

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 PERSISTENTgantinya 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 CHECKkendala (yang umum dalam banyak desain), kita hanya perlu menambahkan kolom yang dihitung dan kunci asing untuk masing-masing dari mereka. Kami hanya perlu satu truthtabel 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 CHECKkendala 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.

ypercubeᵀᴹ
sumber
0

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 .

Vlad Mihalcea
sumber