Trigger: Referencing updated attribute - mysql

I have a Trigger on UPDATE.
What is the correct procedure for referencing attribute from the table that is not updated by the UPDATE SQL command? Is the attribute still in the UPDATE variable? I would like to get the value of that attribute for the updated row.

You can access a values of a column before update and after update in MySQL by using keywords OLD and NEW.
For example if you want to determine whether a value of a column actually has been changed during updated you can do
IF NOT OLD.column_name <=> NEW.column_name THEN
-- do something here
END IF;
Note: <=> is NULL-safe comparison operator in MySQL
BTW: There is no UPDATED virtual table in MySQL. It's from SQL Server.
Here is a SQLFiddle demo. Note that even though update affected all records in the table, only one message has been logged in log table. It's because value for a row with id 2 in the end stayed the same.
UPDATE: to keep your finished flag in sync you need triggers for all events (insert, update, delete).
DELIMITER //
CREATE TRIGGER tg_ai_event
AFTER INSERT ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END//
CREATE TRIGGER tg_ad_event
AFTER DELETE ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = OLD.activity;
END//
CREATE TRIGGER tg_au_event
AFTER UPDATE ON event
FOR EACH ROW
BEGIN
IF NOT OLD.activity <=> NEW.activity THEN
-- if activity id was changed for an event then clculate finished flag
-- for both old and new activity id
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id IN(OLD.activity, NEW.activity);
ELSE
-- otherwise calculate finished flag only if done flag is changed
IF NOT OLD.done <=> NEW.done THEN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END IF;
END IF;
END//
DELIMITER ;
Here is SQLFiddle demo

Related

MYSQL drop table when all the values within a specific column are equal to "DONE"

I am programming MYSQL and I use Python on Raspberry PI 4.
I need to drop table when all the values in my status_s column are equal to "DONE". I cannot figure out how to drop table under a certain condition. MYSQL tables can be found here for testing:
https://www.db-fiddle.com/f/siZmmKWLjRDdpYX6deEPYF/1
Initially, the status_s values are not "DONE". As my program runs, the values update and eventually all of them will be "DONE", at that point, I do not want to have this table anymore as it is not important.
Thanks in advance
UPDATE Adding snippet of Python program
def update_data_when_complete(conn,table_name):
cur = conn.cursor()
sql = "SELECT COUNT(DISTINCT(ID)) = SUM(Status = 'DONE') FROM {table}"
cur.execute(sql.format(table=table_name))
complete_result = cur.fetchone()
conn.commit()
#print("COmplete result = ",complete_result[0])
# if complete_result[0] is 1 here, all rows are "DONE" and must delete table after few minutes
if(complete_result[0] == 1):
sql = "DROP TABLE {table}"
cur.execute(sql.format(table=table_name))
conn.commit()
else:
print("Table not fully complete yet")
Use Event Scheduler.
Create event procedure:
CREATE EVENT remove_temptable
ON SCHEDULE
EVERY 1 MINUTE
COMMENT 'Remove `temptable` when its `status_s` column is equal to "DONE" in all rows.'
DO
BEGIN
IF EXISTS ( SELECT NULL
FROM INFORMATOIN_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'my_database'
AND TABLE_NAME = 'temptable' ) THEN
IF !( SELECT SUM(status_s != 'DONE')
FROM my_database.temptable ) THEN
DROP TABLE my_database.temptable;
END iF;
END IF;
END;
This procedure will check the table temptable for its existence firstly. If it exists then the procedure checks does a row with non-NULL value in status_s column other than 'DONE' exists. If not then the procedure drops the table.
The procedure is executed each minute. You may adjust how often it is executed. Also, when it is created, you may enable or disable it using ALTER EVENT (for example you may enable it after temptable creation and disable after you ensure the table is dropped).
Do not forget to enable Event Scheduler.

How to use Case-When and Update in Trigger?(Error in my code)

