it-swarm.asia

periksa kendala tidak berfungsi?

Saya memiliki tabel berikut.

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

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

23
ALH

Yang Anda butuhkan adalah dua pemicu untuk mengetahui kondisi usia yang tidak valid

  • SEBELUM MASUK
  • SEBELUM PEMBARUAN

Berikut ini didasarkan pada metode perangkap kesalahan jerry-rigged untuk Pemicu MySQL dari Bab 11, Halaman 254-256 buku Pemrograman Prosedur yang Disimpan MySQL di bawah subjudul 'Memvalidasi 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 !!!

16
RolandoMySQLDBA

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 kode dari server SQL lain, dan untuk menjalankan aplikasi yang membuat tabel dengan referensi. Lihat Bagian 1.8.5, “Perbedaan MySQL dari SQL Standar”.

Ini juga merupakan bug yang dilaporkan selama hampir 8 tahun ...

19
gbn

Selain solusi pemicu Nice oleh @Rolando, ada solusi lain dari masalah ini di MySQL (sampai kendala CHECK diimplementasikan).

Bagaimana cara meniru beberapa batasan CHECK 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 table, untuk menghindari penambahan atau penghapusan baris yang tidak disengaja.

Trik ini tidak akan bekerja dengan kolom tipe data FLOAT, sayangnya (terlalu banyak nilai di antara 0.0 dan 20.0).


Bagaimana cara meniru batasan CHECK sewenang-wenang di MySQL (5.7) dan MariaDB (dari 5.2 hingga 10.1)

Sejak 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 mereka VIRTUAL dan GENERATED masing-masing - yang dapat dipertahankan, yaitu disimpan dalam tabel - mereka menyebutnya masing-masing PERSISTENT dan STORED - kita dapat menggunakannya untuk menyederhanakan solusi di atas dan bahkan lebih baik lagi, perluas untuk meniru/menegakkan batasan CHECK yang 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 batasan CHECK.

Kami kemudian menambahkan kolom yang dihitung yang mengevaluasi TRUE/FALSE/UNKNOWN, persis seperti batasan CHECK yang akan - tetapi kolom ini memiliki FOREIGN KEY membatasi ke tabel jangkar kami. Jika kondisi/kolom mengevaluasi ke FALSE 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 batasan CHECK:

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 daripada STORED. Dalam versi 10.2 kata kunci STORED ditambahkan juga, jadi contoh di atas berfungsi dalam kedua rasa (MySQL dan MariaDB) untuk versi terbaru.

Jika kita ingin menerapkan banyak batasan CHECK (yang umum di banyak desain), kita hanya perlu menambahkan kolom yang dikomputasi dan kunci asing untuk masing-masing kendala tersebut. 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 batasan telah diterapkan dalam 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.

13
ypercubeᵀᴹ

Seperti yang saya jelaskan di 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 CHECK menggunakan pemicu basis data untuk versi MySQL sebelum 8.0.16, kemudian periksa artikel ini .

0
Vlad Mihalcea