Trigger in MySQL "BEFORE DELETE" - mysql

Deleting a record gives me below error:
ERROR 1442: 1442: Can't update table 'categoria' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
This is my trigger:
DROP trigger IF EXISTS tg_eliminarCategoria;
DELIMITER $$
CREATE TRIGGER tg_eliminarCategoria
BEFORE DELETE ON Categoria
FOR EACH ROW
BEGIN
IF NOT EXISTS (SELECT * FROM Categoria WHERE nombreCategoria = 'Sin Categoria') THEN
INSERT INTO Categoria (nombreCategoria) VALUES ('Sin Categoria');
END IF;
SET #idCategoria = (SELECT idCategoria FROM Categoria WHERE nombreCategoria = 'Sin Categoria');
UPDATE Contacto SET idCategoria=#idCategoria WHERE idCategoria=OLD.idCategoria;
END$$
DELIMITER ;
The trigger is that by deleting a record from the "Categoria" table, it checks if there is a "Sin Categoria" record and if it does not exist, it modifies all records in the "Contacto" table containing the record deleted by The new record.
Sorry for my English, I'm still learning.

I guess that you want to make sure there is at least one row in the table for 'Sin Categoria'.
Another way to do this is to raise a SIGNAL (like an exception) if someone tries to delete 'Sin Categoria'.
mysql> DROP trigger IF EXISTS tg_eliminarCategoria;
mysql> DELIMITER $$
mysql> CREATE TRIGGER tg_eliminarCategoria
BEFORE DELETE ON Categoria
FOR EACH ROW
BEGIN
IF OLD.nombreCategoria = 'Sin Categoria' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'No debe eliminar la categoría especial: Sin Categoria';
END IF;
END$$
mysql> DELIMITER ;
mysql> delete from Categoria;
ERROR 1644 (45000): No debe eliminar la categoría especial: Sin Categoria
However, I think I have a better recommendation. I am guessing you use this for foreign keys that must reference a category, like this:
CREATE TABLE Contacto (
idContacto INT AUTO_INCREMENT PRIMARY KEY,
idCategoria INT NOT NULL,
FOREIGN KEY (idCategoria) REFERENCES Categoria(idCategoria)
);
When a Contacto has no Categoria, you reference the 'Sin Categoria' row. And you want to make sure no one deletes that row in the Categoria table.
Instead, I recommend using NULL when a row in Contacto has no Categoria. Just make Contacto.idCategoria allow NULL:
CREATE TABLE Contacto (
idContacto INT AUTO_INCREMENT PRIMARY KEY,
idCategoria INT DEFAULT NULL,
FOREIGN KEY (idCategoria) REFERENCES Categoria(idCategoria)
);
INSERT INTO Contacto
SET idContacto = 1234,
idCategoria = NULL;
Then you don't need any row in Categoria named "Sin Categoria".

Related

Create trigger before insert

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.

How to insert multiple records from one table into another on update of third table in MySQL

I'm having difficulty with developing the logic in MySQL. I don't know how to INSERT multiple records from a Table AFTER UPDATE.
CREATE TABLE primeira(
ID int PRIMARY KEY AUTO_INCREMENT,
nome varchar(30) NOT NULL,
valor int DEFAULT 0
);
CREATE TABLE segunda(
ID int PRIMARY KEY AUTO_INCREMENT,
ID_primeira int,
ultimo_valor int DEFAULT 0,
credito int NOT NULL,
limite int DEFAULT 0,
FOREIGN KEY(ID_primeira) references primeira(ID)
);
CREATE TABLE terceira(
ID int PRIMARY KEY AUTO_INCREMENT,
ID_segunda int,
`data` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
estado boolean DEFAULT false,
FOREIGN KEY(ID_segunda) references segunda(ID)
);
CREATE TRIGGER tr_segundaLimite_INS
BEFORE INSERT ON segunda FOR EACH ROW
SET NEW.limite = New.ultimo_valor + New.credito;
DELIMITER //
CREATE TRIGGER tr_primeira_UPD
AFTER UPDATE ON primeira FOR EACH ROW
IF (SELECT limite FROM segunda WHERE segunda.ID_primeira = New.ID AND
(limite - NEW.valor)< 50) THEN
INSERT INTO terceira(ID_segunda)
VALUES ((SELECT ID FROM segunda WHERE segunda.ID_primeira = New.ID
AND (limite - NEW.valor)< 50));
END IF;
END
//
DELIMITER ;
I'm going to use procedures with functions to SELECT the data. The problem with this TRIGGER is that it's not working when there are multiple matching records.
The error that I am getting is-
subquery returns more than 1 row.
The objective is: After an update of the primeira.valor, the trigger would subtract segunda.limite - New.valor. If this difference is < 50 then all the matching segunda.ID would be registered at terceira.ID_segunda on terceira table.
I'm using data below:
INSERT INTO primeira(nome,valor)
VALUES
('Burro',800),
('Chiconizio',300),
('Xerosque',400),
('Shrek',600);
INSERT INTO segunda(ID_primeira,ultimo_valor,credito)
VALUES
(1,600,800),
(1,700,400),
(1,800,500),
(2,150,200),
(2,200,180),
(2,250,300);
UPDATE primeira
SET Valor = 330
WHERE ID = 2;
You need CURSOR for this. You can try the following trigger code. I hope this will fix your issue.
DELIMITER //
CREATE TRIGGER tr_primeira_UPD
AFTER UPDATE ON primeira FOR EACH ROW
BEGIN
DECLARE v_limite_diff INT;
DECLARE v_seg_id INT;
DECLARE done INT DEFAULT FALSE;
DECLARE c1 CURSOR FOR SELECT (s.limite - NEW.valor), s.id
FROM segunda s WHERE s.ID_primeira = New.ID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN c1;
my_loop: LOOP
FETCH c1 INTO v_limite_diff, v_seg_id;
IF done THEN
LEAVE my_loop;
END IF;
IF( v_limite_diff < 50 ) THEN
INSERT INTO terceira(ID_segunda) VALUES(v_seg_id);
END IF;
END LOOP;
END//
DELIMITER ;

How to use trigger in MySql to make foreign key

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.

MySQL, trigger that generates primary key before insert a row

I'm trying to generate a primary key based on current date and time,
Here's my trigger:
DELIMITER $$
CREATE TRIGGER cuenta
BEFORE UPDATE ON cuenta
FOR EACH ROW
BEGIN
INSERT INTO cuenta VALUES (NULL);
SET NEW.NoCuenta = DATE_FORMAT(NOW(), '%000%d%m%y%h%i%s');
END$$
DELIMITER ;
My table cuenta:
NoCuenta, varchar(15), NOT NULL.
TipoCuenta, varchar(7),
saldo, float
IDCliente, int(8).
Then I try to insert:
insert into banco.cuenta(TipoCuenta, saldo, IDCliente)
VALUES('Debito','12500.5','1');
The result:
1 row(s) affected, 1 warning(s): 1364 Field 'NoCuenta' doesn't have a default value
But when I type select*from cuenta... NoCuenta field is empty.
And then when I try to insert a new row, it shows this:
Error Code: 1062. Duplicate entry '' for key 'PRIMARY'
Please HELP!!
Why are you inserting into the table in the trigger? Just try this:
DELIMITER $$
CREATE TRIGGER cuenta
BEFORE UPDATE ON cuenta
FOR EACH ROW
BEGIN
SET NEW.NoCuenta = DATE_FORMAT(NOW(), '%000%d%m%y%h%i%s');
END$$
DELIMITER ;

MySQL: Custom row validation

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.