MySQL 1422, explicit or implicit commit is not allowed in trigger - mysql

I understand that I cannot do a transaction inside a trigger, but I CAN inside a procedure. And I should be able to call a procedure from a trigger. Which is exactly what I'm doing.
TABLE A has an "after update trigger".
I only want to call the procedure if one of two columns has been changed.
TABLE A TRIGGER:
IF (
NEW.colA != OLD.colA
OR
NEW.colB != OLD.colB
) THEN
call setBonuses( NEW.colA, NEW.colB );
END IF
The setBonuses SP effects TABLE B by doing some calculations using TABLE_A.colA,TABLE_A.colB (I don't think it's relevant, but TABLE B does have an "after update trigger", but it has a conditional to only do its update if fields NOT effected by the below procedure are changed, so it does 'nothing' when the bonus field is updated.)
THE STORED PROCEDURE:
begin
IF A > B THEN
SET n = A;
...
ELSEIF A < B THEN
SET n = B;
...
END IF;
start transaction;
-- Bonuses
UPDATE TABLE_B
SET bonus = 0
WHERE... ;
UPDATE TABLE_B
SET bonus = bonus + n
WHERE...;
UPDATE TABLE_B
SET bonus = bonus + n
WHERE...;
UPDATE TABLE_B
SET bonus = bonus + n
WHERE...;
UPDATE TABLE_B
SET bonus = bonus + n
WHERE...;
UPDATE TABLE_B
SET bonus = bonus + n
WHERE...;
commit;
end
The SP works fine if called directly by the application, however, I don't want to have to explicitly call it as a chain in some event, I really just need it to do its calcs and update TABLE B - whenever someone changed TABLE A (colA or colB).
Any reason why this isn't possible.

Related

MySQL - Update Column Value with Function and Trigger Before Insert

i'm trying to update 2 column with trigger before insert, but i have unexpected result. i insert some data and the 2 last column will automatically inserted with values, here my first attempt
see? the last 2 column is null even i set some trigger and function to fill that columns automatically. Here my second attempt WITH EXACTLY SAME DATA
the last 2 column is filled with data, but i don't understand why the first attempt is fail?
here my trigger i use in the column total_harga
CREATE TRIGGER `set_total_harga` BEFORE INSERT ON `tbl_transaksi_detail`
FOR EACH ROW BEGIN
set NEW.total_harga = hitungTotalHargaPerItem(NEW.qty, NEW.harga_satuan);
END
trigger for column harga_satuan
CREATE TRIGGER `set_harga_satuan` BEFORE INSERT ON `tbl_transaksi_detail`
FOR EACH ROW BEGIN
set NEW.harga_satuan = set_Harga_Unit(NEW.unit, NEW.id_barang, NEW.no_transaksi);
END
function set_Harga_Unit
BEGIN
DECLARE
q,
id_toko INT;
SET
id_toko =(
SELECT DISTINCT
`tbl_transaksi`.`id_toko`
FROM
`tbl_transaksi`,
`tbl_transaksi_detail`
WHERE
`tbl_transaksi`.`no_transaksi` = no_trans
); IF unit = "PCS" THEN
SET
q =(
SELECT
`tbl_harga_jual`.`harga_pcs`
FROM
`tbl_harga_jual`
WHERE
`tbl_harga_jual`.`id_barang` = id_brg AND `tbl_harga_jual`.`id_toko` = id_toko
); RETURN q; ELSEIF unit = "PAK" THEN
SET
q =(
SELECT
`tbl_harga_jual`.`harga_pak`
FROM
`tbl_harga_jual`
WHERE
`tbl_harga_jual`.`id_barang` = id_brg AND `tbl_harga_jual`.`id_toko` = id_toko
); RETURN q; ELSEIF unit = "KARTON" THEN
SET
q =(
SELECT
`tbl_harga_jual`.`harga_karton`
FROM
`tbl_harga_jual`
WHERE
`tbl_harga_jual`.`id_barang` = id_brg AND `tbl_harga_jual`.`id_toko` = id_toko
); RETURN q;
END IF; RETURN q;
END
function hitungTotalHargaPerItem
BEGIN
DECLARE hasil int;
set hasil = qty * harga_satuan;
RETURN hasil;
END
The root cause serms to be the select that sets id_toko variable's value:
SET
id_toko =(
SELECT DISTINCT
`tbl_transaksi`.`id_toko`
FROM
`tbl_transaksi`,
`tbl_transaksi_detail`
WHERE
`tbl_transaksi`.`no_transaksi` = no_trans
);
In the select you inner join tbl_transaksi_detail (the table with the trigger in question) on another table. However, in the 1st case tbl_transaksi_detail is still empty (the trigger is before insert), therefore id_toko variable is set to null.
This will result q being null, which in turn results in the entire calculation set to null.
In the 2nd case there is already a record inserted into tbl_transaksi_detail table, therefore the calculation returns a non null value. But it returns the correct values only because the 1st and 2nd records' details are exactly the same.
I do not really understand that select that calculates id_toko anyway. If that is a transaction id, then you may use last_insert_id() if it is auto increment and the transaction record has just been created or max(id_toko) to get the highest value of id_toko (this is not multi user safe).
it seem i have mistaken select query in function set_Harga_Unit, based on clues from #Shadow
SET
id_toko =(
SELECT DISTINCT
`tbl_transaksi`.`id_toko`
FROM
`tbl_transaksi`,
`tbl_transaksi_detail`<<== I DON'T NEED THIS
WHERE
`tbl_transaksi`.`no_transaksi` = no_trans
); IF unit = "PCS" THEN
when first insert in tbl_transaksi_detail, the value no_transaksi is null because i use trigger before insert in empty table (tbl_transaksi_detail), so i remove tbl_transaksi_detail from query
SET
id_toko =(
SELECT DISTINCT
`tbl_transaksi`.`id_toko`
FROM
`tbl_transaksi`
WHERE
`tbl_transaksi`.`no_transaksi` = no_trans
); IF unit = "PCS" THEN
now it working, thanks everybody!

