I have some simple triggers on a table which work for to audit changes to a record by logging to a table any time an INSERT, UPDATE, or DELETE occurs. When a record is inserted into Users, a copy is made in Users_History with a Status field populated with the value 'INSERT'. Same is done for UPDATE and DELETE.
DELIMITER $$
CREATE TRIGGER Users_History_Insert AFTER INSERT ON Audit
FOR EACH ROW BEGIN
INSERT INTO Users_History select *, 'INSERT' from Users where ID = NEW.ID;
END$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER Users_History_Delete BEFORE DELETE ON Audit
FOR EACH ROW BEGIN
INSERT INTO Users_History select *, 'DELETE' from Users where ID = OLD.ID;
END$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER Users_History_Update AFTER UPDATE ON Audit
FOR EACH ROW BEGIN
INSERT INTO Users_History select *, 'UPDATE' from Users where ID = NEW.ID;
END$$
DELIMITER ;
We have a function where two Users records can be merged with each other. Effectively, all values of one record are overwritten with the values of another record, and the unchanged record is deleted.
If you had a table such as this:
| ID | Name | email |
| 1 | Billy | bill#mail.com |
| 2 | Bill | bill2#mail.com |
And I wanted to merge record 2 into 1, I'd end up with this result.
| ID | Name | email |
| 1 | Bill | bill2#mail.com |
I'd like to create a trigger that would have this in Users_History where 2 is the record merged from.
| Users_ID | Name | Email | Status |
| 1 | Bill | bill2#mail.com | MERGED:2 |
As it stands now, we would end up with two records, an UPDATE and a DELETE
UPDATE `Users_To`
SET `Name` = `Users_From`.`Name`, `email` = `Users_From`.`email`
FROM `Users` AS `Users_To`
CROSS JOIN `Users` AS `Users_From`
WHERE `Users_To`.`ID` = 1 AND `Users_From`.`ID` = 2;
DELETE FROM `Users` WHERE `ID` = 2;
I can't think of a way to do this in SQL, is it possible? As I understand, the triggers have no knowledge of the JOIN or WHERE clause affecting a record.
Your UPDATE query has a flaw and is correctly
UPDATE `Users` AS `Users_To` CROSS JOIN `Users` AS `Users_From`
SET `Users_To`.`Name` = `Users_From`.`Name`, `Users_To`.`email` = `Users_From`.`email`
WHERE `Users_To`.`ID` = 1 AND `Users_From`.`ID` = 2;
DELETE FROM `Users` WHERE `ID` = 2;
What the trigger does is simple first it check if the update is a modify of an existing row by checking if there are mor than one rows with the same email.
If that the case grab the other id in your case 2 and add it ti the text.
This works as long you have only 2 rows involved in your update query.
And i don't know your your table, but if you have a UNIQUE constraint you will have a problem with the update.
DELIMITER $$
DROP TRIGGER IF EXISTS Users_History_Update;
CREATE TRIGGER Users_History_Update AFTER UPDATE ON users
FOR EACH ROW BEGIN
SELECT Count(*) INTO #number FROM Users WHERE `email` = NEW.`email`;
IF #number > 1 THEN
SELECT `ID` INTO #id FROM Users WHERE `email` = NEW.`email` AND `ID` <> NEW.`ID`;
INSERT INTO Users_History VALUES (OLD.`ID`,OLD.`Name` ,OLD.`email`, CONCAT('MERGED:',#id));
ELSE
INSERT INTO Users_History VALUES (OLD.`ID`,OLD.`Name` ,OLD.`email`, 'UPDATE');
END IF;
END$$
DELIMITER ;
Related
Assuming MySQL with a users table like
id | user_name | total_likes | updated_at
and a likes table like
id | user_id | like
What I need is to when the likes table gets updated/inserted/deleted then the users.updated_at associated with likes.user_id gets updated to the current date. Further when an insert happens to the likes table, then users.total_likes increases by 1 value.
I can do all the above with queries however, I am trying to use the power of relationships in MySQL. Please can you advise?
As David mentioned above, you are most likely looking for triggers. Something like this:
Delimiter //
CREATE TRIGGER ai_likes AFTER INSERT ON likes
FOR EACH ROW
BEGIN
INSERT INTO users (updated_at) VALUES
NOW()
WHERE id = NEW.id;
UPDATE users SET total_likes = (total_likes + 1)
WHERE id = NEW.id;
END//
Delimiter ;
CREATE TRIGGER au_likes AFTER UPDATE ON likes
FOR EACH ROW
INSERT INTO users (updated_at) VALUES
NOW()
WHERE id = NEW.id;
CREATE TRIGGER ad_likes AFTER DELETE ON likes
FOR EACH ROW
INSERT INTO users (updated_at) VALUES
NOW()
WHERE id = NEW.id;
So my parent table, which is a lookup table shares an attribute with my child table (foreign key). So my goal is to have an after_delete trigger, or any trigger that will update the foreign key in the child table when a row in a parent table is deleted, without having to delete the whole row containing information in the child table. For example, I have a table named Status with the the attributes StatusID and Status. The statuses are WORKING and BROKEN. Then I have a Laptops Table that has the attributes LaptopID, LaptopName, and StatusID (Foreign Key).
For my Status Table, I have:
| 1 | WORKING |
| 2 | BROKEN |
Now Let's say I have a record:
1001 | Lenovo 17XS | 1 |
1002 | DELL XPS | 2 |
My goal is when I delete the second row of my status table, I want the second row of my Laptops table to say:
1002 | DELL XPS | DELETED |
I have turned cascade delete off for the foreign key so it does not delete the whole record.
This is my code for my attempt:
CREATE DEFINER=`TEST`#`%` TRIGGER `status_AFTER_DELETE` AFTER DELETE ON `status` FOR EACH ROW BEGIN
UPDATE Laptops INNER JOIN Status ON Laptops.StatusID = Status.StatusID
Set Laptops.StatusID = 0 WHERE Status.StatusID = Laptops.StatusID;
END
However, the trigger is not working.
I have added a record called "DELETED" with a StatusID 0 afterwards but the trigger is still not working. What am I doing wrong?
An after update trigger is too late, you could do this in a before trigger and you should reference OLD. values (see manual for details) nb debugging triggers is a pain and I usually write to a debug_table when I get into difficulty. eg
DROP TABLE IF EXISTS laptops;
drop table if exists status;
drop trigger if exists t;
create table status(statusid int primary key, val varchar(20));
create table laptops( LaptopID int, LaptopName varchar(20), StatusID int,
foreign key fk1(statusid) references status(statusid)) ;
insert into status values
(1,'working'),(2,'broken'),(0,'deleted');
insert into laptops values
(1001 , 'Lenovo 17XS', 1 ),
(1002 , 'DELL XPS' , 2 );
delimiter $$
CREATE TRIGGER `status_AFTER_DELETE` before DELETE ON `status`
FOR EACH ROW
BEGIN
insert into debug_table(msg) values ('fired');
UPDATE Laptops
Set Laptops.StatusID = 0 WHERE old.StatusID = Laptops.StatusID;
END $$
delimiter ;
truncate table debug_table;
delete from status where statusid = 2;
select * from status;
select * from debug_table;
Let's say we got two tables.
First table is items - id, title.
Second table is history - id, title, action, user.
We can have following AFTER INSERT trigger for "This user inserted this item":
INSERT INTO history (title, action, user) VALUES (NEW.title, 'INSERT', #phpUserId);
If I want to insert new item, I can do something like this.
SET #phpUserId = 123;
INSERT INTO items (title) VALUES ('My best item');
In this case, trigger works perfectly.
But the problem is, when I add some text into variable - for example SET #phpUserId = "library123"; - In this moment the trigger is not able to take that variable.
Any ideas why only integer variables are passed?
Good news there's nothing wrong with your trigger and here's the proof
drop table if exists i,h;
create table i(id int, title varchar(20));
create table h(id int, title varchar(20), action varchar(20), user varchar(30));
drop trigger if exists t;
delimiter $$
create trigger t after insert on i
for each row
begin
INSERT INTO h (title, action, user) VALUES (NEW.title, 'INSERT', #phpUserId);
end $$
delimiter ;
SET #phpUserId = 123;
INSERT INTO i (title) VALUES ('My best item');
SET #phpUserId = 'bob123';
INSERT INTO i (title) VALUES ('My worst item');
+------+---------------+--------+--------+
| id | title | action | user |
+------+---------------+--------+--------+
| NULL | My best item | INSERT | 123 |
| NULL | My worst item | INSERT | bob123 |
+------+---------------+--------+--------+
2 rows in set (0.00 sec)
I am struggling with a trigger to update a second table based on the first table being updated.
I have tried this:
DELIMITER //
FOR EACH ROW
BEGIN
Update phpfb_picks
set points = NEW.value
where username = user and gameid = gameid
END;
//
delimiter;
but get an error about syntax.
If I do:
Update phpfb_picks
set points = NEW.value
where username = user and gameid = gameid
This works to a point. It updates all users records with the same value that was updated.
What I want to do is, when a value is updated on Table A, I want to update Table B with all records for that user, basically the 'value' from table A would always be pushed to 'points' of table B for the user, regardless if the actual record was updated.
If any record for user is updated, update all of table B records with the same value based on the username and gameid
So if Table A has the following records:
username - test
gameid - 1
value - 1
username - test
gameid - 2
value - 2
when value from row 1 is updated to 3, i want to update Table B with all the current values from table A for that user.
Is this even possible?
UPDATE:
Table A (allpoints) has columns:
username
gameid
value
TABLE B (phpfb_picks) has columns:
user
gameid
points
allpoints.username = phpfb_picks.user
allpoints.gameid = phpfb_picks.gameid
allpoints.value = phpfb_picks.points
Whenever an update is made to allpoints, I want all the records for that specific user to update all the specific records for that user in phpfb_picks, passing the allpoints.value to phpfb_picks.points based on the user and gameid
Try this trigger:
DELIMITER $$
CREATE TRIGGER update_phpfb_picks
AFTER UPDATE ON allpoints FOR EACH ROW
BEGIN
UPDATE phpfb_picks
INNER JOIN allpoints ON allpoints.username = phpfb_picks.username AND
allpoints.gameid = phpfb_picks.gameid
SET phpfb_picks.points = allpoints.value
WHERE phpfb_picks.username = NEW.username;
END;
$$
DELIMITER ;
For each row that is updated in allpoints, all rows in phpfb_picks with the same username of the updated row will be updated to the respective value of points present in allpoints as value. The trigger is an AFTER UPDATE so that the newly updated value in allpoints will also be set in phpfb_picks.
Maybe this
drop trigger if exists phpfb;
DELIMITER $$
create trigger phpfb after update on allpoints
FOR EACH ROW
BEGIN
Update phpfb_picks
set points = NEW.value
where user = new.username and gameid = new.gameid;
END $$
delimiter ;
for example
drop table if exists allpoints,phpfb_picks;
create table allpoints (username char(4),gameid int, value int);
create table phpfb_picks (user char(4),gameid int, points int);
insert into allpoints (username,gameid) values
('test',1),('test',2);
insert into phpfb_picks (user,gameid) values
('test',1),('test',1),('test',2);
update allpoints
set value = 2 where username = 'test' and gameid = 1;
result
+------+--------+--------+
| user | gameid | points |
+------+--------+--------+
| test | 1 | 2 |
| test | 1 | 2 |
| test | 2 | 0 |
+------+--------+--------+
3 rows in set (0.00 sec)
I have this relation (A):
ID | B_ID
This relation (B):
ID | FOO
I want to add a trigger to A which will, on insertion of a new row into A (with B_ID always set as NULL), add a row in B with FOO set as NULL and set the B_ID attribute in the new row of A to reference the newly added B row.
Example:
Right after insertion into
A:
1 | NULL
After trigger action:
A:
1 | 555
B:
555 | NULL
Is this possible?
You can try something like the following:
/* Trigger structure for table `a` */
DELIMITER $$
CREATE TRIGGER `trg_a_bi` BEFORE INSERT ON `a`
FOR EACH ROW
BEGIN
INSERT INTO `b` (`foo`) VALUES (NULL);
SET NEW.`b_id` := LAST_INSERT_ID();
END$$
DELIMITER ;
Here a SQL Fiddle.