I want to create a trigger in Mysql.
Before Insert and before Update , to only insert the values if the ID is present in another table.
Here is my trigger (before insert) which does not work:
DELIMITER $$
CREATE TRIGGER
`before_insert_id`
BEFORE INSERT ON
`table2`
FOR EACH ROW
BEGIN
DECLARE msg VARCHAR(255);
IF NEW.id =
( SELECT id
FROM table2
WHERE NEW.id not in (select id from table1)
)
THEN
SET msg = 'id not in table1';
SIGNAL SQLSTATE '45002' SET message_text = msg ;
END IF ;
END ;
$$
DELIMITER ;
Also should we insert values in table2 inside after if statement passes?or is it just for checking only?
IF NOT EXISTS (select * from table1 where id = new.id) then set msg = 'id not in table1' signal... end if; If it exists then data gets inserted automatically.
... only insert the values if the ID is present in another table.
This sound like you just need a foreign key constraint, not a trigger.
ALTER TABLE table2 ADD FOREIGN KEY (id) REFERENCES table1(id);
That will throw an error if you try to insert a row with an id that is not present in table1.
No trigger required.
Related
I am using mysql 8.0.23
My table
CRETAE TABLE sample_table(
id INT AUTO_INCREMENT PRIMARY KEY,
col1 VARCHAR(10) DEFAULT NULL,
col2 VARCHAR(10) DEFAULT NULL
);
Let us insert one row
INSERT INTO sample_table(col1, col2) VALUES('aaa','bbb');
Table after insertion
id
col1
col2
1
aaa
bbb
Trigger before update the table
DELIMITER $$
CREATE TRIGGER sample_trigger BEFORE UPDATE ON sample_table
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = NEW.col1;
END $$
DELIMITER ;
update statement
UPDATE sample_table SET col2 = 'ccc' WHERE id = 1;
Here I am updating only col2 but I am getting "aaa" for message_text, What is the reason behind it?
I wanted to insert col1 value for each update of only col1.
I can't add check for old.col1 <> new.col1 in the trigger. In some scenario I may get same value then also that value should be inserted to another table.
How to achive this? Please help me. Thanks a lot.
OLD has all the old values of a row and NEW all columns.
So check if old and new are different
DELIMITER $$
CREATE TRIGGER sample_trigger BEFORE UPDATE ON sample_table
FOR EACH ROW
BEGIN
IF OLD.col1 <> NEW.col1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = NEW.col1;
END IF;
END $$
DELIMITER ;
In my MySQL Database, I have a table with a composite primary key where the ID is not in auto_increment mode. Something like this :
CREATE TABLE table_a (
fk_table_b INT UNSIGNED NOT NULL,
id INT UNSIGNED,
label VARCHAR(80) NOT NULL,
PRIMARY KEY (fk_table_b, id),
FOREIGN KEY fk_table_b
REFERENCES table_b(id)
);
To increment the ID in function of the foreign key, I made a trigger like this :
DELIMITER $$
CREATE TRIGGER table_a_auto_increment
BEFORE INSERT ON table_a
FOR EACH ROW BEGIN
SET NEW.id = (
SELECT IFNULL(MAX(id), 0) + 1
FROM table_a
WHERE table_a.fk_table_b = NEW.fk_table_b
);
END $$
DELIMITER ;
But when I do SELECT LAST_INSERT_ID() I am getting 0 as the new id ... Normally you could override the LAST_INSERT_ID() by giving it a number like this :
INSERT table_a ( fk_table_b, id)
VALUES (1, LAST_INSERT_ID(5));
SELECT LAST_INSERT_ID(); -- -> it gives me 5
So I have tried to combine both to do this trigger :
DELIMITER $$
CREATE TRIGGER table_a_auto_increment
BEFORE INSERT ON table_a
FOR EACH ROW BEGIN
SET NEW.id = (
SELECT LAST_INSERT_ID(IFNULL(MAX(id), 0) + 1)
FROM table_a
WHERE table_a.fk_table_b = NEW.fk_table_b
);
END $$
DELIMITER ;
But it's still giving me 0 when I insert something in the base ... Do you know if there is a way to make it work ?
Thanks a lot.
-- EDIT 2020-08-14
Finally it seems impossible to override the LAST_INSERT_ID function inside the TRIGGER, so I changed my solution by removing the trigger and doing it inside my insert function like this :
INSERT table_a ( fk_table_b, id, label)
VALUES (1, LAST_INSERT_ID((
SELECT IFNULL(MAX(old_one.id), 0) + 1
FROM table_a AS old_one
WHERE old_one.fk_table_b = table_a.fk_table_b
)), "something");
And then, this is giving me the good result I can use in my backend :)
You may use additional service table:
CREATE TABLE service_table (id BIGINT AUTO_INCREMENT PRIMARY KEY);
and
DELIMITER $$
CREATE TRIGGER table_a_auto_increment
BEFORE INSERT ON table_a
FOR EACH ROW
BEGIN
SET NEW.id = (
SELECT IFNULL(MAX(id), 0) + 1
FROM table_a
WHERE table_a.fk_table_b = NEW.fk_table_b
);
DELETE FROM service_table WHERE id IS NOT NULL;
INSERT INTO service_table VALUES (NEW.id - 1);
INSERT INTO service_table VALUES (NULL);
SET NEW.id = LAST_INSERT_ID() - 1;
END $$
DELIMITER ;
fiddle (foreign key removed).
Maybe the code may be simplified a little - do it yourself.
Service table may be defined as Engine = MEMORY (if available).
The code is not safe for concurrent inserts.
I've currently made a simple record table, with the recordID as the auto-increment primary key, the question is, due to religious reasons, my employer DOES NOT want to include the number 4 and 6 in the recordID, so instead of checking the recordID everytime after the record has been made, is there a much easier way to solve my current problem?
EDIT:
Here's a quick test table I've created based on Vanojx1's answer. So what did I do wrong?
CREATE TABLE `test` (
`ID` int(11) NOT NULL,
`value` int(11) NOT NULL
)
DELIMITER $$
CREATE TRIGGER `jump4and6` BEFORE INSERT ON `test` FOR EACH ROW BEGIN
SET #nextId = (SELECT MAX(`id`) FROM `test`);
IF (#nextId IN (4,6)) THEN
SET NEW.id = #nextId + 1;
SET #nextId = #nextId + 2;
ELSE
SET NEW.id = #nextId;
SET #nextId = #nextId + 1;
END IF;
INSERT INTO `test`(`id`) VALUES (#nextId);
END
$$
DELIMITER ;
ALTER TABLE `test` ADD PRIMARY KEY (`ID`);
ALTER TABLE `test` MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
Everything works so far, but when I tried to insert a row:
INSERT INTO `test`(value) VALUES (123456);
This happens.
#1442 - Can't update table 'test' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
You can create the primary key as an integer, then use a trigger before insert like this:
DELIMITER $$
CREATE TRIGGER jump4and6
BEFORE INSERT
ON your_table
FOR EACH ROW BEGIN
SET #nextId = (SELECT MAX(current_index) FROM your_table_sequence);
IF (#nextId IN (4,6)) THEN
SET NEW.id = #nextId + 1;
SET #nextId = #nextId + 2;
ELSE
SET NEW.id = #nextId;
SET #nextId = #nextId + 1;
END IF;
INSERT INTO your_table_sequence (current_index) VALUES (#nextId);
END$$
You also need a table to store your primary key sequence
I want to use trigger to make foreign key in MySql. I have the following tables:
1) 'content' table:
teacher_id varchar(20)
sub_id varchar(20)
path varchar(100)
file_name varchar(100)
2) 'teacher' table:
teacher_id varchar(20)
teacher_name varchar(45)
and I am using the following code for trigger(delimiter //):
CREATE TRIGGER fk_content_teacher_temp BEFORE INSERT ON `content`
FOR EACH ROW
BEGIN
DECLARE has_row TINYINT;
SET has_row = 0;
SELECT 1 INTO has_row FROM `teacher` INNER JOIN `content` ON content.teacher_id=teacher.teacher_id;
IF has_row=0 THEN
INSERT error_msg VALUES ('Foreign Key Constraint Violated!');
END IF;
END//
The problem is, when am trying to insert in content table for a teacher_id which is not present in teacher table, I get the following error:
1172 - Result consists of more than one row
What can I do to make it work fine, or any other way i can use trigger to make foreign keys?
Thank you in advance!
While it is not clear what exactly you intend with the statement "use trigger to make foreign key", your current issue is that SELECT INTO cannot be used in queries that return more than one result.
SELECT 1 INTO has_row FROM teacher INNER JOIN content ON content.teacher_id=teacher.teacher_id;
returns EVERY match between the two tables.
If you were trying to check if teacher contains the teacher_id value being used in the new content record, you should just be able to drop the JOIN clause completely and just query like so:
SELECT 1 INTO has_row FROM `teacher` WHERE `teacher_id` = NEW.`teacher_id`;
While this is an oldish question I would like to provide some insight for future searchers on how one might deal with such issue.
In a recent project I was unable to use InnoDB but had to use the MyISAM engine (in reality it was MariaDB's Aria engine) for a database transfer which contained foreign keys.
I opted for implementing foreign keys using triggers as described here.
A great intro into the subject is provided here: https://dev.mysql.com/tech-resources/articles/mysql-enforcing-foreign-keys.html
However, I will outline my solution as I found some thing not fully workable for me in the above. E.g. Any update to a parent table was completely prohibited in their "restrict" example when a foreign child key existed even though the child was not affected.
For demonstration I use the following table definitions and test data:
CREATE TABLE `__parent` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`)) ENGINE=`Aria`;
CREATE TABLE `__child` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT,`parent_id` int UNSIGNED, PRIMARY KEY (`id`), INDEX `parent_id_idx` USING BTREE (`parent_id`) ) ENGINE=`Aria`;
INSERT INTO __parent VALUES (1), (2), (3);
INSERT INTO __child VALUES (1,1), (2,2), (3,1), (4,2), (5,3), (6,1);
Prevent inserts into a child table when no corresponding linked parent entry exists:
DELIMITER //
CREATE TRIGGER __before_insert_child BEFORE INSERT ON __child FOR EACH ROW
BEGIN
IF (SELECT COUNT(*) FROM __parent WHERE __parent.id=new.parent_id) = 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t insert record. Foreign parent key does not exist!';
END IF;
END //
DELIMITER ;
Prevent updates to a child table where it would unlink a child record:
DELIMITER //
CREATE TRIGGER __before_update_child BEFORE UPDATE ON __child FOR EACH ROW
BEGIN
IF (SELECT COUNT(*) FROM __parent WHERE __parent.id = new.parent_id) = 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t update record. Foreign parent key does not exist!';
END IF;
END //
DELIMITER ;
Cascading updates to the child table when the parent is updated:
DELIMITER //
CREATE TRIGGER __after_update_parent AFTER UPDATE ON __parent FOR EACH ROW
BEGIN
UPDATE __child SET __child.parent_id=new.id WHERE __child.parent_id=old.id;
END //
DELIMITER ;
Cascade deletes to the child table when a parent is deleted:
DELIMITER //
CREATE TRIGGER __after_delete_parent AFTER DELETE ON __parent FOR EACH ROW
BEGIN
DELETE FROM __child WHERE __child.parent_id=old.id;
END;
END //
DELIMITER ;
Sometime you don't want to cascade but restrict. In this case use the following instead:
Restrict parent updates to the child table:
DELIMITER //
CREATE TRIGGER __before_update_parent BEFORE UPDATE ON __parent FOR EACH ROW
BEGIN
IF ( old.id <> new.id AND (SELECT COUNT(*) FROM __child WHERE __child.parent_id = old.id) <> 0 ) THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t update record. Foreign key updates to child table restricted!';
END IF;
END //
DELIMITER ;
Restrict parent deletes from the child table:
DELIMITER //
CREATE TRIGGER __before_delete_parent BEFORE DELETE ON __parent FOR EACH ROW
BEGIN
IF ( SELECT COUNT(*) FROM __child WHERE __child.parent_id = old.id) <> 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t delete record. Foreign key exists in child table!';
END IF;
END //
DELIMITER ;
Hope this helps someone.
I have such example:
CREATE TABLE a(
id INT PRIMARY KEY AUTO_INCREMENT,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES a(id)
);
DELIMITER ;;
CREATE TRIGGER a_insert BEFORE INSERT ON a
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '01431' SET MESSAGE_TEXT = 'The foreign data source you are trying to reference does not exist.';
END;;
DELIMITER ;
INSERT INTO a(parent_id) VALUES (NULL);
INSERT INTO a(parent_id) VALUES (1);
INSERT INTO a(parent_id) VALUES (2);
INSERT INTO a(parent_id) VALUES (4);
INSERT INTO a(parent_id) VALUES (999);
SELECT * FROM a
This end up with 4 recods:
----------------
id parent_id
----------------
1 NULL
2 1
3 2
4 4
I found post online that MySQL does not support rollbacks in triggers. That is a problem because I want such hierarchy where no row points to it self and inserts like the one with ID=4 works just fine. How do I ensure there are no such records in database?
Well, the problem is with auto_increment, because you in BEFORE INSERT event you don't have that value assigned yet. On the other hand in AFTER INSERT event you can't do anything with it.
If you want to use auto_increment id column a possible solution is to use separate table for sequencing.
Your schema would look like
CREATE TABLE a_seq(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE a(
id INT NOT NULL PRIMARY KEY DEFAULT 0,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES a(id)
);
And your trigger
DELIMITER $$
CREATE TRIGGER a_insert
BEFORE INSERT ON a
FOR EACH ROW
BEGIN
INSERT INTO a_seq VALUES(NULL);
SET NEW.id = LAST_INSERT_ID();
IF NEW.id = NEW.parent_id THEN
SET NEW.id = NULL;
END IF;
END$$
DELIMITER ;
If id=parent_id the trigger deliberately violates NOT NULL constraint assigning NULL value. Therefore this record won't be inserted.
Here is SQLFiddle demo. Uncomment last insert statement. It won't allow you to make such insert.