mysql trigger on update to populate another table - mysql

I am new to this TRIGGER thing and it is beating me up pretty good.
The intention is to populate a price history table every time a price change occurs on table prices.
So far my attempts are taking all prices changes each time one price is updated, or not working at all.
I basically did not get this logic, so that I can limit it to the one price being updated, not all of the updates.
This below is a bit of a pseudo-code.
DELIMITER //
CREATE TRIGGER price_update
AFTER UPDATE
ON prices FOR EACH ROW
BEGIN
INSERT INTO prices_history (version_id,price,data_change)
SELECT version_id, price, time_update FROM prices WHERE????? ;
END //
DELIMITER ;
table prices (summarized)
version_id smallint(4) primary
price decimal (10,0)
time_update timestamp
table prices_history
price_id int(5) primary
version_id smallint(4)
price decimal (10,0)
data_change datetime

Don't do SELECT inside the trigger, as that will use all the rows from table. You have available the columns from the changed row directly. Also you can use OLD. and NEW. for distinguishing between the values before and after UPDATE.
DELIMITER $$
CREATE TRIGGER price_update
BEFORE UPDATE ON prices
FOR EACH ROW
BEGIN
INSERT INTO prices_history SET
price = NEW.price,
version_id = NEW.version_id, // or OLD.version_id
data_change = NEW.time_update;
END$$
DELIMITER ;

prices table should have price_id - not version_id
Then
select #max_version = max(version_no)+1 from price_history where price_id = #changed_price_id
INSERT INTO prices_history (price_id, version_id,price,data_change)
SELECT price_id_id, #max_version, price, time_update FROM prices WHERE price_id = #changed_price_id
;

Related

MySQL - Can't update table in stored function/trigger because it is already used by statement which invoked this stored function/trigger (select)

