How to use an IF statement in a MYSQL Trigger? - mysql

Trying to create a trigger that will look for two conditions and update an audit table with those. The trigger works on a customers table and looks for an update to phone number and/or credit limit. The audit table will have a column that show's which field was update or if both were updated.
Here's what I have so far (update with code from below) same error:
#Here's the audit table I created:
create table cust_audit(customerNumber INT, customerName varchar(50),
phone varchar(50), creditLimit dec(10,2),
last_update timestamp, `user` varchar(30),
row_value varchar(20), fields_changed,
varchar(30));
Delimiter $$
CREATE TRIGGER cust_upd_old
AFTER UPDATE
ON customers
FOR EACH ROW
Begin
INSERT INTO audit_table(customerNumber,
customerName,
phone,
creditLimit,
last_update,
`user`,
row_value,
fields_changed)
VALUES (OLD.customerNumber,
OLD.customerName,
OLD.phone,
OLD.creditLimit,
now(),
current_user(),
'before update',
CASE WHEN NEW.phone = OLD.phone THEN 'creditLimit'
WHEN NEW.creditLimit = OLD.creditLimit THEN 'phone number'
ELSE 'both'END);
END $$
DELIMITER ;
The trigger will run but when I try to run an update statement the update fails with code Error Code: 1054. Unknown column 'customerNumber' in 'field list' but the select statement will work so it's some error with the trigger I think. USING MYSQL 8.0.21
update customers set phone = '2128822470' where customerNumber = 181;
select * from customers where customerNumber = 181;

Can be simplified to
CREATE TRIGGER cust_upd_old
AFTER UPDATE
ON customers
FOR EACH ROW
INSERT INTO audit_table(customerNumber,
customerName,
phone,
creditLimit,
last_update,
`user`,
row_value,
fields_changed)
VALUES (OLD.customerNumber,
OLD.customerName,
OLD.phone,
OLD.creditLimit,
now(),
current_user(),
'before update',
CASE WHEN NEW.phone = OLD.phone THEN 'creditLimit'
WHEN NEW.creditLimit = OLD.creditLimit 'phone number'
ELSE 'both' END);

Related

Can anyone help me out how to perform MySQL trigger logic from existing MS SQL trigger Logic

I have two tables one is Master table where the Daily data keeps loading. Other one is Audit Table which tracks whenever there is an update (single/multiple columns updates) happened on Master Table. Below is the logic used for MS SQL
/*** Mastertable creation **/
CREATE TABLE [dbo].[Master](
[ID] [uniqueidentifier] primary key NOT NULL DEFAULT (newid()),
[Name] [varchar](100) ,
[Status] [varchar](10),
[Dept] [varchar](10) )
/** Inserting Data to Master table ***/
Insert into [dbo].[Master] ([Name],[Status],[Dept]) Values
('AAAA', 'Open','EC'),
('BBBB', 'Closed','CS')
/** Audit Table creation ***/
CREATE TABLE [dbo].[Orders_Audit](
[Id] [uniqueidentifier] NOT NULL,
[ColName] [varchar](50),
[OldValue] [varchar](200),
[NewValue] [varchar](200),
[ModifiedAt] [datetime],
[ModifiedBy] [varchar](50) )
Go
/*** Trigger used based on columns for any updates in Master Table***/
Create TRIGGER [dbo].[Tr_Master]
ON [dbo].[Master]
FOR UPDATE
AS
BEGIN
Declare #action varchar(50)
IF UPDATE([Status])
BEGIN
SET #Action = 'Status'
Insert into [dbo].[Orders_Audit] (Id,ColName,OldValue,NewValue,ModifiedAt,ModifiedBy)
select i.Id,#action ,d.[Status] ,i.[Status] ,getdate() ,SUSER_SNAME()
from inserted i,
deleted d where i.Id = d.Id
END
IF UPDATE([Dept])
BEGIN
SET #Action = 'Dept'
Insert into [dbo].[Orders_Audit] (Id,ColName,OldValue,NewValue,ModifiedAt,ModifiedBy)
select i.Id,#action ,d.[Dept] ,i.[Dept] ,getdate() ,SUSER_SNAME()
from inserted i,
deleted d where i.Id = d.Id
END
End
GO
Need to implement same logic in MySQL
Thanks in Advance!!
CREATE TRIGGER trigger_name
AFTER UPDATE ON Master
FOR EACH ROW
BEGIN
IF NOT (OLD.Status <=> NEW.Status) THEN
INSERT INTO Orders_Audit (Id, ColName, OldValue, NewValue, ModifiedAt, ModifiedBy)
SELECT NEW.Id, 'Status', OLD.Status, NEW.Status, NOW(), CURRENT_USER();
END IF;
IF NOT (OLD.Dept <=> NEW.Dept) THEN
INSERT INTO Orders_Audit (Id, ColName, OldValue, NewValue, ModifiedAt, ModifiedBy)
SELECT NEW.Id, 'Dept', OLD.Dept, NEW.Dept, NOW(), CURRENT_USER();
END IF;
END
IF NOT (OLD.column <=> NEW.column) checks that the column value was changed. This expression is NULL-safe (rather than IF OLD.column <> NEW.column) - i.e. it correctly checks the result when one of the values or both of them are NULL.
PS. CURRENT_USER() returns the account name which was used for authentication. If you need the username provided by the client during authentication then use USER() function instead. Example: user may provide 'john'#'1.2.3.4' (and USER() returns this) but the account used may be 'john'#'%' (and CURRENT_USER() returns this). For stored objects/views CURRENT_USER() may return not invoker but definer account (depends on SECURITY attribute).