set new value depending on another table value trigger

What I am trying is to set a new value depending on the value from another table. This is a BEFORE UPDATE trigger. I need to set a new.amount in "webs" table depending on the value of percent column from table "webTransactions". If percent = 30.00 then new amount must change from whatever to 0.00. What I tried so far but is giving me error:
if new.type=9 and new.referenceId=0 then set new.amount=0.00 where (SELECT * FROM webs s JOIN webTransactions r ON r.webId = s.id WHERE s.percent = 30.00);
Anyone can help to make this trigger run?
Thank you
you can try something like this
UPDATE websTransactions
SET amount= CASE
WHEN type=9 AND referenceId=0 THEN 0.00
ELSE amount
END
WHERE key IN
(SELECT key FROM webs s JOIN webTransactions r ON r.webId = s.id
WHERE s.percent = 30.00)
replace key with the primary key of websTransactions
You might want to to use some more efficient code, triggers are a sort of stored routines so program them that way. Also, it's not neccesary to join the table you're updating in a subquery.
DELIMITER $$
CREATE TRIGGER amount_check BEFORE UPDATE ON webs
FOR EACH ROW
BEGIN
IF NEW.type=9 and NEW.referenceId=0 THEN
SET #percent := (SELECT percent
FROM webTransactions
WHERE webId = OLD.id);
IF #percent = 33.00 THEN
NEW.amount = 0
END IF;
END IF;
END$$
DELIMITER ;

Trigger: Referencing updated attribute

I have a Trigger on UPDATE.
What is the correct procedure for referencing attribute from the table that is not updated by the UPDATE SQL command? Is the attribute still in the UPDATE variable? I would like to get the value of that attribute for the updated row.
You can access a values of a column before update and after update in MySQL by using keywords OLD and NEW.
For example if you want to determine whether a value of a column actually has been changed during updated you can do
IF NOT OLD.column_name <=> NEW.column_name THEN
-- do something here
END IF;
Note: <=> is NULL-safe comparison operator in MySQL
BTW: There is no UPDATED virtual table in MySQL. It's from SQL Server.
Here is a SQLFiddle demo. Note that even though update affected all records in the table, only one message has been logged in log table. It's because value for a row with id 2 in the end stayed the same.
UPDATE: to keep your finished flag in sync you need triggers for all events (insert, update, delete).
DELIMITER //
CREATE TRIGGER tg_ai_event
AFTER INSERT ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END//
CREATE TRIGGER tg_ad_event
AFTER DELETE ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = OLD.activity;
END//
CREATE TRIGGER tg_au_event
AFTER UPDATE ON event
FOR EACH ROW
BEGIN
IF NOT OLD.activity <=> NEW.activity THEN
-- if activity id was changed for an event then clculate finished flag
-- for both old and new activity id
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id IN(OLD.activity, NEW.activity);
ELSE
-- otherwise calculate finished flag only if done flag is changed
IF NOT OLD.done <=> NEW.done THEN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END IF;
END IF;
END//
DELIMITER ;
Here is SQLFiddle demo