I am new to MySQL and learning about trigger. I have 2 tables that I want : when a table (detail_transaction) has been inserted, a 'stock' field of another table (item) change.
'item' Table
id
name
price
stock
1
Item_A
15
900
2
Item_B
9
500
'detail_transaction' Table
id
id_item
count
total_price
1
1
5
75
If I insert new row in 'detail_transaction' table, I WANT my 'stock' field in 'item' table with the same 'id' to decrease and adjust to the 'count' of the 'detail_transaction'. For example :
I insert new row in 'detail_transaction' table :
id
id_item
count
total_price
2
1
10
150
I WANT the 'item' table updated to :
id
name
price
stock
1
Item_A
15
890
2
Item_B
9
500
I created a trigger to try achieve my purpose, but when I tried to insert new row in 'detail_transaction' I got this error : Can't update 'item' table in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
My trigger :
DELIMITER $$
CREATE TRIGGER update_stock
AFTER INSERT
ON detail_transaction
FOR EACH ROW
BEGIN
UPDATE item
JOIN detail_transaction ON detail_transaction.id_item = item.id
SET stock = stock - NEW.count
WHERE item.id = NEW.id_item;
END$$
DELIMITER ;
Then, I inserted row to detail_transaction table :
INSERT INTO detail_transaction (id, id_item, count, total_price)
VALUES (2, 1, 10, (SELECT price FROM item WHERE item.ID = 1) * 10);
But I got the error. What can I do to solve this? Is it because of the SELECT part when I try to INSERT? Thanks for your answer.
Firstly (and opinionated): triggers are hard to debug, test and maintain. Systems that include triggers are really hard to debug because they introduce side effects - "I did X on this table, and then Y happened on a different table". As a developer, you have to keep all the triggers in your head to understand what an individual statement might do.
If we take your example, for instance, you might have a trigger on the "stock" field in Item to create a purchase order record to replenish the stock if it falls below a threshold. The purchase order table might have an insert trigger to create a record in accounts payable, which might have an insert trigger to reject records if the total balance for a given vendor exceeds a threshold. That chain of triggers implements valid business logic, but results in really complex debugging process when suddenly an insert into detail_transaction is rejected because the product vendor exceeds their payment limit. (And yes, I have seen this kind of scenario!).
One of the challenges with triggers is that the database engine does not want an infinite loop to happen, or to have the value of the field you are SELECTing changing as a result of the trigger firing.
Also, you don't need that join - you can get the values from NEW.
DELIMITER $$
CREATE TRIGGER update_stock
AFTER INSERT
ON detail_transaction
FOR EACH ROW
BEGIN
UPDATE item
SET stock = stock - NEW.count
WHERE item.id = NEW.id_item;
END$$
DELIMITER ;
The way to do this is to use a variable:
SET #PRICE = ((SELECT price FROM item WHERE item.ID = 1) * 10);
INSERT INTO detail_transaction (id, id_item, count, total_price)
VALUES (2, 1, 10, #PRICE);
SELECT * from item;
See fiddle.
EDIT - some of the other answers show a simpler solution - calculating the total price in a trigger.
Reasonable people disagree about how to use triggers - but I would suggest that using triggers to calculate derived values - "total stock for a given item", or "total price of a transaction" - is often a bad idea. You're effectively duplicating data - the total stock level for an item is both the sum of transactions, and the attribute in a row. The total price is both "price * quantity", and an attribute in a row. What happens if someone executes an update statement for total_price or total_stock (either intentionally or as part of a bug)? Which value is correct?
You should not mix insert..values and insert..select I would rewrite the insert as
INSERT INTO detail_transaction (id, id_item, count, total_price)
select 2, 1, 10, price * 10
FROM item
WHERE item.ID = 1;
Although my choice would be a before insert trigger
DELIMITER $$
CREATE TRIGGER update_stock before INSERT ON detail_transaction
FOR EACH ROW
BEGIN
set new.total_price = (
select item.price * new.count
FROM item
WHERE item.ID = new.id
);
END$$
DELIMITER ;
with an insert
INSERT INTO detail_transaction (id, id_item, count, total_price)
VALUES (2, 1, 10, null);
The after insert publish by you fails because you use a multi table update invoking a table which fired the trigger, this is not allowed , the resolution of this issue appear in a previous answer.
CREATE TABLE item (
`id` INTEGER AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255),
`price` INTEGER,
`stock` INTEGER
);
INSERT INTO item VALUES
('1', 'Item_A', '15', '900'),
('2', 'Item_B', '9', '500');
SELECT * FROM item;
CREATE TABLE detail_transaction (
`id` INTEGER AUTO_INCREMENT PRIMARY KEY,
`id_item` INTEGER,
`count` INTEGER,
`total_price` INTEGER,
FOREIGN KEY (`id_item`) REFERENCES `item` (`id`)
);
INSERT INTO detail_transaction VALUES
('1', '1', '5', '75');
SELECT * FROM detail_transaction;
id
name
price
stock
1
Item_A
15
900
2
Item_B
9
500
id
id_item
count
total_price
1
1
5
75
-- trigger which calculates total_price value
CREATE TRIGGER tr_bi_get_total_price
BEFORE INSERT ON detail_transaction
FOR EACH ROW
SET NEW.total_price = (
SELECT NEW.`count` * item.price
FROM item
WHERE id = NEW.id_item
);
-- trigger which adjusts stock value
CREATE TRIGGER tr_ai_update_stock_in_item
AFTER INSERT ON detail_transaction
FOR EACH ROW
UPDATE item
SET stock = stock - NEW.count
WHERE item.id = NEW.id_item;
INSERT INTO detail_transaction (id_item, `count`) VALUES (1, 10);
SELECT * FROM detail_transaction;
SELECT * FROM item;
id
id_item
count
total_price
1
1
5
75
2
1
10
150
id
name
price
stock
1
Item_A
15
890
2
Item_B
9
500
fiddle
PS. Each trigger contains only one statement. So neither BEGIN-END noк DELIMITER command needed.

inserting values from another table via a TRIGGER in MYSQL

I need to create a trigger when a new record is added to the "plan" table, a record is automatically created in the "results" table.
Table "Plan" has columns:
IdService
IdEmployee
Groupe
Date
Type (varchar)
Table "Results" has columns:
IdService
IdEmployee
IdClient
Date
Result (varchar)
But the idClient must be taken from table "Clients", corresponding to the group number added to the plan. Thus, the trigger should create not one, but several rows in the result table (since there can be several clients in one group)
I am attaching my code, but there is obviously an error in it
CREATE DEFINER = CURRENT_USER TRIGGER `mydb`.`Plan_AFTER_INSERT` AFTER INSERT ON `Plan` FOR EACH ROW
BEGIN
insert into results (Result, idClient, Date, idService, idEmployee)
values ('в процессе',idClient = (Select idClient from Clients
where Clients.Groupe = Plan.New.Groupe),
NEW.Date, NEW.idService, NEW.idEmployee);
END
It is quite OK only your subquery has ti return only one row, so it is better to limit it
DELIMITER //
CREATE DEFINER = CURRENT_USER TRIGGER `mydb`.`Plan_AFTER_INSERT` AFTER INSERT ON `Plan` FOR EACH ROW
BEGIN
insert into results (Result, idClient, Date, idService, idEmployee)
values ('в процессе',(Select idClient from Clients
where Clients.Groupe = New.Groupe ORDER BY idClient LIMIT 1),
NEW.Date, NEW.idService, NEW.idEmployee);
END//
DELIMITER ;