Create a trigger to insert the old data to a new table

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;
/

Compare two values from different tables - MySql

I need to check before insert into 'year' column of ( Academic Report) that it's not less than the year of 'enrollment date' column in (student)
my tables :
CREATE TABLE STUDENT
(
BCN INT (10) ,
Enrollment_Date timestamp not null default CURRENT_TIMESTAMP,
primary key (BCN),
);
CREATE TABLE ACADEMIC_REPORT
(
Stud_Num INT(10) ,
Year year ,
primary key (Stud_Num , Year ),
foreign key (Stud_Num ) references STUDENT( BCN )
ON DELETE restrict ON UPDATE CASCADE
);
I've try this :
/* ACADEMIC_REPORT._Year Constraint "BEFORE INSERT" */
delimiter //
CREATE TRIGGER ReportYearBIN BEFORE INSERT ON ACADEMIC_REPORT
FOR EACH ROW
BEGIN
declare num int(10);
SET num= (SELECT BCN
FROM STUDENT , ACADEMIC_REPORT
WHERE BCN = ACADEMIC_REPORT.Stud_Num );
IF (ACADEMIC_REPORT.Year < YEAR(num.Enrollment_Date))
THEN
SIGNAL SQLSTATE '10000'
SET MESSAGE_TEXT = 'Error....!';
END IF;
END;
// delimiter ;
but it causes this error during insert
Error Code: 1054. Unknown column 'ACADEMIC_REPORT.Year' in 'field list'
How can I do that constraint ?
Using MySql workbench 6.3
Thank you in advance.
Okay, I created a select statement for the conditions that you want to generate the error on. If the student exists for this report and the report year is less than the students enrollment date it will generate one or more rows for the select query. The proceeding FOUND_ROWS() will then be greater than 0 which will trigger your error message.
CREATE TRIGGER ReportYearBIN BEFORE INSERT ON ACADEMIC_REPORT
FOR EACH ROW
BEGIN
SELECT * FROM STUDENT WHERE BCN=new.Stud_Num AND new._year < Enrollment_Date
IF (SELECT FOUND_ROWS() > 0)
THEN
SIGNAL SQLSTATE '10000'
SET MESSAGE_TEXT = 'Error....!';
END IF;
END;
I've figured out the problem :)
This statement is completely wrong,
YEAR(num.Enrollment_Date)
because it's not correct to write attribute.attribute, you can only use Table.Attribute.
So, here is the solution
/* ACADEMIC_REPORT._Year Constraint "BEFORE INSERT" */
delimiter //
CREATE TRIGGER ReportYearBIN BEFORE INSERT ON ACADEMIC_REPORT
FOR EACH ROW
BEGIN
declare Enroll timestamp;
SET Enroll = (SELECT Enrollment_Date
FROM STUDENT
WHERE BCN = new.Stud_Num);
IF ( new._year < YEAR(Enroll))
THEN
SIGNAL SQLSTATE '10000'
SET MESSAGE_TEXT = 'Error..The Report year cannot be less than the enrollment year..!';
END IF;
END;
// delimiter ;

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:
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

trigger: moving date from 1 table to another

here is my code:
create trigger term_ost after update on `payments`
for each row
begin
UPDATE `current` s
INNER JOIN payments w ON w.id_thing = s.id_thing
INNER JOIN payments w ON w.id= s.id
SET s.`new_pay_date` = w.date;
end;
$$
It's not working.
I want it to set new date after there is new payment for thing which someone bought and change the last date that was in the field date with the new one from new_pay_date.
#EDIT
I am trying to change my trigger so it will update field "new_pay_date" from current after field date is insterted into payments.
Table current:
curr_cash
new_pay_date
id_person
id_thing
sum_of_things
Summary:
When I add new data to payments (e.x. someone paid for thing), I want ot update his last payment time in table current. In "sum_of_things" it sums all the money the client paied.
#edit
After this code:
CREATE TRIGGER term_ost AFTER INSERT ON `payments`
FOR EACH ROW
BEGIN
UPDATE `current`
SET s.`new_pay_date` = NEW.date
WHERE
s.id_thing = NEW.id
END;
there is error:
INSERT INTO `mydb`.`payments` (
`id` ,
`date` ,
`id_thing` ,
`thing_cost`
)
VALUES (
'12312312322', '2012-12-11 15:00:00', '1', '500'
)
MySQL comment:
#1054 - Unknown column 's.new_pay_date' in 'field list'
... :-(
You are using the same table alias w multiple times and that is not allowed. Seems you want to join on multiple columns:
CREATE TRIGGER term_ost AFTER INSERT ON `payments`
FOR EACH ROW
BEGIN
UPDATE `current`
SET `new_pay_date` = NEW.date
WHERE
id_thing = NEW.id
END;