doing my assignment on Databases. Working with MySQL, my task is to create database with several tables. One of them is the table of workers in which primary key is WId, which is the String of 16 A-Z and 0-9 elements. I need to make a constraint such that it is not possible to put there !##$%^ etc.
create table Worker ( WId char(16) primary key,
Name char(10),
Surname char(20),...);
Thanks.
You can always use the SIGNAL keyword to return an error:
EDIT 1:
Based on #Gordon_linoff comment: you would have to do both BEFORE INSERT and BEFORE UPDATE in case if you expect the Id to change.
CREATE TRIGGER myTrigger
BEFORE INSERT
ON worker
FOR EACH ROW
BEGIN
IF NOT (NEW.WId REGEXP '^[A-Za-z0-9]+$')
THEN
BEGIN
SIGNAL sqlstate '45000' set message_text = 'TADA!';
END;
END IF;
END;
Related
I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();
So, I've got a table roughly as follows:
CREATE TABLE CUSTOMER (
CUSTID INT NOT NULL AUTO_INCREMENT,
NAME CHAR (45),
CONSTRAINT CUSTOMER_PRIMARY_KEY PRIMARY KEY (CUSTID))
AUTO_INCREMENT = 100;
I'm auto incrementing the CUSTID so that it's possible to simply insert a name and have it created with the next available CUSTID. However, I also want to ensure that it isn't possible to set the CUSTID value to zero, either on creation of the row or on update so I've constructed the following trigger:
DELIMITER $$
CREATE TRIGGER `custid_before_insert` BEFORE INSERT ON `CUSTOMER`
FOR EACH ROW
BEGIN
IF (NEW.CUSTID) <= 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Check constraint on CUSTOMER.CUSTID failed';
END IF;
END$$
CREATE TRIGGER `custid_before_update` BEFORE UPDATE ON `CUSTOMER`
FOR EACH ROW
BEGIN
IF (NEW.CUSTID) <= 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Check constraint on CUSTOMER.CUSTID failed';
END IF;
END$$
DELIMITER ;
Unfortunately in my blissful ignorance of how AUTO_INCREMENT worked, I've come to the conclusion that this is the wrong way to go about this. Trying to insert a customer with no CUSTID value is tripping the trigger causing the insert to fail which I presume is due to the value being a zero before insertion when AUTO_INCREMENT assigns it a value.
Would the best way to do this really be to change the trigger to occur after the insert and delete the row or is there a better way to do this to just throw an error?
The insert trigger is not needed.
From Auto_Increment
No value was specified for the AUTO_INCREMENT column, so MySQL
assigned sequence numbers automatically. You can also explicitly
assign 0 to the column to generate sequence numbers.
E.G.
create table t(id int auto_increment, primary key(id));
insert into t(id) values (0);
select id from t;
# 1
Update:
To allow the insert to complete when CUSTID is not specified,
INSERT INTO customer(name) VALUES('Chuck');
check for null in the trigger:
IF NEW.CUSTID IS NOT NULL AND NEW.CUSTID <= 0 THEN
Inserting '0' into an auto-increment column causes it to increment the same as inserting NULL, so you really neither need nor want the INSERT trigger. Try it with just the UPDATE trigger.
I have two tables
Table Room(
capacity INTEGER,
roomID varchar(5)
)
and
Event(
attendance INTEGER,
room varchar(5),
CHECK(attendance <= (SELECT R.capacity FROM Room R, WHERE R.roomID = room))
)
But I guess MySQL doesn't allow for subqueries inside of checks.
Is there an alternative way to perform this check? I only have a passing familiarity with triggers, but it seems like they only allow for explicit actions like insert, delete, etc. I just want to prevent the data from being inserted unless it fits the criteria.
MySQL doesn't enforce CHECK constraints.
In some cases, you can use a foreign key constraint instead of a CHECK constraint. For example, let's say you wanted to constrain room.capacity to values between 15 and 100. In other dbms, I'd write a CHECK constraint; for MySQL, I'd use another table and a foreign key constraint.
create table capacities (
capacity integer,
primary key (capacity)
);
-- Assumes that all the values between 15 and 100
-- are valid capacities. But that's unlikely.
insert into capacities values
(15), (16), (17), (18),
-- . . .
(100);
create table room(
capacity INTEGER,
roomID varchar(5),
primary key (roomID),
foreign key (capacity) references capacities (capacity)
);
In your case, I think you're stuck with writing triggers. First, the table.
create table event (
eventID integer not null,
attendance integer not null,
roomID varchar(5) not null,
primary key (eventID),
foreign key (roomID) references room (roomID)
);
One trigger handles INSERT statements. I didn't look hard; there's probably a better SQLSTATE than '22003'.
delimiter $$
create trigger check_capacity_on_insert
before insert on event
for each row begin
if(new.attendance) > (select capacity from room where room.roomid = roomid) then
signal sqlstate '22003' set message_text = 'Value out of range for room.capacity';
end if;
end;
$$
delimiter ;
And another trigger handles UPDATE statements.
delimiter $$
create trigger check_capacity_on_update
before update on event
for each row begin
if(new.attendance) > (select capacity from room where room.roomid = roomid) then
signal sqlstate '22003' set message_text = 'Value out of range for room.capacity';
end if;
end;
$$
delimiter ;
I have a table that has two columns to store id from another table. Column1 gets id from ABC table and Column2 also gets id from that table but letter is called parent ID, so with this information I know who is parent of who.
Now I want to create a constraint not to ever let both columns to get same id.
The following did not work:
ALTER TABLE id_parent_table
ADD CHECK (parent_id != main_id)
This is still allowing to insert two identical numbers.
This is now supported as of MySQL 8.0.16.
See https://dev.mysql.com/doc/refman/8.0/en/create-table-check-constraints.html
mysql> create table id_parent_table (
-> main_id bigint unsigned not null,
-> parent_id bigint unsigned not null,
-> constraint columns_cannot_equal check (main_id <> parent_id)
-> );
Query OK, 0 rows affected (0.38 sec)
mysql> insert into id_parent_table (main_id, parent_id) values (1, 1);
ERROR 3819 (HY000): Check constraint 'columns_cannot_equal' is violated.
Apparently, MySQL does not support check constraints. To quote the online reference:
The CHECK clause is parsed but ignored by all storage engines.
You could, alternatively, use a trigger to fail such an insert or update:
EDIT: MySQL doesn't support a single trigger on two events, so you'd have to have two different triggers:
delimiter //
CREATE TRIGGER id_parent_table_check_insert_trg
BEFORE INSERT ON id_parent_table
FOR EACH ROW
BEGIN
DECLARE msg varchar(255);
IF new.parent_id = new.main_id THEN
SET msg = 'parent_id and main_id should be different';
SIGNAL SQLSTATE '45000' SET message_text = msg;
END IF;
END
//
CREATE TRIGGER id_parent_table_check_update_trg
BEFORE UPDATE ON id_parent_table
FOR EACH ROW
BEGIN
DECLARE msg varchar(255);
IF new.parent_id = new.main_id THEN
SET msg = 'parent_id and main_id should be different';
SIGNAL SQLSTATE '45000' SET message_text = msg;
END IF;
END
//
I have a problem with the following sql code executed in MySQL Workbench 5.6. The table is orderly created, the trigger creation code is also run without errors. But when I try to run the both insert into statements I have an error Error Code: 1054. Unknown column 'Pass' in 'field list'. If I run the last select statements it is also runned as expected!?
I really can not imagine what is happening after running the trigger creation script!?
If I do not run the triger creation code before the insert into statements they are executed as expected and the data is inserted in the columns.
/*TASK 15: Write a SQL statement to create a table Users. Users should have username,
password, full name and last login time. Choose appropriate data types for the table fields.
Define a primary key column with a primary key constraint. Define the primary key column
as identity to facilitate inserting records. Define unique constraint to avoid repeating
usernames. Define a check constraint to ensure the password is at least 5 characters long.
*/
create table Users(
UserID int auto_increment not null,
Username nvarchar(50) not null,
Pass varchar(100) not null,
FullName nvarchar(100),
LastLoginTime datetime,
constraint PK_Users primary key(UserID),
constraint UK_Users_Username UNIQUE(Username),
constraint CH_Users_Password CHECK(length(users.Pass)>=5)
);
DELIMITER $$
CREATE TRIGGER `TR_BeforeInsert_Users`
BEFORE INSERT ON `users`
FOR EACH ROW
BEGIN
IF LENGTH(`Pass` ) < 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint on Pass failed';
END IF;
END$$
delimiter ;
Insert into Users(Username, Pass, FullName,lastlogintime) values('ttitto','alabala','Todor', now());
Insert into Users(Username, Pass, FullName,lastlogintime) values('ttitt','ala','Todor', now());
select length(Pass) from users;
Within triggers, one must reference columns with either the NEW or OLD pseudo-table prefix. In your case:
DELIMITER $$
CREATE TRIGGER `TR_BeforeInsert_Users`
BEFORE INSERT ON `users`
FOR EACH ROW
BEGIN
IF CHAR_LENGTH(NEW.Pass) < 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint on Pass failed';
END IF;
END$$
delimiter ;