I have a MySQL database with...interesting architecture and a convoluted enrollment process. There are several program tables that need to insert rows on an historic enrollment table when they're updated, inserted or deleted. I've got that working using triggers on each of those tables (around 30 different tables) using the following iterated over all of the tables:
DROP TRIGGER IF EXISTS programTable_afterinsert;$$
CREATE TRIGGER programTable_afterinsert AFTER INSERT ON programTable
FOR EACH ROW
BEGIN
IF NEW.Enrolled = 1
THEN
INSERT INTO enrollment (ID, Action, Date_Updated, User, Program, Reason, Action_Date)
VALUES (NEW.ID, 'Enrolled', NOW(), 'programUser', 'programName', 'Enrolled in program', NOW());
ELSEIF NEW.Enrolled = 0
THEN
INSERT INTO enrollment (Member_ID, Action, Date_Updated, User, Program, Reason, Action_Date)
VALUES (NEW.ID, 'Disenrolled', NOW(), 'programUser', 'programName', 'Disenrolled from program', NOW());
END IF;
END;$$
DROP TRIGGER IF EXISTS programTable_afterupdate;$$
CREATE TRIGGER programTable_afterupdate AFTER UPDATE ON programTable
FOR EACH ROW
BEGIN
IF NEW.Enrolled = 1
THEN
INSERT INTO enrollment (ID, Action, Date_Updated, User, Program, Reason, Action_Date)
VALUES (NEW.Member_ID, 'Enrolled', NOW(), 'programUser', 'programName', 'Enrolled in program', NOW());
ELSEIF NEW.Enrolled = 0
THEN
INSERT INTO enrollment (ID, Action, Date_Updated, User, Program, Reason, Action_Date)
VALUES (NEW.ID, 'Disenrolled', NOW(), 'programUser', 'programName', 'Disenrolled from program', NOW());
END IF;
END;$$
DROP TRIGGER IF EXISTS programTable_afterdelete;$$
CREATE TRIGGER programTable_afterdelete AFTER DELETE ON programTable
FOR EACH ROW
BEGIN
IF OLD.Enrolled = 1
THEN
INSERT INTO enrollment (ID, Action, Date_Updated, User, Program, Reason, Action_Date)
VALUES (OLD.ID, 'Disenrolled', NOW(), 'programUser', 'programName', 'Removed from program', NOW());
END IF;
END;$$
A stripped down version of the enrollment and program tables can be created with the following:
delimiter $$
CREATE TABLE `programTable1` (
`ID` varchar(15) NOT NULL,
`Enrolled` tinyint(3) unsigned NOT NULL,
`Referral_Date` datetime NOT NULL,
`Referral_Source` varchar(255)
);$$
CREATE TABLE `programTable2` (
`ID` varchar(15) NOT NULL,
`Enrolled` tinyint(3) unsigned NOT NULL,
`Referral_Date` datetime NOT NULL,
`Referral_Source` varchar(255)
);$$
CREATE TABLE `enrollment` (
`ID` varchar(15) NOT NULL,
`Action` varchar(12) NOT NULL,
`Date_Updated` timestamp NOT NULL,
`User` varchar(12) default NULL,
`Program` varchar(25) NOT NULL,
`Notes` varchar(100) default NULL,
`Reason` varchar(45) default NULL,
`Action_Date` datetime NOT NULL
);$$
The hurdle I'm running into is the enrollment table needs to update the program tables' enrollment when it's modified or a row is added to it. Meaning if someone is enrolled on the program table, they need to have an entry on the enrollment table for that action; if someone is enrolled via the enrollment table, they need to be updated as enrolled or disenrolled on the program table that row applies to.
The main problem is that there are two different sources people are enrolling in the programs from.
Like I said, convoluted. I know the architecture of this application isn't the best, but it's not something that can be changed.
Any ideas would be welcome! Please let me know if there anyone has questions or if any clarification is needed. I've been working on this for awhile now, so I know I'm probably leaving some stuff out of the equation due to being so familiar with it.
There's no problem creating triggers that update each others' tables -- as long as the triggers don't continue to insert back and forth in an infinite loop.
You need to make sure that the INSERT performed by a trigger inserts a row to the other table that will not result in a reciprocal action.
I wrote an example in another recent question: Mirror tables: triggers, deadlock and implicit commits
Related
I'll start by explaining how the db should work:
In this example I have a table that stores work orders, this table has 5 total fields: ID, Number, Worker, temperature, humidity.
And another table that stores sensor data with 4 fields: ID, Device ID, Temp, Hum.
We built an APP that allows workers to submit work order data, My problem comes here The app generates the ID, Number and Worker field, and we want to add the sensor data (Temperature and humidity) to that table every time an insert is made. I tried doing this with a trigger but i get "Error Code: 1442. Can't update table 'ordenes' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
I tried multiple ways of doing it but I either get no change on the table or that error message.
Im looking for a way to do this:
trigger after insert
> insert into "new created line"(temperature, humidity) values
(select temp,humidity from sensors order by id desc limit 1)
Thanks in advance
EDIT:
Create Scheme and table:
SET #OLD_UNIQUE_CHECKS=##UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
CREATE SCHEMA IF NOT EXISTS `Cegasa` DEFAULT CHARACTER SET utf8 ;
USE `Cegasa` ;
DROP TABLE IF EXISTS `Cegasa`.`ORDENES` ;
CREATE TABLE IF NOT EXISTS `Cegasa`.`ORDENES` (
`idORDENES` INT NOT NULL AUTO_INCREMENT,
`NumOrden` VARCHAR(45) NULL,
`Empleado` VARCHAR(45) NULL,
`Temperatura` VARCHAR(45) NULL,
`Humedad` VARCHAR(45) NULL,
PRIMARY KEY (`idORDENES`))
ENGINE = InnoDB;
DROP TABLE IF EXISTS `Cegasa`.`sensores` ;
CREATE TABLE IF NOT EXISTS `Cegasa`.`sensores` (
`id` INT NOT NULL AUTO_INCREMENT,
`EUI` VARCHAR(45) NULL,
`Temp` VARCHAR(45) NULL,
`Hum` VARCHAR(45) NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
USE `Cegasa`;
DELIMITER $$
USE `Cegasa`$$
DROP TRIGGER IF EXISTS `Cegasa`.`ORDENES_AFTER_INSERT` $$
USE `Cegasa`$$
CREATE DEFINER = CURRENT_USER TRIGGER `Cegasa`.`ORDENES_AFTER_INSERT` AFTER INSERT ON `ORDENES` FOR EACH ROW
BEGIN
insert into `cegasa`.`Ordenes` (
`temp`,
`hum`
) SELECT temp,hum FROM sensores ORDER BY ID DESC LIMIT 1;
END$$
DELIMITER ;
SET SQL_MODE=#OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=#OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=#OLD_UNIQUE_CHECKS;
Insert for example sensor data:
INSERT INTO `cegasa`.`sensores`
(`id`,
`EUI`,
`Temp`,
`Hum`)
VALUES
(default,
"th312322aa",
"10",
"33"),(
default,
"daedaf12392",
"30",
"70"
);
Similar insert to the one the app makes
INSERT INTO `cegasa`.`ordenes`
(`idORDENES`,
`NumOrden`,
`Empleado`)
VALUES
(default,
1,
"123a");
Desired outcome after this insert
CREATE TABLE IF NOT EXISTS `sensores` (
`id` INT NOT NULL AUTO_INCREMENT,
`EUI` VARCHAR(45) NULL,
`Temp` VARCHAR(45) NULL,
`Hum` VARCHAR(45) NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
INSERT INTO `sensores` (`id`,`EUI`,`Temp`,`Hum`) VALUES
(default, "th312322aa", "10", "33"),
(default, "daedaf12392", "30", "70");
SELECT * FROM sensores;
id
EUI
Temp
Hum
1
th312322aa
10
33
2
daedaf12392
30
70
CREATE TABLE IF NOT EXISTS `ordenes` (
`idORDENES` INT NOT NULL AUTO_INCREMENT,
`NumOrden` VARCHAR(45) NULL,
`Empleado` VARCHAR(45) NULL,
`Temperatura` VARCHAR(45) NULL,
`Humedad` VARCHAR(45) NULL,
PRIMARY KEY (`idORDENES`))
ENGINE = InnoDB;
CREATE TRIGGER get_last_Temp_Hum
BEFORE INSERT ON ordenes
FOR EACH ROW
BEGIN
DECLARE new_temp VARCHAR(45); -- declare intermediate variables
DECLARE new_hum VARCHAR(45);
SELECT Temp, Hum INTO new_temp, new_hum -- select vast values into it
FROM sensores
ORDER BY id DESC LIMIT 1;
SET NEW.Temperatura = new_temp, -- set columns values in newly inserted row
NEW.Humedad = new_hum; -- to the values stored in the variables
END
INSERT INTO `ordenes` (`idORDENES`,`NumOrden`,`Empleado`) VALUES
(default, 1, "123a");
SELECT * FROM ordenes;
idORDENES
NumOrden
Empleado
Temperatura
Humedad
1
1
123a
30
70
fiddle
Trigger fires on INSERT statement but before the values are inserted into the table (i.e. the insertion is an intention yet). The query in the trigger retrieves needed values into the variables, then SET statement copies these values into the columns in the row which will be inserted. And after the trigger finishes the row contains needed values in the columns, and these values are saved into the table.
I have a table that tracks the number of hours an employee took on a job.
CREATE TABLE HOURLYWORKLOG (
EMPLOYEEREF INT(5) NOT NULL,
ORDERREF INT(5) NOT NULL,
HOURSWORKED VARCHAR(10),
TOTALPAY NUMERIC(10),
NOTES VARCHAR(10),
CONSTRAINT HOURLYWORKLOG_EMPLOYEES_FOREIGN_KEY FOREIGN KEY (EMPLOYEEREF) REFERENCES EMPLOYEES (EMPLOYEEREF),
CONSTRAINT HOURLYWORKLOG_ORDERS_FOREIGN_KEY FOREIGN KEY (ORDERREF) REFERENCES WORKORDER (ORDERREF));
I am looking to create a trigger that stores this data in a separate table if the hoursworked column is updated. Reading around, I can't see anything that explains what I need to do, at least I can't understand the steps involved after reading. As such my current solution is through creating a mirrored table (with a different name)
CREATE TABLE MODIFIEDHOURLYWORKLOG (
EMPLOYEEREF INT(5) NOT NULL,
ORDERREF INT(5) NOT NULL,
HOURSWORKED VARCHAR(10),
TOTALPAY NUMERIC(10),
NOTES VARCHAR(10));
And then creating a trigger as such
CREATE TRIGGER MODIFIEDHWL ON HOURLYWORKLOG
AFTER INSERT
AS
BEGIN
INSERT INTO MODIFIEDHOURLYWORKLOG
(EMPLOYEEREF, ORDERREF, HOURSWORKED, TOTALPAY, NOTES)
SELECT I.EMPLOYEEREF, I.ORDERREF, I.HOURSWORKED, I.TOTALPAY, I.NOTES
FROM HOURLYWORKLOG T
INNER JOIN INSERTED I ON T.EMPLOYEEREF-I.EMPLOYEEREF
END;
This obviously isn't working and is throwing up errors that my syntax is incorrect. I'm not sure how to write a trigger to transfer the data from the original table into the secondary one basically...
Thank you for any help.
You want to use trigger syntax for MySQL, not SQL Server. Something like this:
DELIMITER $$
CREATE TRIGGER MODIFIEDHWL ON HOURLYWORKLOG AFTER INSERT
FOR EACH ROW AS
BEGIN
INSERT INTO MODIFIEDHOURLYWORKLOG(EMPLOYEEREF, ORDERREF, HOURSWORKED, TOTALPAY, NOTES)
SELECT NEW.EMPLOYEEREF, NEW.ORDERREF, NEW.HOURSWORKED, NEW.TOTALPAY, NEW.NOTES
FROM DUAL
WHERE NOT NEW.HOURSWORKED <=> OLD.HOURSWORKED;
END;$$
DELIMITER ;
CREATE TRIGGER MODIFIEDHWL ON HOURLYWORKLOG
after update
for each row
AS
BEGIN
INSERT INTO MODIFIEDHOURLYWORKLOG
(EMPLOYEEREF, ORDERREF, HOURSWORKED, TOTALPAY, NOTES)
SELECT NEW.EMPLOYEEREF, NEW.ORDERREF, NEW.HOURSWORKED, NEW.TOTALPAY,
NEW.NOTES
from dual where NEW.HOURSWORKED != OLD.HOURSWORKED
END;
/
I need help creating a BEFORE INSERT TRIGGER on mySQL Bench. im new to this please.
CREATE TABLE `quincyninying`.`toytracking` (
`Toyid` INT NOT NULL,
`ToyName` VARCHAR(50) NULL,
`Toycost` DECIMAL NULL,
`ToyAction` VARCHAR(50) NULL,
`ActionDate` DATETIME NULL,
PRIMARY KEY (`Toyid`));
CREATE TABLE `quincyninying`.`toy` (
`Toyid` INT NOT NULL,
`ToyName` VARCHAR(50) NULL,
`Toycost` DECIMAL NULL,
PRIMARY KEY (`Toyid`));
Create a BEFORE INSERT trigger on the toy table that adds a record to the toytracking table with the information from the toy table record that is being INSERTED, hard coded ToyAction that will be ‘INSERT’ and the current Date and time the record is inserted.
ERROR 1054: Unknown column 'inserted' in 'NEW' SQL Statement:
CREATE DEFINER = CURRENT_USER TRIGGER `quincyninying`.`toy_BEFORE_INSERT` BEFORE INSERT ON `toy`
FOR EACH ROW
BEGIN
IF new.inserted THEN
SET #toyaction = 'DELETE';
ELSE
SET #toyaction = 'NEW';
END IF;
INSERT INTO `quincyninying`.`toytracking` (toyId, ToyName, ToyCost, Toyaction, ActionDate)
VALUES (new.toyid, new.Toyname, new.Toycost,#Toyaction, now());
END
It throws me an error saying " ERROR 1054: Unknown column 'inserted' in 'NEW' "
Get rid of the IF new.inserted test, since there's no column with that name, and just hard-code INSERT as the value for the ToyAction column as you stated in the requirements.
CREATE DEFINER = CURRENT_USER TRIGGER `quincyninying`.`toy_BEFORE_INSERT` BEFORE INSERT ON `toy`
FOR EACH ROW
BEGIN
INSERT INTO `quincyninying`.`toytracking` (toyId, ToyName, ToyCost, Toyaction, ActionDate)
VALUES (new.toyid, new.Toyname, new.Toycost, 'INSERT', now());
END
Try this:
DELIMITER $$
CREATE
TRIGGER toy_before_insert BEFORE INSERT
ON toy
FOR EACH ROW BEGIN
IF NEW.Toyaction THEN
SET #Toyaction = 'DELETE';
ELSE
SET #Toyaction = 'NEW';
END IF;
INSERT INTO toytracking (toyId, ToyName, ToyCost, Toyaction, ActionDate) VALUES (NEW.Toyid, NEW.ToyName, NEW.ToyCost, #Toyaction, NOW());
END$$
DELIMITER ;
This is my schema:
I am trying to have an insert into "desktops" or "laptops" insert an id generated automatically from "computers". That works.
My issue is when I insert into either table, I can not select last_insert_id();
Is there something I am doing wrong? I am trying to pass the id all the way forward to my application, for further processing. Selecting MAX(id) is not a valid solution. My SQL connection makes one insert statement, and the trigger should not break that functionality...
Use test;
CREATE TABLE `laptops` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=innodb DEFAULT CHARSET=utf8;
CREATE TABLE `desktops` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=innodb DEFAULT CHARSET=utf8;
CREATE TABLE `computers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=innodb DEFAULT CHARSET=utf8;
CREATE TRIGGER `laptops_BINS` BEFORE INSERT ON `laptops` FOR EACH ROW
BEGIN
IF (EXISTS(SELECT id FROM laptops WHERE name = NEW.name)) THEN
SET NEW.id = NULL;
ELSE
INSERT INTO computers (type) VALUES ('laptop');
SET NEW.id = LAST_INSERT_ID();
SET NEW.id = LAST_INSERT_ID(NEW.id);
END IF;
END
CREATE TRIGGER `desktop_BINS` BEFORE INSERT ON `desktops` FOR EACH ROW
BEGIN
IF (EXISTS(SELECT id FROM desktops WHERE name = NEW.name)) THEN
SET NEW.id = NULL;
ELSE
INSERT INTO computers (type) VALUES ('desktop');
SET NEW.id = LAST_INSERT_ID();
SET NEW.id = LAST_INSERT_ID(NEW.id);
END IF;
END
INSERT INTO laptops (name) VALUES ('laptop1');
INSERT INTO laptops (desktop) VALUES ('desktop1');
INSERT INTO laptops (name) VALUES ('laptop2');
INSERT INTO laptops (desktop) VALUES ('desktop2');
SELECT last_insert_id();
Expecting 4, actually its 0.
Any thoughts as to how I can fix the trigger? Maybe someone can help me format the AFTER_INSERT statement to fix last_insert_id?
I tried setting the values to auto-increment, and unique in the laptops and desktops table, neither will fix the issue.
Rather than trying to deal with the 'confusion' of 'last_insert_id'. I decided to change the table structure to be a more 'common' format.
That is change the 'laptops' and 'desktops' tables to have the 'auto_increment' keys. This changes the 'computers' table to have a primary key of 'computer_id' from 'laptops' or 'desktops' and a 'computer_type'.
Here are the table structures and triggers.
It has been tested on mysql 5.5.16 on windows xp.
CREATE TABLE `laptops` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE `desktops` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `computers` (
`computer_id` int(11) NOT NULL,
`computer_type` varchar(45) NOT NULL,
PRIMARY KEY (`computer_id`,`computer_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DELIMITER $$
USE `testmysql`$$
DROP TRIGGER /*!50032 IF EXISTS */ `laptop_bins`$$
CREATE
/*!50017 DEFINER = 'test'#'localhost' */
TRIGGER `laptop_bins` AFTER INSERT ON `laptops`
FOR EACH ROW BEGIN
INSERT INTO computers (computer_id, computer_type ) VALUES (new.id, 'laptop');
END;
$$
DELIMITER ;
DELIMITER $$
USE `testmysql`$$
DROP TRIGGER /*!50032 IF EXISTS */ `desktop_bins`$$
CREATE
/*!50017 DEFINER = 'test'#'localhost' */
TRIGGER `desktop_bins` AFTER INSERT ON `desktops`
FOR EACH ROW BEGIN
INSERT INTO computers (computer_id, computer_type ) VALUES (new.id, 'desktop');
END;
$$
DELIMITER ;
Sample Queries and Output:
INSERT INTO laptops (NAME) VALUES ('laptop1');
INSERT INTO desktops (NAME) VALUES ('desktop1');
INSERT INTO laptops (NAME) VALUES ('laptop2');
INSERT INTO desktops (NAME) VALUES ('desktop2');
Laptops:
id name
------ ---------
1 laptop1
2 laptop2
Desktops:
id name
------ ----------
1 desktop1
2 desktop2
Computers:
computer_id computer_type
----------- ---------------
1 desktop
1 laptop
2 desktop
2 laptop
This more a possible approach to the requirement than an answer.
I can create the code if required. It is not a lot of code on top of what is here.
The problem is to maintain tables in an other database, in sync, without doing lots of repeat work.
My suggestion:
In the 'computers' database - have a 'computers_new' table that is inserted to by the 'after insert' trigger and holds the relevant key information. Including a 'unprocessed' column.
I would then run a script at regular intervals or was triggered when the 'computers_new' table changed. It would:
1) transfer the 'unprocessed' details to the 'laptops', 'desktops' tables in the other database.
2) mark the transferred records as processed.
Advantages:
Lots of small chunks of work.
By using transactions it is reliable.
Drawbacks.
Ensuring tables are in sync.
I have created two tables which i want to insert similar data in.
CREATE TABLE one(
one_id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (one_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE two(
two_id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (two_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
every time in run insert.
To do that,i am using transactions
START TRANSACTION;
SET #name = uuid();
INSERT INTO one(one_id,name) VALUES (Null,#name);
INSERT INTO two(two_id, name) VALUES (Null, #name);
COMMIT;
This does not produce new values on new inserts.It however inserts the same data in the field name as i wanted.
How can i make this work?.
I don't see a need to move to transactions in order to do that, just add an before insert trigger to the table .
Something like :
CREATE TRIGGER `ONE_TABLE_TRIGG` BEFORE INSERT ON `one`
FOR EACH
ROW BEGIN
SET NEW.name= UUID( );
END ;
You can check if it's null before doing that. do this on both tables and you're good or add insert to the other table on 1 trigger.
I solved it without much complexities by having several having several transaction statements in the same file
START TRANSACTION;
SET #name = uuid();
INSERT INTO one(one_id,name) VALUES (Null,#name);
INSERT INTO two(two_id, name) VALUES (Null, #name);
COMMIT;
START TRANSACTION;
SET #name = uuid();
INSERT INTO one(one_id,name) VALUES (Null,#name);
INSERT INTO two(two_id, name) VALUES (Null, #name);
COMMIT;
/*
Etc
*/