How to create a CHECK constraint in MySQL that requires negative values? - mysql

I want to alter an integer column on a table to add a check constraint preventing the value from being zero or a positive number. For example:
CREATE TABLE example (id INTEGER)
ALTER TABLE example ADD CONSTRAINT chk_negID CHECK (id<0)
MySQL happily complies with these yet then allows the following:
INSERT INTO example VALUES (-1);
INSERT INTO example VALUES (1);
Are my constraints not actually being added? Is there a way to list constraints that have been added to a table after it was created?

It's not supported by mysql (even though it accepts it as a valid syntax)
The CHECK clause is parsed but ignored by all storage engines
http://dev.mysql.com/doc/refman/5.5/en/create-table.html

Here is something that would work, but may be hard to debug. This is a trigger:
DELIMITER $$;
CREATE TRIGGER my_trigger AFTER INSERT ON my_table
FOR EACH ROW
BEGIN
IF(OLD.id <= 0)
THEN
DELETE FROM my_table where id = OLD.id;
END IF;
END

Related

mysql add unique multi column with no check actual data

For an mysql v8.0.18 project with mariaDb 10.4.10
I would like add to my existing table an unique constraint for multi columns
ALTER TABLE 'new_purchasseorder' ADD UNIQUE ('created', 'fk_customer_id', 'fk_removal_id', 'fk_recipient_id')
but would like no check for old datas
something like that:
where id > 3869
i also tried the SET FOREIGN_KEY_CHECKS=0; but nor working in this case.
is it possible ?
My table looks like:
You can't do this with a unique constraint as far as I know, because, as you have already discovered, such a constraint will be applied to the entire table, regardless of id value. One workaround might be to use a before insert trigger, which does the assertion:
DELIMITER //
CREATE TRIGGER contacts_before_insert
BEFORE INSERT ON new_purchasseorder FOR EACH ROW
BEGIN
IF EXISTS (SELECT 1 FROM new_purchasseorder
WHERE created = NEW.created AND
fk_customer_id = NEW.fk_customer_id AND
fk_removal_id = NEW.fk_removal_id AND
fk_recipient_id = NEW.fk_recipient_id)
THEN
signal sqlstate '45000';
END IF;
END; //
DELIMITER ;
This insert trigger would cause any insert incoming with what your unique index defines as duplicate data to fail with an error, effectively blocking that insert.
A better long term (and easier) strategy might be to just fix your old data so that it can pass the requirements of the unique constraint.
Starting version 8.0.13, MySQL supports functional key parts - basically indexes on expression. Assuming that all 4 columns are non-nullable, you can do:
create unique index idx_new_purchaseorder on new_purchaseorder (
(
case when id > 3869
then concat_ws('$$', created, fk_customer_id, fk_removal_id, fk_recipient_id)
end
)
)
The case expression filters on id values, and generates a concatenated string that should be unique for rows that comply to the filter. I used some fancy characters to avoid "fake" duplicates.
Demo on DB Fiddle

Field containing numbers and letters [duplicate]

