MYSQL BETWEEN operator [duplicate] - mysql

I am having trouble with this table
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`meetid`,`pid`),
CONSTRAINT `participants_ibfk_1` FOREIGN KEY (`meetid`) REFERENCES `Meetings` (`meetid`) ON DELETE CASCADE
CONSTRAINT `participants_ibfk_2` CHECK (status IN ('a','d','u'))
CONSTRAINT `participants_ibfk_3` CHECK (pid IN (SELECT name FROM Rooms) OR pid IN (SELECT userid FROM People))
);
I want to have a foreign key constraint, and that works. Then, I also want to add a constraint to the attribute status so it can only take the values 'a', 'd' and 'u'. It is not possible for me to set the field as Enum or set.
Can anyone tell me why this code does not work in MySQL?

CHECK constraints are not supported by MySQL. You can define them, but they do nothing (as of MySQL 5.7).
From the manual:
The CHECK clause is parsed but ignored by all storage engines.
The workaround is to create triggers, but they aren't the easiest thing to work with.
If you want an open-source RDBMS that supports CHECK constraints, try PostgreSQL. It's actually a very good database.

I don't understand why nobody here has mentioned that VIEW WITH CHECK OPTION can be a good alternative to the CHECK CONSTRAINT in MySQL:
CREATE VIEW name_of_view AS SELECT * FROM your_table
WHERE <condition> WITH [LOCAL | CASCADED] CHECK OPTION;
There is a doc on the MySQL site: The View WITH CHECK OPTION Clause
DROP TABLE `Participants`;
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL check (status IN ('a','d','u')),
PRIMARY KEY (`meetid`,`pid`)
);
-- should work
INSERT INTO `Participants` VALUES (1,1,'a');
-- should fail but doesn't because table check is not implemented in MySQL
INSERT INTO `Participants` VALUES (2,1,'x');
DROP VIEW vParticipants;
CREATE VIEW vParticipants AS
SELECT * FROM Participants WHERE status IN ('a','d','u')
WITH CHECK OPTION;
-- should work
INSERT INTO vParticipants VALUES (3,1,'a');
-- will fail because view uses a WITH CHECK OPTION
INSERT INTO vParticipants VALUES (4,1,'x');
P.S.: Keep in mind that your view should be updatable! See MySQL Updatable Views
(thanks Romeo Sierra for clarification in comments).

Beside triggers, for simple constraints like the one you have:
CONSTRAINT `participants_ibfk_2`
CHECK status IN ('a','d','u')
you could use a Foreign Key from status to a Reference table (ParticipantStatus with 3 rows: 'a','d','u' ):
CONSTRAINT ParticipantStatus_Participant_fk
FOREIGN KEY (status)
REFERENCES ParticipantStatus(status)

Starting with version 8.0.16, MySQL has added support for CHECK constraints:
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
);
Previously, this was only available using BEFORE INSERT and BEFORE UPDATE triggers:
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;

Here is a way of getting the checks you wanted quickly and easily:
drop database if exists gtest;
create database if not exists gtest;
use gtest;
create table users (
user_id integer unsigned not null auto_increment primary key,
username varchar(32) not null default '',
password varchar(64) not null default '',
unique key ix_username (username)
) Engine=InnoDB auto_increment 10001;
create table owners (
owner_id integer unsigned not null auto_increment primary key,
ownername varchar(32) not null default '',
unique key ix_ownername (ownername)
) Engine=InnoDB auto_increment 5001;
create table users_and_owners (
id integer unsigned not null primary key,
name varchar(32) not null default '',
unique key ix_name(name)
) Engine=InnoDB;
create table p_status (
a_status char(1) not null primary key
) Engine=InnoDB;
create table people (
person_id integer unsigned not null auto_increment primary key,
pid integer unsigned not null,
name varchar(32) not null default '',
status char(1) not null,
unique key ix_name (name),
foreign key people_ibfk_001 (pid) references users_and_owners(id),
foreign key people_ibfk_002 (status) references p_status (a_status)
) Engine=InnoDB;
create or replace view vw_users_and_owners as
select
user_id id,
username name
from users
union
select
owner_id id,
ownername name
from owners
order by id asc
;
create trigger newUser after insert on users for each row replace into users_and_owners select * from vw_users_and_owners;
create trigger newOwner after insert on owners for each row replace into users_and_owners select * from vw_users_and_owners;
insert into users ( username, password ) values
( 'fred Smith', password('fredSmith')),
( 'jack Sparrow', password('jackSparrow')),
( 'Jim Beam', password('JimBeam')),
( 'Ted Turner', password('TedTurner'))
;
insert into owners ( ownername ) values ( 'Tom Jones'),( 'Elvis Presley'),('Wally Lewis'),('Ted Turner');
insert into people (pid, name, status) values ( 5001, 'Tom Jones', 1),(10002,'jack Sparrow',1),(5002,'Elvis Presley',1);

