Can't update value in mySQL database - mysql

So I'm trying to make a simple scheme of order-> payment-> shipping. Here is the structure of the main tables:
cust_order (order_id, customer_id, catalog_id, items_ordered, order_date, payment_status(Paid/Unpaid), shipping_status (Preparing/Shipped))
payment (payment_id, order_id, pay_bank, pay_accnum, pay_date)
shipping (shipping_id, order_id, shipping_date)
And the plot is:
An order was made. The default value of payment_status would be Unpaid and shipping_status = Preparing
(Trigger 1) If an order was made, then a row will be inserted on payment table, with the value of pay_bank, pay_accnum, pay_date is NULL
Assume that the customer has paid, the value of pay_bank, pay_accnum, pay_date has been filled. Then (Trigger 2), the payment_status on the order table will be changed to 'Paid'
(Trigger 3) if the payment_status on the order table is changed to 'Paid', then a row will be inserted on shipping table (so the shipping admin can see the order that need to be sent) with the shipping_date value is NULL
Assume that the shipping admin has send the product, he edited the value of shipping_date, so it's no longer NULL. Then (Trigger 4), the shipping_status in order table is changed to 'Shipped'
I've created all the 4 triggers. Here is the code:
Trigger 1
create trigger order_to_payment
after insert on cust_order
for each row
begin
insert into payment (order_id,catalog_id)
values (new.order_id, new.catalog_id);
end;
//
Trigger 2
create trigger verifPay
after update on payment
for each row
begin
if new.pay_bank is NOT NULL and new.pay_accnum is NOT NULL and new.pay_date is NOT NULL then
update cust_order
set payment_status = 'Paid'
where order_id=new.order_id;
end if;
end;//
Trigger 3
create trigger paid_to_shipping
after update on cust_order
for each row
begin
if new.payment_status = 'Paid' then
insert into shipping(order_id,customer_id)
values (new.order_id, new.customer_id);
end if;
end;
//
Trigger 4
create trigger ChangeShippedStatus
after update on shipping
for each row
begin
if new.shipping_date is NOT NULL then
update cust_order
set shipping_status='Shipped'
where order_id=new.order_id;
end if;
end;//
And here I found a trouble when I'm trying to edit the pay_date value on the shipping table, it says #1442 - Can't update table 'shipping' in stored function/trigger because it is already used by statement which invoked this stored function/trigger
I've made a similar scheme before and it worked, so I'm trying to figure out where the problem is in this case

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.

MySQL create trigger for logging table

Hello I am new for MySQL. I have 2 table, a data table and a log/history table. I would like to make a trigger, that put the old data from Data to Data_log if any of the column change on the Data. I made a trigger but I don't know how to check if the value of any column changes. Lately I would like to create some procedure/view which can return one line data on a specific date. Like return all field from ID 1 on 2022-03-27
Data:
ID
name
data
price
1
thing1
desc of t1
100
2
thing2
desc of t2
300
Data_log:
log_id
data_id
column_name
old_data
date
1
1
data
desc t1
2022-03-28 06:49:14
2
2
price
600
2022-03-28 11:34:46
3
1
price
4400
2022-03-28 09:15:54
Trigger (only check price column):
DELIMITER //
CREATE TRIGGER `log_old_data` BEFORE UPDATE ON `data`
INSERT INTO data_log
(
data_id,
old_data
)
VALUES
(
OLD.id,
OLD.price <- I need here a Select I think
);
END//
Since you have few columns, it may be simpler to do it "by hand" for every columns
DELIMITER //
CREATE TRIGGER `log_old_data` BEFORE UPDATE ON `data`
IF NEW.name != OLD.name THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.name);
END IF;
IF NEW.data != OLD.data THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.data);
END IF;
IF NEW.price != OLD.price THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.price);
END IF;
END //
DELIMITER ;
PS: I did not test it, but it should work. If it doesn't, leave your mysql version to allow me to test on your version
For the SELECT part, since yo record every change on a separate table you only have to do a query on it
SELECT * FROM log_old_data WHERE `log_id` = 1 AND DATE(`date`) = '2022-03-27';
PS: Careful, DATE() on a WHERE condition may not be the perfect choice, it will not use indexes. I use generated columns to add index on date for this kind of case.

