skip to Main Content

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.

3

Answers


  1. 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?

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search