Ing. Michal Hudak
Ing. Michal Hudak

Reputation: 5612

Mysql triggers - capture each column change

I am trying to create trigger, that capture changes in database after update.

Table my_table I am watching:

enter image description here

Table my_table_log where I am writing changes to log them enter image description here

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

Upvotes: 1

Views: 4186

Answers (2)

robsn
robsn

Reputation: 744

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.

Upvotes: 2

Lennart - Slava Ukraini
Lennart - Slava Ukraini

Reputation: 7171

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

Upvotes: 0

Related Questions