Related

MySQL trigger to target an attribute of a column

I am working with an overlap super/subtype relationship dealing with person(s) in my DB. What I would like to do is have the overlapping subtypes insert new rows when the supertype gains a new row. I have attached my LRD to clarify the relationship. LRD
I would like to create a trigger that inserts new person rows into the correct subtype based on the attributes employee/user in the person table.
The code I have attempted so far gives me an error upon inserting rows into person noting "employee column does not exist". I would assume this is because this code is trying to use the if statement for the subtypes where it is in fact absent.
I would appreciate any feedback.
Table Details
CREATE TABLE PERSON
(person_id int(10) not null AUTO_INCREMENT,
first_name varchar(15) not null,
last_name varchar(15) not null,
employee char(1),
participant char(1),
CONSTRAINT person_pk PRIMARY KEY (person_id))
ENGINE=InnoDB;
CREATE TABLE EMPLOYEE
(eperson_id int(10) not null AUTO_INCREMENT,
enterprise_email varchar(30),
manager_id int(10),
CONSTRAINT employee_pk PRIMARY KEY (eperson_id),
CONSTRAINT employee_fk1 FOREIGN KEY(eperson_id) REFERENCES PERSON(person_id) ON update cascade,
CONSTRAINT employee_fk2 FOREIGN KEY(manager_id) REFERENCES EMPLOYEE(eperson_id) ON update cascade)
ENGINE=InnoDB;
CREATE TABLE PARTICIPANT
(pperson_id int(10) not null AUTO_INCREMENT,
city varchar(30),
state varchar(2),
zip int(5),
sign_up_date date,
termination_date date,
CONSTRAINT participant_pk PRIMARY KEY (pperson_id),
CONSTRAINT participant_fk FOREIGN KEY(pperson_id) REFERENCES PERSON(person_id) ON update cascade)
ENGINE=InnoDB;
Trigger Code
DELIMITER //
CREATE TRIGGER subtype_creator
AFTER INSERT ON PERSON
FOR EACH ROW
BEGIN
IF (employee = ā€˜eā€™ ) THEN
INSERT INTO EMPLOYEE
SET eperson_id = NEW.person_id,
last_name = NEW.last_name,
enterprise_email = NULL,
manager_id = NULL;
IF (participant = ā€˜pā€™ )THEN
INSERT INTO PARTICIPANT
SET pperson_id = NEW.person_id,
city=NULL,
state = NULL,
zip = NULL,
sign_up_date =NULL,
termination_date = NULL;
END IF;
END IF;
END//
DELIMITER ;
This may work for you.
First off, I think to have the AUTO_INCREMENT attribute on columns EMPLOYEE.eperson_id and PARTICIPANT.pperson_id is not needed.
Since both of those columns are FOREIGN KEYS and are referencing back to the person_id column of table PERSON, they need to have, and will be getting, their values from that column through the TRIGGER anyway so no need to autoincrement them in the tables.
So I would change that.
This TRIGGER should work with populating both tables EMPLOYEE and PARTICIPANT after INSERT on table PERSON:
DELIMITER //
CREATE TRIGGER subtype_creator
AFTER INSERT ON PERSON
FOR EACH ROW
BEGIN
INSERT INTO EMPLOYEE(eperson_id, enterprise_email, manager_id)
VALUES(NEW.person_id, NULL, NULL);
INSERT INTO PARTICIPANT(pperson_id, city, state, zip, sign_up_date, termination_date)
VALUES(NEW.person_id, NULL, NULL, NULL, NULL, NULL);
END//
DELIMITER ;
Hope this helps you.
I ended up figuring out two methods to solve my issue. I ended up altering my 'employee' and 'participant' into boolean/tinyint data types.
CREATE TABLE PERSON
(person_id int(10) not null AUTO_INCREMENT,
first_name varchar(15) not null,
last_name varchar(15) not null,
employee tinyint(1),
participant tinyint(1),
CONSTRAINT person_pk PRIMARY KEY (person_id))
ENGINE=InnoDB;
After that alteration I decided to try and break up the one trigger into two. This was successful.
Type 1
DELIMITER //
CREATE TRIGGER employee_creator
AFTER INSERT ON PERSON
FOR EACH ROW
BEGIN
IF (NEW.employee = 1 ) THEN
INSERT INTO EMPLOYEE
SET eperson_id = NEW.person_id,
last_name = NEW.last_name,
enterprise_email = NULL,
manager_id = NULL;
END IF;
END//
DELIMITER ;
DELIMITER //
CREATE TRIGGER participant_creator
AFTER INSERT ON PERSON
FOR EACH ROW
BEGIN
IF (NEW.participant =0 )THEN
INSERT INTO PARTICIPANT
SET pperson_id = NEW.person_id,
city=NULL,
state = NULL,
zip = NULL,
sign_up_date =NULL,
termination_date = NULL;
END IF;
END//
DELIMITER ;
After inplementing that first option I realized the ELSEIF would allow me to not split the two and create a single trigger.
Type 2
DELIMITER //
CREATE TRIGGER employee_creator
AFTER INSERT ON PERSON
FOR EACH ROW
BEGIN
IF (NEW.employee = 1 ) THEN
INSERT INTO EMPLOYEE
SET eperson_id = NEW.person_id,
last_name = NEW.last_name,
enterprise_email = NULL,
manager_id = NULL;
ELSEIF (NEW.participant =0 )THEN
INSERT INTO PARTICIPANT
SET pperson_id = NEW.person_id,
city=NULL,
state = NULL,
zip = NULL,
sign_up_date =NULL,
termination_date = NULL;
END IF;
END//
DELIMITER ;