How can i seect query for product list after order (TRIGGER)

My tables:
Orders Products
Id(PK) |Quantity | Date | ProdIdFK |OrdFK(ref CustomerID) ProdID(PK) | Quantity | Name
and customer table.
I have to make an order using trigger so i do:
INSERT into Orders(Id, Quantity, Date, ProdIDFK, OrdFK)
values(3, 2, '2020/01/27', 15, 2);
CREATE TRIGGER QuantityUpdate
AFTER INSERT
ON Orders FOR EACH ROW
BEGIN
UPDATE Products
SET products.Quantity = Products.Quantity - New.Quantity
WHERE products.ProdID = New.ProdID
END$$
DELIMITER ;
But just nothing happening, it shows the old quantity and doesnt change. I tried to put INSERT after BEGIN(i dunno what is correct one) and also nothing. What is correct query for this?
Your trigger code looks OK - apart, maybe, for a glitch in the column name spotted by Gordon Linoff.
However, if you want it to fire on the INSERT statement that is showed in your script, you need to create it first, then INSERT.
CREATE TRIGGER QuantityUpdate
AFTER INSERT ON Orders FOR EACH ROW
BEGIN
...
END$$
DELIMITER ;
INSERT into Orders(Id, Quantity, Date, ProdIDFK, OrdFK)
VALUES(3, 2, '2020-01-27', 15, 2);
Once the trigger is created, it fires for every order inserted. It has no way, however, to take in account inserts that happened before its creation.
At the very least, the syntax is wrong in either the INSERT or the trigger, because one is using ProdIdfk and the other ProdId.
If the first is the correct name, then the trigger should be:
UPDATE Products p
SET p.Quantity = p.Quantity - New.Quantity
WHERE p.ProdID = New.ProdIDFK;

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 ;

MySQL trying to create trigger to update inventory when a new purchase is made

I have three tables, purchases (purchase_id, quantity_purchased, purchase_date,purchase_time, payment_method) inventory (inventory_id, amount, item_id) and items (item_name, price, notes, item_id) I need to create a trigger to update the inventory (amount-quantity_purchased) every time a new purchase is made. This is what I have so far:
CREATE TRIGGER inventory_update
AFTER INSERT ON purchases
FOR EACH ROW
UPDATE inventory
SET amount = amount - NEW.quantity_purchased
WHERE purchase_id = NEW.purchase_id;
any help is appreciated, thanks
Is there a foreign key that would link your purchase table to your inventory?
Below is what I brainstormed, it should work if you can determine what product you sold when you insert into the product table.
Create trigger inventory_update on Dbo.purchases
after insert
as
declare #inserted int, #stock int, #pid int
select #Pid= pid from inserted
select #inserted = quantity_purchased from inserted
select #stock = quantity_purchased from dbo.inventory
where #PID= inventory. inventory_id -- This line needs a way to find out what the purchase item is
if #inserted>#stock
begin
print('Not enough Stock')
rollback
end
Else if #inserted<#stock and #PID is not null
begin
update inventory
set amount= inventory.amount-#inserted
where #PID =inventory.item_ID -- Once again the issue is figuring out what your purchase item is on this line.
print('A Sale has been made')
end
else
print('There is an error in the process')
I would guess that the purchases table has an item_id. If so:
DELIMITER $$
CREATE TRIGGER inventory_update AFTER INSERT ON purchases
FOR EACH ROW
BEGIN
UPDATE inventory i
SET i.amount = i.amount - NEW.quantity_purchased
WHERE i.item_id = NEW.item_id;
END;
$$
DELIMITER ;