Update mysql rows with formula having different values for each row

I have two tables:
Table 1 : parts
id (primary key)
code
title
quantity
Table 2 : bill_items
id (primary key)
bill_id
parts_id: refers to primary key of table parts
qty
I would like to update parts table - qty every time I create a row in table bill_items. The qty in parts table is to be decremented by qty of bill_items. There may be N number of updates to table bill_items in one go. Would like to use a single INSERT....ON DUPLICATE or UPDATE statement.
Thank you for your time.
I think for this case better using trigger :
DELIMITER $$
CREATE
TRIGGER `bill_items_after_insert` AFTER INSERT
ON `bill_items`
FOR EACH ROW BEGIN
UPDATE parts set quantity = quantity - NEW.qty WHERE id = NEW.parts_id;
END$$
DELIMITER ;
I suggest you also make trigger for UPDATE and DELETE also for data consistency.
UPDATED
Based on your comment, it is possible to use normal insert and update using transaction for consistency data :
START TRANSACTION;
INSERT INTO bill_items (bill_id, parts_id, qty) VALUES (your_bill_id,your_parts_id,your_qty);
UPDATE parts SET quantity = quantity - your_qty WHERE id = your_parts_id;
COMMIT;

Updating other table column after insert in MySQL

I have these two table called "cases" and attendance respectively which has four columns:
cases-
id empid reaction date_t
1 EMP12654 interested 2017-09-22
attendance-
id empid logintime logouttime date_t flag workinghours call_att
1 EMP12654 00:14:49 05:14:49 2017-09-18 set 6 1
What I want to do is create a trigger on cases table that updates call_att column of attendance table with number of entries in reaction column of cases table, this is what I have tried so far
CREATE DEFINER=`root`#`localhost` TRIGGER `number_call`
AFTER INSERT ON `cases` FOR EACH ROW
BEGIN UPDATE attendance set call_att=call_att +1
WHERE empid=new.empid AND date_t=new.date_t; END
But that doesn't seem to work. I am quite new to triggers.
try this
CREATE TRIGGER number_call
AFTER INSERT ON cases
FOR EACH ROW
BEGIN
UPDATE attendance set call_att=(select count(*) from cases where empid=NEW.empid )
date_t=NEW.date_t;
END

MySQL Trigger Value from Previous Row with Same Column Name

Inventory Table:
Inventory History Table:
The query:
INSERT INTO inventory_history (SKU, Quantity, timestamp)
SELECT SKU, Quantity, modifiedtime FROM inventory WHERE modifiedtime BETWEEN '2016-12-25 00:00:00' AND '2016-12-26 00:00:00';
The Trigger:
CREATE TRIGGER `sold_diff` BEFORE INSERT ON `inventory_history`
FOR EACH ROW begin
declare prev_quantity int(11) default 0;
declare prev_sku varchar(255) default null;
select sku
into prev_sku
from inventory_history
where prev_sku = NEW.sku
order by id desc
limit 1;
select quantity
into prev_quantity
from inventory_history
order by id desc
limit 1;
set NEW.sold = prev_quantity
;
end
The Result:
Now, how it's set-up is it's taking prev_quantity from the previous row, and putting it into the sold column.
I can not figure out a way to bind SKU in with prev_quantity, so that it will give me the previous Quantity value from the corresponding SKU.
Desired Result:
I've messed with a variety of different WHERE clauses on the two declared, but nothing is working right.. so I'm thinking this is not the right path to take.
How can this be achieved?
I think you are taking the wrong approach.
You seem to want an insert on the inventory table. When a new value is inserted or updated (or deleted), you then insert a row in the inventory_history table with the old and new values.
You then don't need an explicit insert on inventory_history.