I tried to create below Trigger but I have an Error.
Can I Use Update or use 'case when' in trigger?
Please help me to fix my issue here.
Code Explanation:
I want to update my existing row after insert or update.
Do not change the 'FUSDate1' or 'FUSDate2' If I didn't add anything to the 'FUSDate1' or 'FUSDate2'
Update the FUSDate to the New One or Inserted one If I Updated or inserted data to FUSDate
Code:
delimiter //
create trigger SafetyCertificationTRG
after insert on SafetyCertification
for each row
begin
case when (FUSDate1='' or FUZDate1 is NULL) then (FUZDate1=OLD.FUSDate1) else (update SafetyCertification set FUZDate1=NEW.FUSDate1) end;
case when (FUSDate2='' or FUZDate2 is NULL) then (FUZDate2=NEW.FUSDate2) else (update SafetyCertification set FUZDate2=NEW.FUSDate2) end;
end //
delimiter ;
Edit: I am going to add some information here to make the question more clear.
I have one column as FUS that can be get these 3 values only:('FUS1', 'FUS2' and 'FUS3')
I have 3 other columns: FUSDate1, FUSDate2, FUSDate3.
I want to save the Current Date to The FUSDate1 Or FUSDate2 Or FUSDate3 based on the user User Selection of FUS. (They are in same Table)
I used the provided answer and change it to this but I cannot do the above.
Code: This code is just for FUS1 and FUSDate1
delimiter //
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- detect a change made to a value in col
IF OLD.FUS <=> NEW.FUS THEN
-- value of col is not changed, so do nothing
DO 0;
ELSE
-- we detected a new value was assigned to col
IF OLD.FUS ='%FUS1%' THEN
-- we can override the new value, keep it the same
SET NEW.FUSDate1 = CURDATE();
END IF;
END IF;
END //
delimiter ;
Another code that I expected to do my work but still have problem, Does not update Like above Code:
delimiter //
CREATE TRIGGER SafetyCertification_bu
BEFORE INSERT ON SafetyCertification
FOR EACH ROW
BEGIN
IF NEW.FUS='%FUS1%' THEN
SET new.FUSDate1=MD5(CURDATE());
END IF;
END //
delimiter ;
Update 3:
The Third code that provided in answer does not add anything to the FUSdate1 and 2 and 3 when i update or insert any data.
Code
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
A couple of notes:
A trigger cannot issue an UPDATE against the table that fired the trigger, this is prohibited.
The unqualified references (e.g. FUSDate1 and FUZDate1) are not valid. Those references don't resolve to anything.
The reference to OLD.FUSDate1 is invalid in the context of an AFTER INSERT trigger. In an UPDATE trigger, the OLD.col refers to the value of col before the update.
The expression (FUZDate1=OLD.FUSDate1) is not an assignment, it's a comparison that's going to return 0, 1 or NULL.
If we want to trigger on an UPDATE and an INSERT, that will require two separate triggers.
If we want to modify the contents of the current row, we can use BEFORE trigger instead of AFTER.
I want to update my existing row after insert or update.
It would be much easier to apply changes before the row is inserted or updated. We can assign a value to a column col by referencing NEW.col in an assignment in a BEFORE INSERT or BEFORE UPDATE trigger. For example:
SET NEW.col = expr;
Do not change the FUSDate1 or FUSDate2 If I didn't add anything to the FUSDate1 or FUSDate2
This seems pretty straightforward. Just don't make any assignments to NEW.FUSDate1 or NEW.FUSDate2.
Update the FUSDate to the New One or Inserted one If I Updated or inserted data to FUSDate
The example proposed trigger contains references to FUZDate1 or FUZDate2, but there's no mention of these columns in the specification. The specification is confusing.
An UPDATE statement will assign a value to a column, no need for a trigger to do that. An INSERT statement can assign a value to a column, again, no need for a trigger for that.
The specification is not clear. Providing example starting state (rows in the table), and example INSERT or UPDATE statements, and the desired state after the statement is executed would go a long ways towards clarifying the requirements.
A demonstration of a BEFORE UPDATE trigger that prevents a new value being assigned to a particular column:
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- detect a change made to a value in col
IF OLD.col <=> NEW.col THEN
-- value of col is not changed, so do nothing
DO 0;
ELSE
-- we detected a new value was assigned to col
IF OLD.col IS NOT NULL THEN
-- we can override the new value, keep it the same
SET NEW.col = OLD.col;
END IF;
END IF;
END$$
DELIMITER ;
For what this example trigger achieves, we don't necessarily need that many IF conditions; those are included as a demonstrate some of the checks we can perform, how we can make references to the existing value of col and the new value assigned to col.
FOLLOWUP
Based on the update information in the question, here's an example of a BEFORE UPDATE trigger that satisfies the specification.
This is for the UPDATE action. To get this same behavior with an INSERT statement, this trigger definition needs to be repeated, with BEFORE INSERT in place of BEFORE UPDATE.
(I would use the name _bu for the before update trigger, and _bi for before insert trigger.)
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
second followup
To answer the question about using CASE WHEN statement in the context of a MySQL stored program...
We could implement the example trigger (directly above in this answer) replacing the IF-THEM with either of the two forms of the CASE statement.
either
CASE
WHEN NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
WHEN NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
WHEN NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END CASE;
-or-
CASE NEW.fus
WHEN 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
WHEN 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
WHEN 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END CASE;
note
The CASE statement is available in MySQL stored programs; outside of a stored program, it is not a valid SQL statement.
Also, we should not confuse this CASE statement with the CASE expression. The CASE expression is valid in the context of a SQL statement, such as a SELECT or UPDATE statement.
DEMONSTRATION
"I examined the Trigger, But unfortunately does not save anything in fusDates."
#Christiano: here is a simple demonstration
create table
CREATE TABLE `safety_certification`
( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
, fus VARCHAR(5)
, fusdate1 DATE
, fusdate2 DATE
, fusdate3 DATE
) ENGINE=INNODB
;
populate table with demonstration rows
INSERT INTO `safety_certification` (id, fus, fusdate1, fusdate2, fusdate3) VALUES
( 1, '', NULL, NULL, NULL)
,( 2, '', NULL, NULL, NULL)
,( 3, '', NULL, NULL, NULL)
,( 4, '', NULL, NULL, NULL)
,( 5, '', NULL, NULL, NULL)
;
create trigger
DELIMITER $$
CREATE TRIGGER `safety_certification_bu`
BEFORE UPDATE ON `safety_certification`
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
updates will exercise BEFORE UPDATE trigger we just defined
UPDATE `safety_certification` sc SET sc.fus = 'FUS1' WHERE sc.id = 1 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS2' WHERE sc.id = 2 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS3' WHERE sc.id = 3 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS4' WHERE sc.id = 4 ;
display contents of table
SELECT * FROM `safety_certification`;
returns:
id fus fusdate1 fusdate2 fusdate3
------ ------ ---------- ---------- ------------
1 FUS1 2018-05-04 (NULL) (NULL)
2 FUS2 (NULL) 2018-05-04 (NULL)
3 FUS3 (NULL) (NULL) 2018-05-04
4 FUS4 (NULL) (NULL) (NULL)
5 (NULL) (NULL) (NULL)
seems like the trigger is populating the columns fusdate1, fusdate2 and fusdate3 per the specification, when some particular values are assigned to fus
I had a same problem and I used two trigger,
First trigger send data to the other table, so now you can create another trigger to send this data to the first table. I think it is low performance but it is the only way that I reached to it. I hope others have another solution for u.

