I am learning SQL, I am using mysql workbench and I have a problem related with trigger.
I have two tables, one to them called DireccionEntrega with attributes: idDir, idT, idPed, Road, Pais, Region. The other called Cobertura with attributes: idCob, idProv, Pais, Region.
I want to create a trigger for table DireccionEntrega so that when I insert a values in it, the values of Country and Region of table DireccionEntrega, they must be in the table Cobertura. If not, an error message is displayed.
I show you how I created the trigger:
create trigger Comprobacion before insert on DireccionEntrega
for each row
begin
if not exists(select Cobertura.idProv, Cobertura.Pais, Cobertura.Region from Cobertura, Proveedor, LineasPedido where
Cobertura.idProv = Proveedor.idProv and Proveedor.idProv = LineasPedido.idProv)
then
signal sqlstate '45000'
set message_text = 'Error insert...';
end if;
end;
I do not what is happening but its not working how I expect.
The expectations of the trigger is following like this:
insert into DireccionEntrega(Pais, Region) values ('Spain','West')
it's okey because I have that information in Cobertura.
insert into DireccionEntrega(Pais, Region) values ('Portugal','South')
it's wrong because I don't have that information in Cobertura.
If you need something to solve what I want, please let me know and answer whatever you want.
Thanks so much!
Ok, thanks for the dbfiddle!
So, to NOT find a matching pair of Country and Region in Cobertura, before insert into table DireccionEntrega, the trigger handling changes to:
if not exists(
select Cobertura.Pais, Cobertura.Region
from Cobertura
join Proveedor ON Cobertura.idProv = Proveedor.idProv
join LineasPedido ON Proveedor.idProv = LineasPedido.idProv
where Cobertura.Pais = NEW.Pais and Cobertura.Region = NEW.Region
) then
set #message = CONCAT(
'Error insert... did not find a matching pair for: ',
NEW.Pais, ' and ', NEW.Region, ' in Cobertura.'
);
signal sqlstate '45000'
set message_text = #message;
end if;
So with the two fiddle insert query examples:
insert into DireccionEntrega(idT, idPed, N, Calle, Poblacion, Ciudad, Telefono, email, Pais, Region)
values (1,1,13,'Alba','malaga','malaga', 654544434,'shop1#gmail.com','España','Este');
insert into DireccionEntrega(idT, idPed, N, Calle, Poblacion, Ciudad, Telefono, email, Pais, Region)
values (1,2,11,'CAdd','dos hermanas','sevilla', 654544434,'shop1#gmail.com','Portugal','Oeste');
Because neither España and Este and Portugal and Oeste are not found in Cobertura, both give the error:
Error insert... did not find a matching pair for: España and Este in Cobertura.
Error insert... did not find a matching pair for: Portugal and Oeste in Cobertura.
So those 2 records do NOT get inserted.
Here's the updated dbfiddle example with the trigger change applied.
Related
For some background this is part of an assignment for my Databases class. We have a set of tables that all entail towards Company information. This includes WORKS_ON which shows the hours that Employees work on specific projects. The goal of my code was to write a trigger that prevents a user from assigning more than 40 hours of total work to one employee. I intended to do this via obtaining the sum of hours that are currently in the table associated with the new row's Employee SSN(Essn). From there I meant to add the sum to the new row's hours and compare to 40. If the amount was more than 40, then a custom message is concatenated and an error is raised to the user. The professor included PHP which handles the printing of that error for me. As of now the only potential fix/error I can think of is that it is not legal to perform arithmetic within the if statement. Please let me know if you see anything I need to fix/improve.
DELIMITER &&
CREATE TRIGGER MAXTOTALHOURS
BEFORE INSERT ON WORKS_ON FOR EACH ROW
BEGIN
DECLARE sumHours integer;
DECLARE customMessage VARCHAR(100);
SELECT SUM(hours) INTO sumHours FROM WORKS_ON WHERE Essn = New.Essn;
IF (sumHours + New.hours > 40) THEN
SET customMessage = concat('You entered', New.hours, '. You currently work ', sumHours, '. You are over 40 hours!');
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = customMessage;
END IF;
END &&
DELIMITER ;
I have three tables. They are called book, reader, borrow.
The function of my trigger: insert borrowing records into the borrow table. But before inserting, we have to check the status of the reader's library card and the status of the book. Only when they are all normal can we lend the book to the reader, that is, change the status of the borrowed book in the book table to "borrowed", and then insert the borrowing record into the borrow table. However, some errors occurred when updating the book table.
Please look at my note 1, which reported me such an error: [stored trigger is already used by statement which invoked stored trigger]. I know that the trigger of MySQL can't modify the defined table, so I think this error is very puzzling. Then I created a new account table and added the sentence marked in Note 2 to the trigger. It works properly. So this mistake really confused me.
I would be very grateful if you could help me. Followings is my code:
DROP TRIGGER IF EXISTS before_insert_borrow;
CREATE TRIGGER before_insert_borrow BEFORE INSERT ON borrow
FOR EACH ROW
BEGIN
DECLARE cst VARCHAR(10);
DECLARE b_cts VARCHAR(10);
SELECT Card_status FROM reader WHERE read_no = NEW.read_no INTO cst;
SELECT B_status FROM book WHERE book_no = NEW.book_no INTO b_cts;
IF cst = 'loss' THEN
SIGNAL SQLSTATE '09000' SET MESSAGE_TEXT = 'your card is lost……';
ELSEIF b_cts = 'checked' THEN
SIGNAL SQLSTATE '09000' SET MESSAGE_TEXT = 'the book you want has been checked out.';
END IF;
UPDATE book SET B_status = 'checked' WHERE book_no = NEW.book_no; -- 1
UPDATE account SET total = total + 1000 WHERE id =1; -- 2
END;
-- test data
INSERT INTO borrow(read_no,book_no)VALUES('001','B17682');
Currently trying to have a generic activity log table that stores which table, field, value changed (+ necessary primary key)
DELIMITER $$
CREATE TRIGGER tr_customers_insert_activity_log AFTER INSERT ON `customers`
FOR EACH ROW
BEGIN
DECLARE curr_column CHAR(255);
DECLARE finished INT DEFAULT false;
DECLARE column_name_cursor CURSOR FOR SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'customers' ORDER BY ordinal_position;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET finished = 1;
OPEN column_name_cursor;
column_loop: LOOP
IF finished THEN
LEAVE column_loop;
END IF;
FETCH column_name_cursor INTO curr_column;
INSERT INTO activity_log(`cid`, `table`, `field`, `value`, `modified_by`, `modified_at`)
VALUES (NEW.cid, 'customers', curr_column, NEW.#curr_column, NEW.modified_by, NEW.modified_at);
END LOOP column_loop;
CLOSE column_name_cursor;
END$$
DELIMITER ;
The problem I have is in here:
INSERT INTO activity_log(`cid`, `table`, `field`, `value`, `modified_by`, `modified_at`)
VALUES (NEW.cid, 'customers', curr_column, NEW.#curr_column, NEW.modified_by, NEW.modified_at);
Since I am dynamically looping through each field by name I don't know how I can get the NEW.#curr_column value. How can you access a property of the NEW/OLD objects using the value of a variable?
To clarify the syntax error is:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '#curr_column, NEW.modified_by, NEW.modified_at); END LOOP column_loop' at line 17
Thanks!
It's not possible to dynamically address the NEW and OLD values within a TRIGGER.
We can use a CASE expression. But this probably isn't what you were looking for. It's not really "dynamic". We need to statically address each column name we're interested in.
CASE curr_column
WHEN 'cid' THEN NEW.cid
WHEN 'foo' THEN NEW.foo
WHEN 'othercol' THEN NEW.othercol
END
Also problematic is the various datatypes of the columns you might want to store in activity_log table value column... DATE, INTEGER, DECIMAL, ENUM, VARCHAR, ... those are all going to need to be cast to a single datatype of the value column.
Some alternatives to consider:
have the trigger save a copy of the entire row
rather than making the trigger "dynamic", make the creation of the trigger more dynamic... i.e. use a SELECT from information_schema.columns to assist in producing the contents needed in the trigger definition
So let's say i have a table that has a column "id_author" , i'd like to set a limit for the occurrence of the same value ; example : can't have more than 3 same "id_author" values so when i insert the 4th one it's refused.
Is there a way to implement this? Thanks
You can use a trigger before insert, that will throw a signal in case it violates your condition:
CREATE TRIGGER tooManyRecords
BEFORE INSERT ON yourTable
FOR EACH ROW
BEGIN
DECLARE counter INTEGER;
SELECT COUNT(*) INTO counter FROM yourTable
WHERE id_author = NEW.id_author;
IF counter >= 3 THEN
SIGNAL SQLSTATE '45000' SET message_text = 'there are already 3 records for the provided id';
END
I've to write a trigger on my table which will perform the following functions.
Before Update on row, check price of Item
If price has changed from the last price, then select the table name, where to insert the item name, from another table having type of item and the associated table name.
Insert the item name in the selected table.
To put simply i've a table(TypeNameTable) having item categories and corresponding table names, if the price of item has changed then i've to get the table name from the TypeNameTable and insert the item name in the table, which is retrieved from TypeNameTable.
I'm not able to insert into table when I get the table names dynamically. Please suggest how to do it. Here's what I'm doing:
BEGIN
#declare countryTableName varchar(50);
declare itemPrice int;
declare itemTableName text;
IF (New.Price != Old.Price) THEN
SET countryTableName = (select `ItemManager`.`TypeNames`.`TypeTableName`
from `ItemManager`.`TypeNames`
where `ItemManager`.`TypeNames`.`ItemType` = NEW.ItemType);
INSERT INTO `ItemManager`.itemTableName
( `ItemName`, `ItemPrice`,
VALUES
( NEW.Name, New.Price );
END IF;
END$$
I get the error
ItemManager.itemTableName doesn't exists.
Answering my own question.
Figured out that using Dynamic SQL is not allowed in MySQL triggers . The restrictions are listed here.
However it's possible in Oracle where we can use PRAGMA AUTONOMOUS_TRANSACTION which executes the query in new context, and hence supports Dynamic SQL.
Example listed here at Point 27 .
You could CONCAT() your INSERT statement into a variable and execute that as PREPARED STATEMENT, someting like
...
SET #sql := CONCAT( 'INSERT INTO ', itemTableName, ' ... ' );
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
...
afaik this is the only way to process dynamically generated SQL in stored routines and triggers.
If it is possible, I'd suggest you to change design a little. Instead of different tables you can create one table itemTable.
...
IF (New.Price != Old.Price) THEN
INSERT INTO `ItemManager`.`itemTable`
( `ItemName`, `ItemPrice`,
VALUES
( NEW.Name, New.Price );
END IF;
...
If there are different item properties, this table can be a parent table for specific child tables.