I'm building a data versioning system, and I need to increment a version number each time a new row is added to the version table, but it increments once and then stops:
DELIMITER |
CREATE TRIGGER trigger2 AFTER UPDATE ON something
FOR EACH ROW
BEGIN
IF NEW.updated_at <> OLD.updated_at THEN
INSERT INTO versions_something (
`id`,
`some_id`,
`version`,
`title`,
`description`,
`created_at`,
`updated_at`
) VALUES (
null,
NEW.id,
1,
NEW.title,
NEW.description,
NOW(),
NOW()
);
END IF;
UPDATE
versions_something
SET
version = (SELECT MAX(version)) + 1
WHERE versions_something.id = LAST_INSERT_ID();
END;
|
DELIMITER ;
I've tried putting the UPDATE into a separate trigger (AFTER INSERT ON versions_something ...), but MySQL complains that it's clashing with the trigger before it.
I've tried the UPDATE on its own, using the last ID in the table and it works each time, so I have no idea what's happening.
Related
Here is the table I created.
USE my_guitar_shop;
DROP TABLE IF EXISTS Products_Audit;
CREATE TABLE Products_Audit (
audit_id INT PRIMARY KEY,
category_id INT REFERENCES categories(category_id),
product_code VARCHAR ( 10 ) NOT NULL UNIQUE ,
product_name VARCHAR ( 255 ) NOT NULL,
list_price INT NOT NULL,
discount_percent INT NOT NULL DEFAULT 0.00 ,
date_updated DATETIME NULL);
Create a trigger named products_after_update. This trigger should insert the old data about the product into the Products_Audit table after the row is updated. Then, test this trigger with an appropriate UPDATE statement.
Here is the trigger I created but the data is not showing up in the Products_Audit table it is showing all null.
USE my_guitar_shop;
DROP TRIGGER IF EXISTS products_after_update;
DELIMITER $$
CREATE TRIGGER products_after_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
INSERT INTO products_audit (audit_id, product_id, category_id, product_code,
product_name, list_price, discount_percent, date_updated)
SELECT audit_id, products.product_id, products.category_id, products.product_code,
products.product_name,products.list_price, products.discount_percent, date_updated
FROM products JOIN products_audit
ON products_audit.audit_id = (SELECT audit_id FROM inserted);
END $$
DELIMITER ;
EDIT with the INSERT INTO
USE my_guitar_shop;
DROP TRIGGER IF EXISTS products_after_update;
DELIMITER $$
CREATE TRIGGER products_after_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
INSERT INTO products_audit (audit_id, product_id, category_id,product_code,
product_name, list_price, discount_percent, date_updated)
VALUES (OLD.audit_id, OLD.product_id, OLD.category_id, OLD.product_code,
OLD.product_name, OLD.list_price, OLD.discount_percent, OLD.date_updated)
DELIMITER ;
You are overcomplicating the insert. As mysql documentation on triggers says:
In an UPDATE trigger, you can use OLD.col_name to refer to the columns
of a row before it is updated and NEW.col_name to refer to the columns
of the row after it is updated.
Therfore, use the OLD.column_name format in the insert. Also, I would set the audit_id field to auto increment and leave it out of the insert:
INSERT INTO products_audit (product_id, category_id, product_code,
product_name, list_price, discount_percent, date_updated)
VALUES (OLD.product_id, OLD.category_id, OLD.product_code,
OLD.product_name, OLD.list_price, OLD.discount_percent, OLD.date_updated)
Here's an example of how I do it:
CREATE OR REPLACE EDITIONABLE TRIGGER E_TABLE_TRG
before insert or update or delete on e_table
for each row
declare
l_seq number;
begin
-- Get a unique sequence value to use as the primary key
select s_seq.nextval
into l_seq
from dual;
if inserting then
:new.date_opened := sysdate;
:new.last_txn_date := null;
:new.status := 'A';
end if;
if inserting then
insert into e_table_history
(
t_seq,
user_id,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:new.user_id,
:new.date_opened,
:new.last_txn_date,
:new.status,
'I',
sysdate
);
elsif updating then
insert into e_table_history
(
t_seq,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:new.date_opened,
:new.last_txn_date,
:new.status,
'U',
sysdate
);
else
insert into e_table_history
(
t_seq,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:old.date_opened,
:old.last_txn_date,
:old.status,
'D',
sysdate
);
end if;
end;
/
ALTER TRIGGER E_TABLE_TRG ENABLE;
/
I have 2 triggers on my table as defined...
DELIMITER $$
CRATE TRIGGER newRecordToHistory AFTER INSERT ON myTable FOR EACH ROW
BEGIN
IF (new.recordType = 1) THEN
INSERT INTO myTableHistory
(
myTableId,
someInformation,
reason,
mytimestamp,
status
)
VALUES
(
new.myTableId,
new.someInformation,
new.reason,
now(),
'NEW'
);
END IF;
END$$
DELIMITER ;
and
DELIMITER $$
CRATE TRIGGER updateRecordToHistory AFTER UPDATE ON myTable FOR EACH ROW
BEGIN
IF (new.recordType = 1) THEN
INSERT INTO myTableHistory
(
myTableId,
someinformation,
reason,
mytimestamp,
status
)
VALUES
(
new.myTableId,
new.someInformation,
new.reason,
now(),
'UPDATED'
);
END IF;
END$$
DELIMITER ;
When I insert a new record into myTable, I get 2 records in myHistoryTable...
ID someInformation reason mytimestamp status
1 'This is new' 'Needed new record' 09/12/14 08:00:00 'NEW'
1 'This is new' 'Needed new record' 09/12/14 08:00:00 'UPDATED'
I also get a record in the table when I delete. But my code handles inserting into the history table when I delete (so the user can specify the reason for deleting it) from myTable.
I would only expect only one record in myHistoryTable on insert and no extra records in the myHistoryTtable when I delete (other than the ones I put there manually). Why does this happen? And how can I avoid this?
Thanks!
I am trying to create trigger, that capture changes in database after update.
Table my_table I am watching:
Table my_table_log where I am writing changes to log them
And here is trigger so far:
CREATE TRIGGER `log_update`
AFTER UPDATE ON `my_table`
FOR EACH ROW
BEGIN
INSERT INTO
`my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
VALUES
(
NEW.id,
'u',
'name',
OLD.name,
NEW.name,
user(),
NOW()
);
END
Question: How to log each change of column ?
Problem: I am curently watching only if column name changed in my_table. And I have another trigger for column age. How to set trigger for each row and each column that was changed?
Thank you for your suggestions/code/inspirations
You might use ifs for every column you'd like to watch in your trigger:
create trigger `log_update`
after update on `my_table`
for each row
begin
if (old.name <> new.name) then
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
new.id,
'u',
'name',
old.name,
new.name,
user(),
now()
);
end if;
if (old.age <> new.age) then
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
new.id,
'u',
'age',
old.age,
old.age,
user(),
now()
);
end if;
end
But better make the insert a stored procedure to avoid redudancy:
create procedure `log_insert`
(
id int(11),
`action` char,
column_name varchar(255),
value_before varchar(255),
value_after varchar(255)
)
begin
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
id,
`action`,
column_name,
value_before,
value_after,
user(),
now()
);
end
And call it in your trigger:
create trigger `log_update`
after update on `my_table`
for each row
begin
if (old.name <> new.name) then
call log_insert
(
new.id,
'u',
'name',
old.name,
new.name
);
end if;
if (old.age <> new.age) then
call log_insert
(
new.id,
'u',
'age',
old.age,
new.age
);
end if;
end
You can re-use the stored procedure to log events in your insert and delete triggers.
Make shure to use a composite primary key in your my_table_log to allow updates over several columns. I'd use at least:
primary key(id,column_name,who,ts).
Or use dedicated single column primary key to avoid varchars in your primary key for better performance.
One alternative is to just log the new values together with user() and now():
create table my_table_log
( id ...
, name ...
, age ...
, action ...
, who ...
, ts ... )
To determine what was changed, compare with the previous row.
It is however rather expensive to determine what a row looked like at a certain point in time, you will have to find the last version before that point in time. Another model that makes this a lot easier is to keep track of begin_ts and end_ts for each row:
create table my_table_log
( id ...
, name ...
, age ...
, action ...
, who ...
, begin_ts ...
, end_ts ...)
The insert trigger adds a copy of the row with begin_ts = now() and end_ts = null. The update trigger updates end_ts = now() where end_ts is null and inserts a row like the insert trigger. The delete trigger updates end_ts and might add a copy together with who deleted the row. Determining what a row looked like at ts t is just a matter of where t between start_ts and end_ts
I have these tables in my database:
I want to add the registod and alarmes table one idRegisto .
The alarm table is populated automatically by a trigger. I would like to connect the two tables and the table alarmes populated idRegistos automatically by a trigger with the values โโof table records.
Does anyone can help me please.
I hope I have explained well my doubts
Thank you
My Trigger that populated table alarmes
DELIMITER $$
create TRIGGER alerta
BEFORE INSERT ON registos
FOR EACH ROW
begin
Set #comp=0;
Set #tempmax=0;
Set #tempmin=0;
Set #hummax=0;
Set #hummin=0;
Set #orvalho=0;
select lim_inf_temp, lim_sup_temp, lim_sup_humid, lim_inf_humid, lim_pt_orvalho into #tempmin, #tempmax, #hummax, #hummin, #orvalho from sensores where idSensor=NEW.idSensor;
Set #maxidAlarme=0;
if (CAST(NEW.Temperatura AS UNSIGNED)<#tempmin) then
SELECT MAX(idAlarme) into #maxidAlarme FROM alarmes;
SET #maxidAlarme=#maxidAlarme+1;
INSERT INTO alarmes(idAlarme,descricao_alarme) VALUES (#maxidAlarme,"ERROR");
end if;
end $$;
DELIMITER ;
In alarm table, do you want to use the same newly generated idRegisto of registos table? - Ravinder
Yes. This is what i want. โ user3320956
To insert the same newly generated idRegisto field value in alarm table,
Change part of your trigger body as below:
if ( CAST( NEW.Temperatura AS UNSIGNED ) < #tempmin ) then
SELECT MAX( idAlarme ) into #maxidAlarme FROM alarmes;
SET #maxidAlarme := #maxidAlarme + 1;
SET #auto_idRegisto := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'registos'
AND TABLE_SCHEMA = DATABASE() );
INSERT INTO alarmes( idAlarme, descricao_alarme, idRegisto )
VALUES ( #maxidAlarme, "ERROR", #auto_idRegisto );
end if;
I am trying to do two statements in one scheduled event but it is not working. It wont do anything. Could you please help me figure out what is wrong?
delimiter |
CREATE EVENT resetTimeClockTimeToday
ON SCHEDULE EVERY 1 DAY STARTS '2012-07-10 00:00:00' DO
BEGIN
UPDATE timeclock.employees SET timeToday = 0;
UPDATE timeclock.punches SET missedOut = 1 WHERE timeOUT IS NULL;
END|
delimiter ;
You need to correctly use a different delimiter, e.g.:
DROP EVENT IF EXISTS `Daily Event`;
DELIMITER $$
CREATE DEFINER=`root`#`localhost` EVENT `Daily event` ON SCHEDULE EVERY 1 DAY STARTS '2015-03-06 00:30:00' ON COMPLETION PRESERVE ENABLE DO
BEGIN
-- Newly due summary sheets
UPDATE `summaries`
SET `status` = 'DUE'
WHERE `status` = 'OPEN'
AND NOW() >= LAST_DAY(CONCAT(`year`, '-', `month`, '-01'));
-- Alert user that didn't clock out
INSERT INTO `alerts` (`from`, `to`, `subject`, `message`)
SELECT ....;
-- Alert manager that didn't clock out
INSERT INTO `alerts` (`from`, `to`, `subject`, `message`)
SELECT ....;
-- Alert for stale tickets
INSERT INTO `alerts` (`from`, `to`, `subject`, `message`)
SELECT ....;
END;
$$;
I restarted the MySQL service and it worked!