MySQL Triggers that updates a table based on inserted and stored values

I have to create a trigger in a table proyect, to check if all the tests for the proyect are 'ready'. If all of them are ready, I need to search the proyect and put the status of the proyect as 'ready'. If not, Incomplete.
I'm aware that I need to create one trigger for the UPDATE, and another one for the INSERT. I've planned something like this, But I can not make it work.
I created this solution, if the total number of test for that proyect, is below to the number of test ready for that proyect, then the proyect is not ready. and if both are equals, the proyect is ready.
I don't know why it doesn't work:
CREATE TRIGGER update-proyect
AFTER INSERT ON tests
FOR EACH ROW
SET #total = select COUNT(*)
from tests
where IdProyect= NEW.IdProyect;
SET #ready = select COUNT(*)
from prueba
from tests
where IdProyect= NEW.IdProyect
AND status = 'ready';
IF (#total == #ready)
UPDATE proyect SET status = 'Ready' WHERE IdProyect = NEW.IdProyect;
ELSE
UPDATE proyect SET status = 'Incomplete' WHERE IdProyect = NEW.IdProyect;
END IF;
Loads of errors update-proyect is an invalid trigger name change to update_proyect, If you have more than 1 statement in a trigger they have to be encased in a begin and end statements, a select after a set has to be encased in braces, there is no == comparison relation operator in mysql either use = or null safe equals <=>, an if statement has to have a then statement.
This at least syntaxes.
DROP TRIGGER IF EXISTS update_proyect;
DELIMITER $$
CREATE TRIGGER update_proyect
AFTER INSERT ON tests
FOR EACH ROW
BEGIN
SET #total = (select COUNT(*)
from tests
where IdProyect= NEW.IdProyect
);
SET #ready = (select COUNT(*)
from prueba
#from tests
where IdProyect= NEW.IdProyect
AND status = 'ready'
);
IF (#total = #ready) THEN
UPDATE proyect SET status = 'Ready' WHERE IdProyect = NEW.IdProyect;
ELSE
UPDATE proyect SET status = 'Incomplete' WHERE IdProyect = NEW.IdProyect;
END IF;
END $$
DELIMITER ;

MySQL procedure gone wrong

I have a MySQL database in which I have the following rows (by exemple) created by default (id, task and case may be different but the current value is always 1)
....idtaskcaseuser............datecurrent
238......31001.....0..............null..........1
239......41001.....0..............null..........1
I have to randomly create rows like this with insert statement (new rows). As you can see a date is filled and de current equal 0
....idtaskcaseuser............datecurrent
240......51001.....12015.04.03..........0
241......21002.....12015.04.03..........0
When I come across one of the lines created by default I want to use an update instead of an insert statement.
So I created the following procedure in MySQL
DELIMITER //
DROP PROCEDURE IF EXISTS FillProgress//
CREATE PROCEDURE FillProgress ( get_case INT(10),get_task INT(10), get_user INT(10) )
BEGIN
DECLARE test tinyint(1);
SET test = (SELECT COUNT(*) FROM progress WHERE case_id = get_case AND task_id = get_task);
IF test = 1 THEN
UPDATE progress SET current = 0, date = NOW(), user_id = get_user WHERE task_id = get_id AND case_id = get_case;
ELSE
INSERT INTO progress(task_id,case_id,user_id,date,current) VALUES (get_task,get_case,get_user,NOW(),0);
END IF;
END; //
DELIMITER ;
I use count to see if a already have a row with the same case and task. If it's true (test=1) I use UPDATE, otherwise and use INSERT.
If I test with the following row already wrote in the database
....idtaskcaseuserdatecurrent
241......41001.....0..null..........1
I use CALL FillProgress(1001,4,1);
The row is not updated, but I do not have any error message.
11:38:02 CALL FillProgress(1001,4,1) 0 row(s) affected 0.000 sec
And if I manually use my update query
UPDATE progress SET current = 0, date = NOW(), user_id = 1 WHERE task_id = 4 AND case_id = 1001;
It works like a charm.
The insert query also works fine.
The UPDATE query within the procedure has a "WHERE task_id = get_id" clause, however I don't see get_id being defined in the procedure; there is a "get_task" parameter for the stored procedure, though.

Make a query in mysql without invoking a trigger (How to disable a trigger)

I have 2 tables: comments and comments_likes.
comments
id
message
likes
triggers:
AFTER DELETE
DELETE FROM comments_likes WHERE comment_id = OLD.id;
comments_likes
id
comment_id
triggers:
AFTER INSERT
UPDATE comments SET likes = likes + 1 WHERE comments.id = NEW.comment_id;
AFTER DELETE
UPDATE comments SET likes = likes - 1 WHERE comments.id = OLD.comment_id;
AFTER UPDATE
**omited code, updates comments**
So the question is, can I disable the triggers when activating them from another trigger?
What I want is do something likes this:
AFTER DELETE
IF NOT called_from_another_trigger() THEN
UPDATE comments SET likes = likes - 1 WHERE comments.id = OLD.comment_id;
END IF;
[EDIT]
A non optimized solution would be (very slow query... makes a query for each LIKE register):
BEGIN
IF (SELECT id FROM comments WHERE comments.id = OLD.comment_id) THEN
UPDATE comments SET comments.cache_likes = comments.cache_likes - 1 WHERE comments.id = OLD.comment_id;
END IF;
END
UPDATE LOW PRIORITY and IGNORE don't works.
[EDIT 2]
I have another idea, is possible to set a global variable in the first trigger and read it from the other trigger?
Ex:
first trigger:
#disable_triggers = true;
// do the stuff that calls another triggers
#disable_triggers = false;
other trigger:
if #disable_triggers = false then
// do the stuff
end if;
To disable triggers you can do:
Trigger 1
SET #disable_trigger = 1;
// do stuff that calls trigger 2
SET #disable_trigger = NULL;
Trigger 2
IF #disable_trigger IS NULL THEN
// do stuff only if called from a query and not from trigger 1
END IF;
Can be a bit ugly, but what i do is rename the table to table2 which the trigger does not attach too, and then, on the end, rename back.
No you can't. That's the point of triggers: to be run always.
Besides, I can't see why you would need that in your case. At worst nothing gets updated - no errors will be raised.
You can always add a condition in your trigger, to check if they (or part of their code) should be run (for example if there is a record in relevant table).