Allow only unique combination of values for a composite primary key

I created the following table called books:
CREATE TABLE IF NOT EXISTS `books` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I created another tablet called compare to compare any 2 books from the books table:
CREATE TABLE IF NOT EXISTS `compare` (
`id_1` BIGINT UNSIGNED NOT NULL,
`id_2` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id_1`,`id_2`),
FOREIGN KEY (`id_1`) REFERENCES books(`id`),
FOREIGN KEY (`id_2`) REFERENCES books(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The previous works as expected but I need to force MYSQL to allow ONLY unique combination of values in the compare table.
For example if I have the following row in compare table:
id_1 | id_2
------------
1 | 2
I want to force MYSQL to NOT allow me to insert the following row:
id_1 | id_2
------------
2 | 1
So I want MYSQL to allow only either 1,2 or 2,1 NOT both.
Another way would be to swap the values in a before-insert trigger to make sure id_1 < id_2:
CREATE TRIGGER `compare_before_insert` BEFORE INSERT ON `compare` FOR EACH ROW
BEGIN
DECLARE t BIGINT;
IF(NEW.id_1 > NEW.id_2) THEN
SET t = NEW.id_1;
SET NEW.id_1 = NEW.id_2;
SET NEW.id_2 = t;
END IF;
END
This will allow you to use the key to make the pairs unique.
In MySQL (at least prior to version 8), you need to do this using a trigger if you want the database to enforce the constraint.
delimiter $$
create trigger compare_insert_trigger before insert on compare
for each row
begin
if (exists (select 1
from compare c
where c.id2 = new.id1 and c.id1 = new.id2
)
) then
signal sqlsate '45000'
set message_text = 'Book pair already exists in compare';
end if;
end;$$
delimiter ;
Of course, you still need the unique constraint.

Creating custom (user defined) constraints in mysql [duplicate]

I am having trouble with this table
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`meetid`,`pid`),
CONSTRAINT `participants_ibfk_1` FOREIGN KEY (`meetid`) REFERENCES `Meetings` (`meetid`) ON DELETE CASCADE
CONSTRAINT `participants_ibfk_2` CHECK (status IN ('a','d','u'))
CONSTRAINT `participants_ibfk_3` CHECK (pid IN (SELECT name FROM Rooms) OR pid IN (SELECT userid FROM People))
);
I want to have a foreign key constraint, and that works. Then, I also want to add a constraint to the attribute status so it can only take the values 'a', 'd' and 'u'. It is not possible for me to set the field as Enum or set.
Can anyone tell me why this code does not work in MySQL?
CHECK constraints are not supported by MySQL. You can define them, but they do nothing (as of MySQL 5.7).
From the manual:
The CHECK clause is parsed but ignored by all storage engines.
The workaround is to create triggers, but they aren't the easiest thing to work with.
If you want an open-source RDBMS that supports CHECK constraints, try PostgreSQL. It's actually a very good database.
I don't understand why nobody here has mentioned that VIEW WITH CHECK OPTION can be a good alternative to the CHECK CONSTRAINT in MySQL:
CREATE VIEW name_of_view AS SELECT * FROM your_table
WHERE <condition> WITH [LOCAL | CASCADED] CHECK OPTION;
There is a doc on the MySQL site: The View WITH CHECK OPTION Clause
DROP TABLE `Participants`;
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL check (status IN ('a','d','u')),
PRIMARY KEY (`meetid`,`pid`)
);
-- should work
INSERT INTO `Participants` VALUES (1,1,'a');
-- should fail but doesn't because table check is not implemented in MySQL
INSERT INTO `Participants` VALUES (2,1,'x');
DROP VIEW vParticipants;
CREATE VIEW vParticipants AS
SELECT * FROM Participants WHERE status IN ('a','d','u')
WITH CHECK OPTION;
-- should work
INSERT INTO vParticipants VALUES (3,1,'a');
-- will fail because view uses a WITH CHECK OPTION
INSERT INTO vParticipants VALUES (4,1,'x');
P.S.: Keep in mind that your view should be updatable! See MySQL Updatable Views
(thanks Romeo Sierra for clarification in comments).
Beside triggers, for simple constraints like the one you have:
CONSTRAINT `participants_ibfk_2`
CHECK status IN ('a','d','u')
you could use a Foreign Key from status to a Reference table (ParticipantStatus with 3 rows: 'a','d','u' ):
CONSTRAINT ParticipantStatus_Participant_fk
FOREIGN KEY (status)
REFERENCES ParticipantStatus(status)
Starting with version 8.0.16, MySQL has added support for CHECK constraints:
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
);
Previously, this was only available using BEFORE INSERT and BEFORE UPDATE triggers:
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;
Here is a way of getting the checks you wanted quickly and easily:
drop database if exists gtest;
create database if not exists gtest;
use gtest;
create table users (
user_id integer unsigned not null auto_increment primary key,
username varchar(32) not null default '',
password varchar(64) not null default '',
unique key ix_username (username)
) Engine=InnoDB auto_increment 10001;
create table owners (
owner_id integer unsigned not null auto_increment primary key,
ownername varchar(32) not null default '',
unique key ix_ownername (ownername)
) Engine=InnoDB auto_increment 5001;
create table users_and_owners (
id integer unsigned not null primary key,
name varchar(32) not null default '',
unique key ix_name(name)
) Engine=InnoDB;
create table p_status (
a_status char(1) not null primary key
) Engine=InnoDB;
create table people (
person_id integer unsigned not null auto_increment primary key,
pid integer unsigned not null,
name varchar(32) not null default '',
status char(1) not null,
unique key ix_name (name),
foreign key people_ibfk_001 (pid) references users_and_owners(id),
foreign key people_ibfk_002 (status) references p_status (a_status)
) Engine=InnoDB;
create or replace view vw_users_and_owners as
select
user_id id,
username name
from users
union
select
owner_id id,
ownername name
from owners
order by id asc
;
create trigger newUser after insert on users for each row replace into users_and_owners select * from vw_users_and_owners;
create trigger newOwner after insert on owners for each row replace into users_and_owners select * from vw_users_and_owners;
insert into users ( username, password ) values
( 'fred Smith', password('fredSmith')),
( 'jack Sparrow', password('jackSparrow')),
( 'Jim Beam', password('JimBeam')),
( 'Ted Turner', password('TedTurner'))
;
insert into owners ( ownername ) values ( 'Tom Jones'),( 'Elvis Presley'),('Wally Lewis'),('Ted Turner');
insert into people (pid, name, status) values ( 5001, 'Tom Jones', 1),(10002,'jack Sparrow',1),(5002,'Elvis Presley',1);

insert trigger: issue with constraint

I'm trying to create trigger on customer-order database where each customer has several orders and each order has several items.
I'm planning to create a trigger to ensure that
the total number of all orders place by the same customer cannot
exceed 10000
How can create the insert trigger for above constraint.
Here is my SQL file with sample data provided.
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `mydb` ;
CREATE TABLE customers
(`id` int not null auto_increment primary key, `first_name` varchar(64), `last_name`varchar(64) );
INSERT INTO customers(`first_name`, `last_name`)VALUES('Jhon', 'Doe');
CREATE TABLE items
(`id` int not null auto_increment primary key,`item` varchar(64),`price` decimal(19,2));
INSERT INTO items(`item`, `price`)VALUES('Item1', 10.5),('Item2', 25);
CREATE TABLE orders
(`id` int not null auto_increment primary key, `date` date, `customer_id` int,`status` int not null default 1, -- 1 new constraint fk_customer_id foreign key (customer_id) references customers (id));
INSERT INTO orders(`date`, `customer_id`, `status`)VALUES(CURDATE(), 1, 1);
CREATE TABLE order_items(`id` int not null auto_increment primary key,
`order_id` int not null, `item_id` int not null, `quantity` decimal(19,3) not null, `price` decimal(19,3) not null,
constraint fk_order_id foreign key (order_id) references orders (id),
constraint fk_item_id foreign key (item_id) references items (id));
INSERT INTO order_items(`order_id`, `item_id`, `quantity`, `price`)VALUES
(1, 1, 2, 10.5),(1, 2, 4, 25);
;
Although Jahul's answer would technically work, here is alternative logic:
DELIMITER $$
CREATE TRIGGER `customer_orders_check`
BEFORE INSERT ON `orders` FOR EACH ROW
BEGIN
IF ((select count(*)
from `orders`
where a.customer_id = NEW.customer_id
) >= 10000 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Too many orders already';
END IF;
END;
$$
DELIMITER ;
That said, I would suggest an alternative approach. Counting up to 10,000 rows for each insert seems like a lot of work. Instead, keep the counter in the customers table, using an after insert trigger (and perhaps after update/delete as well). Then when inserting a new row, you can just check the count in customers.
This trigger will stop insert --
CREATE TRIGGER `customer_orders_check`
BEFORE INSERT ON `orders` FOR EACH ROW
BEGIN
IF exists(select count(*)
from `orders` a
where a.customer_id= NEW.customer_id
having count(*)>=10000 ) THEN
SET NEW.id = 1 ;
END IF;
END;

How do I add a custom CHECK constraint on a MySQL table?

I am having trouble with this table
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`meetid`,`pid`),
CONSTRAINT `participants_ibfk_1` FOREIGN KEY (`meetid`) REFERENCES `Meetings` (`meetid`) ON DELETE CASCADE
CONSTRAINT `participants_ibfk_2` CHECK (status IN ('a','d','u'))
CONSTRAINT `participants_ibfk_3` CHECK (pid IN (SELECT name FROM Rooms) OR pid IN (SELECT userid FROM People))
);
I want to have a foreign key constraint, and that works. Then, I also want to add a constraint to the attribute status so it can only take the values 'a', 'd' and 'u'. It is not possible for me to set the field as Enum or set.
Can anyone tell me why this code does not work in MySQL?
CHECK constraints are not supported by MySQL. You can define them, but they do nothing (as of MySQL 5.7).
From the manual:
The CHECK clause is parsed but ignored by all storage engines.
The workaround is to create triggers, but they aren't the easiest thing to work with.
If you want an open-source RDBMS that supports CHECK constraints, try PostgreSQL. It's actually a very good database.
I don't understand why nobody here has mentioned that VIEW WITH CHECK OPTION can be a good alternative to the CHECK CONSTRAINT in MySQL:
CREATE VIEW name_of_view AS SELECT * FROM your_table
WHERE <condition> WITH [LOCAL | CASCADED] CHECK OPTION;
There is a doc on the MySQL site: The View WITH CHECK OPTION Clause
DROP TABLE `Participants`;
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL check (status IN ('a','d','u')),
PRIMARY KEY (`meetid`,`pid`)
);
-- should work
INSERT INTO `Participants` VALUES (1,1,'a');
-- should fail but doesn't because table check is not implemented in MySQL
INSERT INTO `Participants` VALUES (2,1,'x');
DROP VIEW vParticipants;
CREATE VIEW vParticipants AS
SELECT * FROM Participants WHERE status IN ('a','d','u')
WITH CHECK OPTION;
-- should work
INSERT INTO vParticipants VALUES (3,1,'a');
-- will fail because view uses a WITH CHECK OPTION
INSERT INTO vParticipants VALUES (4,1,'x');
P.S.: Keep in mind that your view should be updatable! See MySQL Updatable Views
(thanks Romeo Sierra for clarification in comments).
Beside triggers, for simple constraints like the one you have:
CONSTRAINT `participants_ibfk_2`
CHECK status IN ('a','d','u')
you could use a Foreign Key from status to a Reference table (ParticipantStatus with 3 rows: 'a','d','u' ):
CONSTRAINT ParticipantStatus_Participant_fk
FOREIGN KEY (status)
REFERENCES ParticipantStatus(status)
Starting with version 8.0.16, MySQL has added support for CHECK constraints:
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
);
Previously, this was only available using BEFORE INSERT and BEFORE UPDATE triggers:
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;
Here is a way of getting the checks you wanted quickly and easily:
drop database if exists gtest;
create database if not exists gtest;
use gtest;
create table users (
user_id integer unsigned not null auto_increment primary key,
username varchar(32) not null default '',
password varchar(64) not null default '',
unique key ix_username (username)
) Engine=InnoDB auto_increment 10001;
create table owners (
owner_id integer unsigned not null auto_increment primary key,
ownername varchar(32) not null default '',
unique key ix_ownername (ownername)
) Engine=InnoDB auto_increment 5001;
create table users_and_owners (
id integer unsigned not null primary key,
name varchar(32) not null default '',
unique key ix_name(name)
) Engine=InnoDB;
create table p_status (
a_status char(1) not null primary key
) Engine=InnoDB;
create table people (
person_id integer unsigned not null auto_increment primary key,
pid integer unsigned not null,
name varchar(32) not null default '',
status char(1) not null,
unique key ix_name (name),
foreign key people_ibfk_001 (pid) references users_and_owners(id),
foreign key people_ibfk_002 (status) references p_status (a_status)
) Engine=InnoDB;
create or replace view vw_users_and_owners as
select
user_id id,
username name
from users
union
select
owner_id id,
ownername name
from owners
order by id asc
;
create trigger newUser after insert on users for each row replace into users_and_owners select * from vw_users_and_owners;
create trigger newOwner after insert on owners for each row replace into users_and_owners select * from vw_users_and_owners;
insert into users ( username, password ) values
( 'fred Smith', password('fredSmith')),
( 'jack Sparrow', password('jackSparrow')),
( 'Jim Beam', password('JimBeam')),
( 'Ted Turner', password('TedTurner'))
;
insert into owners ( ownername ) values ( 'Tom Jones'),( 'Elvis Presley'),('Wally Lewis'),('Ted Turner');
insert into people (pid, name, status) values ( 5001, 'Tom Jones', 1),(10002,'jack Sparrow',1),(5002,'Elvis Presley',1);