Problem to insert running total in MySQL transactional database. need your help for solutions and opinion. Table structure of my table is,
create table `wtacct` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ACCOUNT_NO` varchar(16),
`AMOUNT` float(16,2),
`BALANCE` float(16,2)
);
[Please note other fields have been removed to make it simple example]
I am doing Transaction as,
Dr 10 USD from account 1001 and
Cr 10 USD to account 2002
Insert query
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, 100), ('', 2002, 10, 5000);
I want the Balance as,
BALANCE of Account no 1001 = Last transaction Balance of account 1001 - 10.
My solutions and limitations
Solution 1
In insert statement put sub query in balance field:
select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
Limitation: Mysql does not support same table select query (wtacct) where inserting the data (wtacct).
Solution 2
Using insert into select statement
insert into wtacct select '' ID, 1001 ACCOUNT_NO, -10 AMOUNT, (BALANCE-10) BALANCE where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
Limitation: For first transaction there is no record in wtacct for the account 1001 so select query will not return any record for first transaction.
Solution 3
Taking balance in variable and use it in insert statement.
select #balance1001 :=balance from wtacct
where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
select #balance2002 :=balance from wtacct
where account_no=2002 and id in(select max(id) from wtacct where account_no=2002)
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, #balance1001-10), ('', 2002, 10, #balance2002+10);
Limitation: there is a chance to be change the balance in time between select and insert query execution. also its costly, 3 query execution required.
Solution 4
Insert and then update Balance
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, 0);
UPDATE wtacct set balance = (ifnull(Select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where id <last_insert_id() and account_no=1001),0) -10)
where id =last_insert_id() and account_no=1001
........
Limitation: query is costly. its required 4 (two insert and 2 update) query execution. note last_insert_id() is php function
Solution 5
Using a trigger on insert statement. In the trigger, the balance will be updated calculating last transaction value and insert amount.
Limitation: Trigger not support transaction behavior and may fail.
Please give your solution and opinion on the above solutions. Please note in the above example their may be some syntax error/error. Please ignore them.
A big limitation I didn't see listed is a potential race condition, where two rows are being inserted into the table at the same time. There's a chance that the two inserts will both get the current "balance" from the same previous row.
One question: do you also have a separate "current balance" table that keeps a single value of the current "balance" for each account? Or are you only relying on the "balance" from the previous transaction.
Personally, I would track the current balance on a separate "account balance" table. And I would use BEFORE INSERT/UPDATE triggers to maintain the value in that row, and use that to return the current balance for the account.
For example, I would define a trigger like this which gets fired when a row is inserted into `wtacct` table:
CREATE TRIGGER wtacct_bi
BEFORE INSERT ON wtacct
FOR EACH ROW
BEGIN
IF NEW.amount IS NULL THEN
SET NEW.amount = 0;
END IF
;
UPDATE acct a
SET a.balance = (#new_balance := a.balance + NEW.amount)
WHERE a.account_no = NEW.account_no
;
SET NEW.balance = #new_balance
;
END$$
The setup for that trigger...
CREATE TABLE acct
( account_no VARCHAR(16) NOT NULL PRIMARY KEY
, balance DECIMAL(20,2) NOT NULL DEFAULT 0
) ENGINE=InnoDB
;
CREATE TABLE wtacct
( id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT
, account_no VARCHAR(16) NOT NULL COMMENT 'FK ref acct.account_no'
, amount DECIMAL(20,2) NOT NULL
, balance DECIMAL(20,2) NOT NULL
, FOREIGN KEY FK_wtacct_acct (account_no) REFERENCES acct (account_no)
ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB
;
My reason for using a separate "current balance" table is that there is only one row for the given account_no, and that row retains the current balance of the account.
The UPDATE statement in the trigger should obtain an exclusive lock on the row being updated. And that exclusive lock prevents any other UPDATE statement from simultaneously updating the same row. The execution of the UPDATE statement will add the `amount` from the current transaction row being inserted to the current balance.
If we were using Oracle or PostgreSQL, we could use a RETURNING clause to get the value that was assigned to the \'balance\' column.
In MySQL we can do a wonky workaround, using a user-defined variable. The new value we are going to assign to the column is first assigned to the user_defined variable, and then that is assigned to the column.
And we can assign the value of the user-defined variable to the `balance` column of the row being inserted into `wtacct`.
The purpose of this approach is to make the retrieval and update of the current balance in a single statement, to avoid any race conditions.
The UPDATE statement locates the row, obtains an exclusive (X) lock on the row, retrieves the current balance (value from the \'balance\' column), calculates the new current balance, and assigns it back to the \'balance\' column. Then continues to hold the lock until the transaction completes.
Once the trigger completes, the INSERT statement (which initially fired the trigger) proceeds, attempting to insert the new row into `wtacct`. If that fails, then all of the changes made by the INSERT statement and execution of the trigger are rolled back, keeping everything consistent.
Once a COMMIT or ROLLBACK is issued by the session, the exclusive (X) lock held on the row(s) in `acct` are released, and other sessions can obtain locks on that row in `acct`.
I have done it using Store Procedure for MySql
CREATE DEFINER=`root`#`%` PROCEDURE `example_add`(IN dr Int, IN cr Int)
BEGIN
DECLARE LID int;
Declare Balance decimal(16,2);
INSERT INTO example (Debit,Credit)
VALUES (dr, cr);
SET LID = LAST_INSERT_ID();
SET Balance = (select SUM(Debit) - SUM(Credit) as Balance from example);
UPDATE Example SET Balance = Balance WHERE ID = LID;
END
Use it example_add(10,0) or example_add(0,15) then select and see the result.
Related
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.
I have a table with 3 fields: Id(PK,AI), Name(varchar(36)), LName(varchar(36)).
I have to insert name and last name, Id inserts automatically because of it's constraints,
Is There a way to Jump id auto increment value when it reaches 6?
for instance do this 7 times:
Insert Into table(Name, LName) Values ('name1', 'lname1') "And jump id to 7 if it is going to be 6"
It may sound stupid to do this but I have the doubt.
Also Jump and do not record id 6.
record only, 1-5, 7,8,9 and so on
What I want to achieve starts from a Union:
Select * From TableNames
Union All
Select * From TableNames_general
In the TableNames_general I assign it's first value so that when the user sees the table for the first time it will be displayed the record I inserted.
The problem comes when the user inserts a new record, if the Id of the inserted record is the same as the one I have inserted it will be duplicated, that is why I want to achieve when the users inserts one record and if the last insert id already exists just jump that record. this is because I must have different ids due to its relationship among child tables.
Identity column generate values for you, And its best left this way, You have the ability to insert specific values in Identity column but its best left alone and let it generate values for you.
Imagine you have inserted a value explicitly in an identity column and then later on Identity column generates the same value for you, you will end up with duplicates.
If you want to have your input in that column then why bother with identity column anyway ??
Well this is not the best practice but you can jump to a specific number by doing as follows:
MS SQL SERVER 2005 and Later
-- Create test table
CREATE TABLE ID_TEST(ID INT IDENTITY(1,1), VALUE INT)
GO
-- Insert values
INSERT INTO ID_TEST (VALUE) VALUES
(1),(2),(3)
GO
-- Set idnentity insert on to insert values explicitly in identity column
SET IDENTITY_INSERT ID_TEST ON;
INSERT INTO ID_TEST (ID, VALUE) VALUES
(6, 6),(8,8),(9,9)
GO
-- Set identity insert off
SET IDENTITY_INSERT ID_TEST OFF;
GO
-- 1st reseed the value of identity column to any smallest value in your table
-- below I reseeded it to 0
DBCC CHECKIDENT ('ID_TEST', RESEED, 0);
-- execute the same commad without any seed value it will reset it to the
-- next highest idnetity value
DBCC CHECKIDENT ('ID_TEST', RESEED);
GO
-- final insert
INSERT INTO ID_TEST (VALUE) VALUES
(10)
GO
-- now select data from table and see the gap
SELECT * FROM ID_TEST
If you query the database to get the last inserted ID, then you can check if you need to increment it, by using a parameter in the query to set the correct ID.
If you use MSSQL, you can do the following:
Before you insert check for the current ID, if it's 5, then do the following:
Set IDENTITY_INSERT to ON
Insert your data with ID = 7
Set IDENTITY_INSERT to OFF
Also you might get away with the following scenario:
check for current ID
if it's 5, run DBCC CHECKIDENT (Table, reseed, 6), it will reseed the table and in this case your next identity will be 7
If you're checking for current identity just after INSERT, you can use SELECT ##IDENTITY or SELECT SCOPE_IDENTITY() for better results (as rcdmk pointed out in comments)
Otherwise you can just use select: SELECT MAX(Id) FROM Table
There's no direct way to influence the AUTO_INCREMENT to "skip" a particular value, or values on a particular condition.
I think you'd have to handle this in an AFTER INSERT trigger. An AFTER INSERT trigger can't update the values of the row that was just inserted, and I don't think it can make any modifications to the table affected by the statement that fired the trigger.
A BEFORE INSERT trigger won't work either, because the value assigned to an AUTO_INCREMENT column is not available in a BEFORE INSERT trigger.
I don't believe there's a way to get SQL Server IDENTITY to "skip" a particular value either.
UPDATE
If you need "unique" id values between two tables, there's a rather ugly workaround with MySQL: roll your own auto_increment behavior using triggers and a separate table. Rather than defining your tables with AUTO_INCREMENT attribute, use a BEFORE INSERT trigger to obtain a value.
If an id value is supplied, and it's larger than the current maximum value from the auto_increment column in the dummy auto_increment_seq table, we'd need to either update that row, or insert a new one.
As a rough outline:
CREATE TABLE auto_increment_seq
(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=MyISAM;
DELIMITER $$
CREATE TRIGGER TableNames_bi
BEFORE INSERT ON TableNames
FOR EACH ROW
BEGIN
DECLARE li_new_id INT UNSIGNED;
IF ( NEW.id = 0 OR NEW.id IS NULL ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NULL);
SELECT LAST_INSERT_ID() INTO li_new_id;
SET NEW.id = li_new_id;
ELSE
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END IF;
END$$
CREATE TRIGGER TableNames_ai
AFTER INSERT ON TableNames
FOR EACH ROW BEGIN
DECLARE li_max_seq INT UNSIGNED;
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END;
DELIMITER ;
The id column in the table could be defined something like this:
TableNames
( id INT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY
COMMENT 'populated from auto_increment_seq.id'
, ...
You could create an identical trigger for the other table as well, so the two tables are effectively sharing the same auto_increment sequence. (With less efficiency and concurrency than an Oracle SEQUENCE object would provide.)
IMPORTANT NOTES
This doesn't really insure that the id values between the tables are actually kept unique. That would really require a query of the other table to see if the id value exists or not; and if running with InnoDB engine, in the context of some transaction isolation levels, we might be querying a stale (as in, consistent from the point in time at the start of the transaction) version of the other table.
And absent some additional (concurrency killing) locking, the approach outline above is subject to a small window of opportunity for a "race" condition with concurrent inserts... the SELECT MAX() from the dummy seq table, followed by the INSERT, allows a small window for another transaction to also run a SELECT MAX(), and return the same value. The best we can hope for (I think) is for an error to be thrown due to a duplicate key exception.
This approach requires the dummy "seq" table to use the MyISAM engine, so we can get an Oracle-like AUTONOMOUS TRANSACTION behavior; if inserts to the real tables are performed in the context of a REPEATABLE READ or SERIALIZABLE transaction isolation level, reads of the MAX(id) from the seq table would be consistent from the snapshot at the beginning of the transaction, we wouldn't get the newly inserted (or updated) values.
We'd also really need to consider the edge case of an UPDATE of row changing the id value; to handle that case, we'd need BEFORE/AFTER UPDATE triggers as well.
This question already has answers here:
Check if a row exists, otherwise insert
(12 answers)
MySQL Conditional Insert
(13 answers)
Closed 9 years ago.
I want to run a set of queries to insert some data into an SQL table but only if the record satisfying certain criteria are met. The table has 4 fields: id (primary), fund_id, date and price
I have 3 fields in the query: fund_id, date and price.
So my query would go something like this:
INSERT INTO funds (fund_id, date, price)
VALUES (23, '2013-02-12', 22.43)
WHERE NOT EXISTS (
SELECT *
FROM funds
WHERE fund_id = 23
AND date = '2013-02-12'
);
So I only want to insert the data if a record matching the fund_id and date does not already exist. If the above is correct it strikes me as quite an inefficient way of achieving this as an additional select statement must be run each time.
Is there a better way of achieving the above?
Edit: For clarification neither fund_id nor date are unique fields; records sharing the same fund_id or date will exist but no record should have both the same fund_id and date as another.
This might be a simple solution to achieve this:
INSERT INTO funds (ID, date, price)
SELECT 23, DATE('2013-02-12'), 22.5
FROM dual
WHERE NOT EXISTS (SELECT 1
FROM funds
WHERE ID = 23
AND date = DATE('2013-02-12'));
p.s. alternatively (if ID a primary key):
INSERT INTO funds (ID, date, price)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE ID = 23; -- or whatever you need
see this Fiddle.
Although the answer I originally marked as chosen is correct and achieves what I asked there is a better way of doing this (which others acknowledged but didn't go into). A composite unique index should be created on the table consisting of fund_id and date.
ALTER TABLE funds ADD UNIQUE KEY `fund_date` (`fund_id`, `date`);
Then when inserting a record add the condition when a conflict is encountered:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = `price`; --this keeps the price what it was (no change to the table) or:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = 22.5; --this updates the price to the new value
This will provide much better performance to a sub-query and the structure of the table is superior. It comes with the caveat that you can't have NULL values in your unique key columns as they are still treated as values by MySQL.
Assuming you cannot modify DDL (to create a unique constraint) or are limited to only being able to write DML then check for a null on filtered result of your values against the whole table
FIDDLE
insert into funds (ID, date, price)
select
T.*
from
(select 23 ID, '2013-02-12' date, 22.43 price) T
left join
funds on funds.ID = T.ID and funds.date = T.date
where
funds.ID is null
I have a table which goes somewhat as follows:
Name1 (Key)|Name2(Key)|Total|LastUpdate
There will be hundreds of thousands of such records.
Now very frequently, my main program will query a source to get updated values. It will then update the total and last update. It may have to update hundreds of such rows.
Currently I have this:
Update mytable SET Total=[...] AND LastUpdate=[....] WHERE Name1='x' AND Name2='y';
Update mytable SET .....
I'm wondering if there is a faster way of updating the rows (similar to how you can Insert multiple rows at the same time).
The Totals will be completely different, but the LastUpdate time will be the same for each Update.
Update to Clarify:
The changes to total aren't just an increment, and don't depend on the current value - so its not deterministic in that regard. The source provides me a new value every second or so, and I have to put in a new one.
if its to slow to do the updates separatly, mayby you can do somthing like this
CREATE TABLE mytable_updates
(
Name1 VARCHAR(50) NOT NULL,
Name2 VARCHAR(50) NOT NULL,
total_increase INT NOT NULL
PRIMARY KEY(Name1, Name2)
)
INSERT INTO mytable_updates VALUES ('x', 'y', 5), ('x', 'z', 3);
...
UPDATE mytable_updates
LEFT JOIN mytable USING (Name1, Name2)
SET
Total = Total + total_increase,
LastUpdate = NOW();
I have a scenario like this:
There are two tables table1 and table2. The table1 has a primary key pkey and table2 has a foreign key fkey now during an insert if the foreign key is provided the value should be inserted as it is. Otherwise, it has to get a primary key from table1 using some computation and determine the foreign key to be inserted. How do i do this??
I am using MySql 5.0
EDIT
In my scenario, table1 holds the billing details, that is, the table1 has the bills and the total amount to be paid. The customer pays some amount of the total outstanding balance or will pay for a particular bill. What i want to do here is. When i am not provided with a bill_id (which is primary key in table1 and foreign key in table2) i would like to search for the oldest bill that is due in table1 and deduct the amount due and further deduct the remaining amount if any from the next bill in the billed order. I want to do this in the database layer rather than the upper layer. So when an insert is being done without a value for the foreign key the value should be retrieved and placed by the trigger or otherwise directly inserted. How do i achieve this?
Using the answers provided here, i tried this:
CREATE DEFINER=`root`#`localhost` TRIGGER `inflow_pay_done_insert` BEFORE INSERT ON `inflow_pay_done` FOR EACH ROW BEGIN
DECLARE pkey INT;
SET pkey = (SELECT bill_id from inflow_bills where payment_stat = 0 and rs_id = NEW.rs_id order by time_stamp limit 1);
SET NEW.bill_id = IF(NEW.bill_id , NEW.bill_id , pkey);
UPDATE raw_mat_sup rms SET rms.outstanding_bal_payable = rms.outstanding_bal_payable - NEW.amount where rms.rs_id = NEW.rs_id;
END|
and i am getting the following error when i am trying to insert in inflow_pay_done:
/* SQL Error (1048): Column 'bill_id' cannot be null */
you could use a subquery in the BEFORE INSERT trigger for this..
DELIMITER |
DROP TRIGGER `inflow_pay_done_insert`|
CREATE TRIGGER `inflow_pay_done_insert` BEFORE INSERT ON `inflow_pay_done`
FOR EACH ROW
BEGIN
UPDATE raw_mat_sup rms
SET rms.outstanding_bal_payable = rms.outstanding_bal_payable - NEW.amount
WHERE rms.rs_id = NEW.rs_id;
NEW.bill_id = IF(NEW.bill_id,
/* if "bill_id" is provided in INSERT statement, use provided value */
NEW.bill_id,
/* if not, query other table for the correct value */
( /* this subquery is just an example, put your own query here*/
SELECT bill_id FROM inflow_bills
/* find customers newest bill based on newest date and customer id */
WHERE payment_stat = 0 AND rs_id = NEW.rs_id
ORDER BY time_stamp DESC LIMIT 1
)
);
END;
|
delimiter;
UPDATE
Because of a MySQL Bug, this will only work when the column is allowed to be NULL and there is no constraint on the column (-> foreign key). The reason is that MySQL, unlike other DBMS, checks for constraints before a BEFORE INSERT trigger is executed and effectively avoids the execution of the trigger which would correct the data to insert.
The only solution for this, until the behaviour of MySQL changes, is to use a STORED PROCEDURE instead of plain INSERT. The stored procedure is then called with the values that should be inserted. In the procedure, the data correction (like in this case: selecting the right bill_id) is done and then INSERT is executed from within the stored procedure.
UPDATE II
This bug seems to be fixed in 5.7.1. Changelog says:
If a column is declared as NOT NULL, it is not permitted to insert
NULL into the column or update it to NULL. However, this constraint
was enforced even if there was a BEFORE INSERT (or BEFORE UPDATE
trigger) that set the column to a non-NULL value. Now the constraint
is checked at the end of the statement, per the SQL standard.