How to fix this MySQL trigger?

I'm trying to get this trigger to work:
CREATE TRIGGER Transaction_insert BEFORE INSERT ON Transaction
FOR EACH ROW WHERE Number = NEW.AccountNumber
IF Account.CreditBalance + NEW.Amount < Account.CreditLimit THEN
UPDATE Account SET CreditBalance = CreditBalance + NEW.Amount where Number = NEW.AccountNumber;
ELSE
SET NEW.Valid = 0
END IF;
This is the error I get from myPHPAdmin.
Your IF needs to be a full SELECT to reference another table (Account)
IF EXISTS (SELECT * FROM `Account` A
WHERE A.CreditBalance + NEW.Amount < A.CreditLimit AND
A.Number = NEW.AccountNumber) THEN
UPDATE ...
Edit: this was on your 2nd duplicate answer
In this case, remove the WHERE after FOR EACH ROW
Updated Answer
This is what I think you want, assuming that Account to Transaction is a 1:N relationship keyed on Number/AccountNumber:
DELIMITER //
-- Assumptions:
-- 1. Transaction.AccountNumber is F.K. REFERENCES Account(Number)
-- 2. Account.Number is UNIQUE
--
CREATE TRIGGER trg_bi_transaction BEFORE INSERT ON Transaction
FOR EACH ROW
BEGIN
-- Adjust account balance (if permitted)
--
UPDATE Account
SET CreditBalance = CreditBalance + NEW.Amount
WHERE Number = NEW.AccountNumber
AND
(CreditBalance + NEW.Amount) < CreditLimit;
-- Was the adjustment valid/permitted?
--
SET NEW.Valid = (ROW_COUNT() = 1);
END //
DELIMITER ;
That trigger will attempt to UPDATE the proper Account for any given Transaction if the CreditLimit permits. The Valid field will be set to 1 if the UPDATE succeeded, and 0 if it did not.
Original Answer
MySQL triggers do not support trigger-level WHERE clauses. Move the Number/NEW.AccountNumber check inside the trigger body.

Finding min and max value of the table in a constant time

I have a table which contains relative large data,
so that it takes too long for the statements below:
SELECT MIN(column) FROM table WHERE ...
SELECT MAX(column) FROM table WHERE ...
I tried index the column, but the performance still does not suffice my need.
I also thought of caching min and max value in another table by using trigger or event.
But my MySQL version is 5.0.51a which requires SUPER privilege for trigger and does not support event.
It is IMPOSSIBLE for me to have SUPER privilege or to upgrade MySQL.
(If possible, then no need to ask!)
How to solve this problem just inside MySQL?
That is, without the help of OS.
If your column is indexed, you should find min(column) near instantly, because that is the first value MySQL will find.
Same goes for max(column) on an indexed column.
If you cannot add an index for some reason the following triggers will cache the MIN and MAX value in a separate table.
Note that TRUE = 1 and FALSE = 0.
DELIMITER $$
CREATE TRIGGER ai_table1_each AFTER INSERT ON table1 FOR EACH ROW
BEGIN
UPDATE db_info i
SET i.minimum = LEAST(i.minimum, NEW.col)
,i.maximum = GREATEST(i.maximum, NEW.col)
,i.min_count = (i.min_count * (new.col < i.minumum))
+ (i.minimum = new.col) + (i.minimum < new.col)
,i.max_count = (i.max_count * (new.col > i.maximum))
+ (i.maximum = new.col) + (new.col > i.maximum)
WHERE i.tablename = 'table1';
END $$
CREATE TRIGGER ad_table1_each AFTER DELETE ON table1 FOR EACH ROW
BEGIN
DECLARE new_min_count INTEGER;
DECLARE new_max_count INTEGER;
UPDATE db_info i
SET i.min_count = i.min_count - (i.minimum = old.col)
,i.max_count = i.max_count - (i.maximum = old.col)
WHERE i.tablename = 'table1';
SELECT i.min_count INTO new_min_count, i.max_count INTO new_max_count
FROM db_info i
WHERE i.tablename = 'table1';
IF new_max_count = 0 THEN
UPDATE db_info i
CROSS JOIN (SELECT MAX(col) as new_max FROM table1) m
SET i.max_count = 1
,i.maximum = m.new_max;
END IF;
IF new_min_count = 0 THEN
UPDATE db_info i
CROSS JOIN (SELECT MIN(col) as new_min FROM table1) m
SET i.min_count = 1
,i.minimum = m.new_min;
END IF;
END $$
DELIMITER ;
The after update trigger will be some mix of the insert and delete triggers.