We are in the process of migrating between 2 systems and need to have 2 fields for one of our database tables that always stay in sync. Here is the table structure:
CREATE TABLE `example` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`object_id` int(11) NOT NULL DEFAULT '0',
`value` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `object_id` (`object_id`)
);
Every time one of the systems inserts a new row we need to have object_id set to id. We can't use 'before insert' since the id column is an auto_increment column so it's value is NULL before insert and due to the limitations of the MySQL 'after insert' on triggers I can't do the following:
CREATE TRIGGER insert_example
AFTER INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
I can't update the code for either system so I need a way to accomplish this on the database side. Both systems are going to be inserting new rows. How can I accomplish this?
Using a trigger which fires before the insert should do the job
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
EDIT:
As the OP pointed out NEW.id won't work with auto-increment; one could use the following trigger (use at own risk):
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = (
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'example'
);
But I'd rather re-think this somewhat strange requirement - why do you need the pk value twice in the table?
Is there any reason you cant use a BEFORE INSERT trigger?
I've always seen AFTER INSERT triggers as a method to manipulate other tables rather than the table for which the trigger was executed on.
Rule of thumb, manipulate table the trigger is running on = BEFORE INSERT, manipulate other tables AFTER INSERT :)
I think your trigger will never create in the first place because you can't refer NEW.column_name in an AFTER INSERT trigger.
Try doing this in a BEFORE INSERT trigger (PLEASE IGNORE THIS FIX AS IT WILL NOT WORK):
CREATE TRIGGER `insert_example` BEFORE INSERT ON `t`
FOR EACH ROW
SET NEW.`object_id` = NEW.`id`;
Please change the table and column names as per your schema.
Hope this helps.
Related
i have a DB for managing an airport. I want to try to calculate the time difference between columns ArrivingDate and DepartingDate (both are DATETIME type) into a third column called Flight_time (TIME type) after any INSERT in the table FLIGHT_SCHEDULES.
I tried to create a trigger for doing that, but with no success. I have already read some stuff on the internet about my error but couldn't find something which solve my dilemma.
This is the table:
CREATE TABLE FLIGHT_SCHEDULES(
id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
Flight INT(10) UNSIGNED NOT NULL,
Airplane INT(10) UNSIGNED NOT NULL,
DepartingDate DATETIME NOT NULL,
ArrivingDate DATETIME NOT NULL,
Flight_time TIME DEFAULT '00:00:00',
CONSTRAINT flight_unique UNIQUE (Flight),
CONSTRAINT fk_scheduled_flight_id FOREIGN KEY (Flight) REFERENCES FLIGHTS(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_scheduled_airplane_id FOREIGN KEY (Airplane) REFERENCES AIRPLANES(id) ON UPDATE CASCADE ON DELETE CASCADE
);
This is the trigger I created:
delimiter //
CREATE TRIGGER calculate_flightTime2 BEFORE INSERT ON FLIGHT_SCHEDULES FOR EACH ROW
BEGIN
UPDATE FLIGHT_SCHEDULES
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END //
I can create the trigger with no problems, but the I try to insert a new row in the table I receive the following error message:
Error Code: 1442. Can't update table 'flight_schedules' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. 0.000 sec
Note that I tried to create an AFTER trigger too, but the result was the same.
It is a restriction of mysql so you don t end up in an endless loop.
Try something like this.
CREATE TRIGGER calculate_flightTime2
BEFORE INSERT
ON FLIGHT_SCHEDULES
FOR EACH ROW
BEGIN
IF NEW.Flight_time = '00:00:00' THEN
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END IF;
END
Usually it's not a good idea to materialize values that can be calculated from others. This bears the risk of inconsistencies. What happens if you change the arriving time for example?
So it's probably best if you drop the column Flight_time.
ALTER TABLE FLIGHT_SCHEDULES
DROP Flight_time;
For convenience you can then create a view, that includes the calculated value.
CREATE VIEW FLIGHT_SCHEDULES_WITH_FLIGHT_TIME
AS
SELECT *,
timediff(ArrivingDate, DepartingDate) Flight_time
FROM FLIGHT_SCHEDULES;
In MySQL 8+ you could alternatively use a generated column. That's a safe way as the DBMS guarantees consistency like that.
ALTER TABLE FLIGHT_SCHEDULES
ADD Flight_time time AS timediff(ArrivingDate, DepartingDate);
But if you insist on using a trigger, you don't need an UPDATE to set a value in the new pseudo record. Just an assignment is enough.
...
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
...
That's a strange issue.
There are source MySQL DB (MASTER) and its replic (SLAVE). It's a (as you understand its a MASTER-SLAVE) statement-bases replication, because I need triggers to run on the SLAVE side.
The original triggers were replaced by new ones.
Every table has 3 trigers: on INSERT, on UPDATE and on DELETE.
Trigers were generated by a single pattern and differ only by params.
Every trigger does a single INSERT query to a table (CHANGES) on the SLAVE.
This table is not replicated and exists only on the SLAVE.
There is an autoincremented column (ID - bigint) in this table.
None of the triggers set or modify values of the column ID. The DB sets a default values for it.
It's about 20 inserts executed on CHANGES per minute.
I see errors with duplicated values.
How it's possible?
Let's again:
An INSERT / UPDATE / DELETE query is executed on MASTER.
This change is replicated to SLAVE.
A trigger is called and inserts a row to the CHANGES.
Duplicated values error is generated.
And as I said before, none of the triggers set or modify value of the autoincremented field (ID). And only triggers work with table CHANGES.
I understand that two or more triggers can be called together, and try to do INSERT together, but I think DB should easy solve this. Or that's a very bad DB.
UPD:
CREATE TABLE `CHANGES` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`field1` ENUM(...) NOT NULL,
`field2` BOOL NOT NULL DEFAULT FALSE,
`field3` VARCHAR(64) NOT NULL,
`field4` VARCHAR(255) NOT NULL,
`field5` TEXT,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TRIGGER `tr_TABLE_insert` AFTER INSERT ON `TABLE`
FOR EACH ROW BEGIN
INSERT INTO `CHANGES` (`field1`, `field3`, `field4`, `field5`)
VALUES ("value1", "value3", "value4", "value5");
END
UPD 2:
I found a temporary and more dirty method - added BEFORE INSERT trigger for CHANGES to set ID manually.
It works. But I still can't figure out why the native AUTO_INCREMENT mechanism is generating duplicated ids.
I have the same question as described here and also I think the answer https://stackoverflow.com/a/22343265/297487 is a good solution but I have another question about this answer.
Is the following trigger (copied from answer) thread-safe? I mean if two concurrent record inserted to table, does "priority" column (as describe in question) have consistent value (The same value as id)?
delimiter //
drop trigger if exists bi_table_name //
create trigger bi_table_name before insert on table_name
for each row begin
set #auto_id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='table_name'
AND TABLE_SCHEMA=DATABASE() );
set new.priority= #auto_id;
end;
//
delimiter ;
How do we interpret "for each row" clause?
Suppose that MySQL wants insert two concurrent rows. How does this trigger works?
One interpretation is as follows:
MySQL locks table and before insert one of the rows (one of concurrent record) MySQL trigger starts and gets current AUTO_INCREMENT value and sets it to "priority" column and then inserts a record. After that MySQL starts inserting another record and then the same situation applies for new record.
Another interpretation might be as follows:
When two concurrent records are inserted to MySQL, MySQL locks the table and then before inserting two concurrent records a trigger starts and "for each row" clause iterate between two record and set "priority" column value to the same value and then insert two concurrent record in database. In this situation the trigger does not work as expected.
Which one of the above interpretation is correct?
Update :
I have the following table :
CREATE TABLE `t_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_persian_ci NOT NULL,
`p_name` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `p_name_2` (`p_name`)
) ENGINE=InnoDB AUTO_INCREMENT=206284 DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
I want to insert value of p_name the same as id when row inserted.
The trigger that sets value of p_name is as follows(copied from your code)
delimiter $$
drop trigger if exists file_p_name $$
create trigger file_p_name before insert on t_file
for each row begin
set #id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='t_file'
AND TABLE_SCHEMA=DATABASE() );
set new.p_name= #id;
end;
$$
delimiter ;
In our application, i surround the code that inserts int_file table with try catch,in almost always everything is OK,but sometimes(in concurrent insert to t_file table), i see the following exception in our application's log:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'387456\' for key \'p_name_2\''
It seems the trigger does not work as expected or maybe i am wrong!!!
The answer referred in your question was posted by me some time back.
While in "For Each Row", The NEW is the corresponding new row, being inserted, in context. And the set new.xxx is only applicable for that row and but not 'for all rows' in batch. So there won't be any collision and the question of failure should not arise.
I have also answered a similar question
How does “for each row” work in triggers in mysql?. Please go through the examples given in the answer.
Refer to Documentation:
MySQL: Triggers
I need to create MySQL trigger that would log user ID on delete table row statement which must fit in one query, since I'm using PHP PDO. This is what I've come up so far:
I need the way to pass user ID in the delete query even though it is irrelevant to delete action to be performed:
Normally the query would look like this:
DELETE FROM mytable WHERE mytable.RowID = :rowID
If I could use multiple queries in my statement, I would do it like this:
SET #userID := :userID;
DELETE FROM mytable WHERE mytable.RowID = :rowID;
This way the variable #userID would be set before trigger event fires and it can use it. However since I need to squeeze my delete statement in one query, so I came up with this:
DELETE FROM mytable
WHERE CASE
WHEN #userID := :userID
THEN mytable.RowID = :rowID
ELSE mytable.RowID IS NULL
END
Just a note: RowID will never be null since it's the primary key. Now I have to create a delete trigger to log the user ID to the audit table, however I suppose that in this case trigger will be fired before the delete query itself which means that #userID variable will not be created? This was my idea of passing it as a value to the trigger.
I feel like I'm close to the solution, but this issue is a blocker. How to pass user ID value to the trigger without having multiple queries in the statement? Any thoughts, suggestions?
You can use NEW / OLD mysql trigger extensions. Reference: http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
Here is a sample code :
drop table `project`;
drop table `projectDEL`;
CREATE TABLE `project` (
`proj_id` int(11) NOT NULL AUTO_INCREMENT,
`proj_name` varchar(30) NOT NULL,
`Proj_Type` varchar(30) NOT NULL,
PRIMARY KEY (`proj_id`)
);
CREATE TABLE `projectDEL` (
`proj_id` int(11) NOT NULL AUTO_INCREMENT,
`proj_name` varchar(30) NOT NULL,
`Proj_Type` varchar(30) NOT NULL,
PRIMARY KEY (`proj_id`)
);
INSERT INTO `project` (`proj_id`, `proj_name`, `Proj_Type`) VALUES
(1, 'admin1', 'admin1'),
(2, 'admin2', 'admin2');
delimiter $
CREATE TRIGGER `uProjectDelete` BEFORE DELETE ON project
FOR EACH ROW BEGIN
INSERT INTO projectDEL SELECT * FROM project WHERE proj_id = OLD.proj_id;
END;$
delimiter ;
DELETE FROM project WHERE proj_id = 1;
SELECT * FROM project;
SELECT * FROM projectDEL;
I am a user of a some host company which serves my MySql database. Due to their replication problem, the autoincrement values increses by 10, which seems to be a common problem.
My question is how can I simulate (safely) autoincrement feature so that the column have an consecutive ID?
My idea was to implement some sequence mechanism to solve my problem, but I do not know if it is a best option. I had found such a code snipset over the web:
DELIMITER ;;
DROP TABLE IF EXISTS `sequence`;;
CREATE TABLE `sequence` (
`name` CHAR(16) NOT NULL,
`value` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;;
DROP FUNCTION IF EXISTS `nextval`;
CREATE FUNCTION `nextval`(thename CHAR(16) CHARSET latin1)
RETURNS BIGINT UNSIGNED
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
INSERT INTO `sequence`
SET `name`=thename,
`value`=(#val:=##auto_increment_offset)+##auto_increment_increment
ON DUPLICATE KEY
UPDATE `value`=(#val:=`value`)+##auto_increment_increment;
RETURN #val;
END ;;
DELIMITER ;
which seems quite all correct. My second question is if this solution is concurrent-safe? Of course INSERT statement is, but what about ON DUPLICATE KEY update?
Thanks!
Why do you need to have it in the first place?
Even with auto_increment_increment == 1 you are not guaranteed, that the autoincrement field in the table will have consecutive values (what if the rows are deleted, hmm?).
With autoincrement you are simply guaranteed by the db engine, that the field will be unique, nothing else, really.
EDIT: I want to reiterate: In my opinion, it is not a good idea to assume things like concurrent values of an autoincrement column, because it is going to bite you later.
EDIT2: Anyway, this can be "solved" by an "on insert" trigger
create trigger "sequence_b_ins" before insert on `sequence`
for each row
begin
NEW.id = select max(id)+1 from `sequence`;
end
Or something along these lines (sorry, not tested)
Another option would be to use a stored proc to do the insert and have it either select max id from your table or keep another table with the current id being used and update as id's are used.