Suppose I have an attribute called phone number and I would like to enforce certain validity on the entries to this field. Can I use regular expression for this purpose, since Regular Expression is very flexible at defining constraints.
Yes, you can. MySQL supports regex (http://dev.mysql.com/doc/refman/5.6/en/regexp.html) and for data validation you should use a trigger since MySQL doesn't support CHECK constraint (you can always move to PostgreSQL as an alternative:). NB! Be aware that even though MySQL does have CHECK constraint construct, unfortunately MySQL (so far 5.6) does not validate data against check constraints. According to http://dev.mysql.com/doc/refman/5.6/en/create-table.html: "The CHECK clause is parsed but ignored by all storage engines."
You can add a check constraint for a column phone:
CREATE TABLE data (
phone varchar(100)
);
DELIMITER $$
CREATE TRIGGER trig_phone_check BEFORE INSERT ON data
FOR EACH ROW
BEGIN
IF (NEW.phone REGEXP '^(\\+?[0-9]{1,4}-)?[0-9]{3,10}$' ) = 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Wroooong!!!';
END IF;
END$$
DELIMITER ;
INSERT INTO data VALUES ('+64-221221442'); -- should be OK
INSERT INTO data VALUES ('+64-22122 WRONG 1442'); -- will fail with the error: #1644 - Wroooong!!!
However you should not rely merely on MySQL (data layer in your case) for data validation. The data should be validated on all levels of your app.
MySQL 8.0.16 (2019-04-25) and MariaDB 10.2.1 (2016-04-18) now not only parse CHECK constraint but also enforces it.
MySQL: https://dev.mysql.com/doc/refman/8.0/en/create-table-check-constraints.html
MariaDB: https://mariadb.com/kb/en/constraint/
Actually, we can can set regular expression within check constraints in MySQL.
Eg.,:
create table fk
(
empid int not null unique,
age int check(age between 18 and 60),
email varchar(20) default 'N/A',
secondary_email varchar(20) check(secondary_email RLIKE'^[a-zA-Z]#[a-zA-Z0-9]\.[a-z,A-Z]{2,4}'),
deptid int check(deptid in(10,20,30))
)
;
This INSERT query will work:
insert into fk values(1,19,'a#a.com','a#b.com', 30);
This INSERT query will not work:
insert into fk values(2,19,'a#a.com','a#bc.com', 30);

Creating an immutable field in mysql

I'd like to make a TIMESTAMP field DEFAULT CURRENT_TIMESTAMP, for 'creation time' purpose. But if someone or somehow something changes that TIMESTAMP, my data won't be consistent.
Is there a way I can ensure it won't change unless I delete the row and reinsert it, other than application level?
With the suggested answer provided, i could work around with something like this
CREATE TRIGGER consistency1 BEFORE UPDATE ON table1
FOR EACH ROW
BEGIN
IF NEW.creationtime != OLD.creationtime THEN
SET NEW.creationtime = OLD.creationtime;
END IF;
END;
Since my comment has been appreciated, here's the extended version.
I personally don't think that it's possible.
Anyway, there are a couple of things you can try:
Make sure that only your application can write on the database
Write a trigger like this (pseudocode!)
create trigger prevent_change_timestamp on tbl_name
before update
#fetch old row value
#verify if the timestamp field has been changed
#raise an error (any SQL error will do)
Or like this
create trigger revert_change_timestamp on tbl_name
after update
#fetch pre-change row value
#update the row with the "old" value in place of the new one
I'd personally go with the 3rd option, if possible. Anyway, the 2nd one is good too. I'd not rely on the 1st option unless necessary (eg: no access to trigger functionality)
More info here: reference
It's funny in a way that database apps don't offer this functionality as standard: not only for a "created" timestamp field, but for things like autoincrement id fields, and any miscellaneous values which you may want to set on creating a record and then never allow to be changed... wonder what the rationale is?
What you can do here is, you can write a TRIGGER on the table when a row is being updated. In that trigger, you can compare the old and new values, and if they are different then you can just overwrite the new value with the old one.
I tried this in MySQL 5.1 and got an error
DELIMITER //
CREATE TRIGGER member_update_0
-> AFTER UPDATE ON members
-> FOR EACH ROW
-> BEGIN
-> IF NEW.id != OLD.id THEN
-> SET NEW.id = OLD.id;
-> END IF;
-> END;//
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
The same trigger with AFTER replaced by BEFORE is accepted;
to me, this is a counter-intuitive way to do it, but it works
delimiter ;
UPDATE members SET id=11353 WHERE id=1353;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
It is actually possible to do this very neatly if you are using InnoDB.
Create another table with just one column. That column should have a foreign key (hence the innodb requirement in this solution) that points to the immutable column of the original table in question.
Put a restriction like "ON UPDATE RESTRICT".
In summary:
CREATE TABLE original (
....
immutable_column ...
INDEX index1(immutable_column)
....
) ENGINE=INNODB;
CREATE TABLE restricter (
.....
col1,
INDEX index2(col1),
FOREIGN KEY (col1) REFERENCES original (immutable_colum) ON UPDATE RESTRICT ON DELETE CASCADE
) ENGINE=INNODB;
Taking the idea a step further (for those of us still stuck with a legacy version of MySQL) you can have BOTH a protected & defaulted create_stamp AND an auto-updating update_stamp as follows:
If you have a table such as
CREATE TABLE `csv_status` (
`id` int(11) NOT NULL primary key AUTO_INCREMENT,
`create_stamp` datetime not null,
`update_stamp` timestamp default current_timestamp on update current_timestamp,
`status` enum('happy','sad') not null default 'happy'
);
Then you can define these triggers on it
drop trigger if exists set_create_stamp ;
create definer = CURRENT_USER trigger set_create_stamp BEFORE INSERT on
csv_status for each row
set NEW.create_stamp = now();
drop trigger if exists protect_create_stamp ;
delimiter //
create definer = CURRENT_USER trigger protect_create_stamp BEFORE UPDATE on
csv_status for each row
begin
if NEW.create_stamp != OLD.create_stamp then
set NEW.create_stamp = OLD.create_stamp;
end if;
end;//
delimiter ;

Is it Possible to Enforce Data Checking in MySQL using Regular expression

Suppose I have an attribute called phone number and I would like to enforce certain validity on the entries to this field. Can I use regular expression for this purpose, since Regular Expression is very flexible at defining constraints.
Yes, you can. MySQL supports regex (http://dev.mysql.com/doc/refman/5.6/en/regexp.html) and for data validation you should use a trigger since MySQL doesn't support CHECK constraint (you can always move to PostgreSQL as an alternative:). NB! Be aware that even though MySQL does have CHECK constraint construct, unfortunately MySQL (so far 5.6) does not validate data against check constraints. According to http://dev.mysql.com/doc/refman/5.6/en/create-table.html: "The CHECK clause is parsed but ignored by all storage engines."
You can add a check constraint for a column phone:
CREATE TABLE data (
phone varchar(100)
);
DELIMITER $$
CREATE TRIGGER trig_phone_check BEFORE INSERT ON data
FOR EACH ROW
BEGIN
IF (NEW.phone REGEXP '^(\\+?[0-9]{1,4}-)?[0-9]{3,10}$' ) = 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Wroooong!!!';
END IF;
END$$
DELIMITER ;
INSERT INTO data VALUES ('+64-221221442'); -- should be OK
INSERT INTO data VALUES ('+64-22122 WRONG 1442'); -- will fail with the error: #1644 - Wroooong!!!
However you should not rely merely on MySQL (data layer in your case) for data validation. The data should be validated on all levels of your app.
MySQL 8.0.16 (2019-04-25) and MariaDB 10.2.1 (2016-04-18) now not only parse CHECK constraint but also enforces it.
MySQL: https://dev.mysql.com/doc/refman/8.0/en/create-table-check-constraints.html
MariaDB: https://mariadb.com/kb/en/constraint/
Actually, we can can set regular expression within check constraints in MySQL.
Eg.,:
create table fk
(
empid int not null unique,
age int check(age between 18 and 60),
email varchar(20) default 'N/A',
secondary_email varchar(20) check(secondary_email RLIKE'^[a-zA-Z]#[a-zA-Z0-9]\.[a-z,A-Z]{2,4}'),
deptid int check(deptid in(10,20,30))
)
;
This INSERT query will work:
insert into fk values(1,19,'a#a.com','a#b.com', 30);
This INSERT query will not work:
insert into fk values(2,19,'a#a.com','a#bc.com', 30);

Trigger to silently ignore/delete duplicate entries on INSERT

I have the following table:
T(ID primary key, A, B)
I want to have pair (A, B) unique but I don't want to have constraint unique(A,B) on them because it will give error on insert.
Instead I want MySQL to silently ignore such inserts.
I can't use "insert on duplicate keys ignore" because I can't control client's queries.
So, can I build such trigger? Or maybe there is some constraint that allows silent ignore?
Edit: I dug around and I think I want something like SQLite's "Raise Ignore" statement.
Before mysql 5.5. it wasn't possible to stop an insert inside a trigger. There where some ugly work arounds but nothing I would recommend. Since 5.5 you can use SIGNAL to do it.
delimiter //
drop trigger if exists aborting_trigger //
create trigger aborting_trigger before insert on t
for each row
begin
set #found := false;
select true into #found from t where a=new.a and b=new.b;
if #found then
signal sqlstate '45000' set message_text = 'duplicate insert';
end if;
end //
delimiter ;
Add a unique key (A,B) and use INSERT statement with an IGNORE keyword.
From the reference - If you use the IGNORE keyword, errors that occur while executing the INSERT statement are treated as warnings instead.
INSERT Syntax.