Transaction doesn't fail even if one of operations fails - mysql

So I was playing with transactions, and I have tried to subtract the funds from one transfer it to another. As you can see from the picture, the first update query wasn't successful...Unlike the second one, which executed successfully. Now, what I was expecting is, that when I hit the commit, I wouldn't see any changes. But that wasn't the case. Also, I have use START TRANSACTION (it implicitly sets autocommit to 0), rather than BEGIN command.
Here is the output of this:
What I am missing here?

I don't understand your confusion. Both your updates succeeded. The first one happened to not affect any rows, so only the second one actually changed the data.
You committed the transaction, so all changes take effect.
If you wanted to test transactions, roll back the transaction. Then, when you look at the data, you'll see that nothing changed.

Not any of your operations has failed.
In first update, where conditions were not satisfied and hence no any
row was updated.
In second one, where condition was satisfied for one record, hence that one record was updated.

As an addition to Gordon's answer and his mentioning of stored procedure, I will add an answer just for the future readers and for the completeness, because my real issue was how to rollback a transaction if some condition is not satisfied:
DELIMITER //
CREATE PROCEDURE transfer(IN sender INT, IN receiver INT)
BEGIN
START TRANSACTION;
SET #senderBalance = (SELECT balance FROM bank_acc WHERE acctnum = sender LIMIT 1);
select #senderBalance;
IF (#senderBalance < 50) THEN
ROLLBACK;
ELSE
update bank_acc set balance = balance - 50 where acctnum = sender;
update bank_acc set balance = balance + 50 where acctnum = receiver;
COMMIT;
END IF;
END//
DELIMITER ;
Later, you can use it like this:
call transfer(#sender := 20, #receiver := 10);

Related

Transactions within MySql Stored Procedures

I am trying to use a transaction within a MySQL Stored Procedure.
Specifically, update a user table with amended data from a temporary record. from another table.
then once transferred, delete the temporary record.
I have created the code below, which when executed returns the string "transaction has succeeded".
However, nothing is actually updated and the temporary record is Not deleted.
Both SQL statements, when executed separately work Just fine, the first one does the update, the second does the delete.
Can anyone enlighten me as to what may be wrong?
BEGIN
-- set a default response
DECLARE response varchar(48) DEFAULT "the transaction has failed.";
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
-- set vars
SET response = "the transaction has failed, you may have already updated the account.";
select response;
END;
START TRANSACTION;
-- we are inserting data, using information from another table
update user, updateUserNamesAndNumbers
SET user.firstName = updateUserNamesAndNumbers.firstName,
user.lastName = updateUserNamesAndNumbers.lastName,
user.landline = updateUserNamesAndNumbers.landline,
user.mobile = updateUserNamesAndNumbers.mobile
WHERE
updateUserNamesAndNumbers.uuid = transferCode
AND
updateUserNamesAndNumbers.userId= user.user_id
;
-- finally delete the original tuple
DELETE from updateUserNamesAndNumbers
where uuid= transferCode ;
SET response="The transaction has succeeded";
COMMIT;
SELECT response;
END
Change the implicit join to an explicit join
update user join updateUserNamesAndNumbers on updateUserNamesAndNumbers.uuid = transferCode
I've Partially answered my own Question.
Thanks to P.Salmon, for Querying the transferCode variable.
It turns out I had defined the string as varchar(24), but the input was actually bigger than that.
So once I sorted that, the code worked, but only the first time.
I still need to have a really good think about this, as a second call to the same routine with the same transferCode input, where the temporary tuple had already been deleted by the first call, does not throw the MySQL Exception, as I thought it should. So "its Still Thinking Cap On Time"

SQL Trigger fallback

My bosses nor any of the DBAs know how to make triggers and I don't neither(new-ish programmer), they just copied/pasted some old triggers from the DB for examples for me. Anyway, my triggers copy data from one table to another after an update/insert. The insert/update that goes to the original table is vital, so if anything fails, I just want the trigger to fail and the original insert/update to still run just fine.
I am using MySQL to test, but we use DB2, but they won't give me access to test triggers in their DB2 environemt, so this the closest solution I can use to test the trigger logic.
I noticed that BEGIN ATOMIC is in the example triggers, does that do what I want? And what would be equivalent in MySQL, so I can test?
I have subselects in my triggers are these safe? Should I declare variables to help avoid issues?
--#SET TERMINATOR #
create table test_trigger (i int) in userspace1#
create table test_trigger_copy (i int) in userspace1#
create or replace trigger test_trigger_air
after insert on test_trigger
referencing new as n
for each row
begin
declare continue handler for sqlexception begin end;
insert into test_trigger_copy(i) values (case when mod (n.i, 2)=0 then cast(RAISE_ERROR('70001', 'No even numbers!') as int) else n.i end);
end#
insert into test_trigger(i) values 1, 2, 3#
select * from test_trigger_copy#
select * from test_trigger#
For DB2 (for LUW at least) databases.
The INSERT statement inside the trigger generates an exception when you try to insert an even number into the base table. The CONTINUE handler consumes exceptions, so you get all inserted numbers in the base table, but only odd numbers in the copy table.
A close MySQL equivalent to DB2's BEGIN ATOMIC, would be a combination of START TRANSACTION, COMMIT, EXIT HANDLER, and ROLLBACK. Instead of:
BEGIN ATOMIC
<statement(s)>
END
one could do the following in MySQL:
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
<statement(s)>
COMMIT;
END
As to subselects (you mean subqueries?) in the trigger, I do not see why they would not work, as long as you observe the restrictions.

TRY/CATCH not working when table goes missing

I can't seem to get this to work... The result is SQL Management Studio seems to hang, gives me the message
Msg 208, Level 16, State 1, Line 76 Invalid object name
If I try to close the code window, I get a warning that the transaction isn't committed, and asks if I would like to commit it. If I do, the truncate has happened, and the items are missing.
I'm trying to make sure the truncate does NOT happen or gets rolled back if the table in the "INSERT" statement is missing.
BEGIN TRY
BEGIN TRAN
TRUNCATE TABLE X
INSERT INTO TABLE X ([values...]) -- pseudo code; insert works fine if table is present
SELECT * FROM <potentially missing table>
COMMIT TRAN
END TRY
BEGIN CATCH
if (##TRANCOUNT > 0)
ROLLBACK
END CATCH
Based on the information provided it looks like it may be a problem with your syntax, but it is unclear without a CREATE TABLE statement and some working code. It could also be that you're not checking if the table exists before the SELECT. I just tested the below and it has the desired results.
BEGIN TRY
BEGIN TRAN
TRUNCATE TABLE [test_table]
INSERT INTO [test_table] VALUES ('...')
SELECT * FROM [test_table]
COMMIT
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
PRINT N'rolling back transaction' --for confirmation of the catch
ROLLBACK
END CATCH
Or to avoid the TRY/CATCH use IF EXISTS to check if the table exists before starting anything.
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'test_table')
BEGIN
BEGIN TRAN
TRUNCATE TABLE [test_table]
INSERT INTO [test_table] VALUES ('...')
SELECT * FROM [test_table]
COMMIT
END
ELSE
BEGIN
-- do something else
PRINT N'The table does not exist'
END
Hope this helps!
2020-10-11:
I tried SET XACT_ABORT ON, prior to engaging a transaction.
Interestingly, when encountering a missing table (e.g. at SELECT stmt), the script STILL halts without dropping into the TRY/CATCH block.
Moreover, and actually worse, all prior SQL output is no longer available.
Notwithstanding, the transaction was rolled back, as there is no open Transaction, thereafter.
PROOF:
executing "select ##TRANCOUNT" returns 0
manually executing "ROLLBACK TRAN" states: "The ROLLBACK TRANSACTION
request has no corresponding BEGIN TRANSACTION."
I, therefore, decided to check for required tables, at script start/prior to engaging a Transaction, instead of using XACT_ABORT.

Interrupt a trigger execution without a box message (tsql)

I am writing a trigger for controlling a column. The script works as I want but my problem is in the raiseerror. I want the trigger to work without showing the error message to the user.
Can anyone who knows what is the equivalent of raiseerror without showing the error message to the user?
I tried with rollback transaction which gve me another error message instead, and I tried with return which did not interrupt the execution of the trigger.
This is my trigger:
DECLARE #val varchar(9)
SELECT #val= [DC_Piece]
from INSERTED
where INSERTED [DC_Domaine]=0 and INSERTED.[DC_IdCol]=6
IF UPDATE([DC_Piece])
BEGIN
IF NOT EXISTS( select [DO_PIECE]
from DOCEN
where #val= [DO_Piece] and [DO_Domaine]=0 and [DO_Type]=6)
RAISERROR('STOP',11,1)
END
Please help me
You need to completely rewrite your trigger to take into account that it will be called once per statement (NOT per row!) and the Inserted and Deleted pseudo tables can contain multiple rows which you should consider.
So try something like this:
CREATE TRIGGER trg_abort_insert
ON dbo.YourTableNameHere
AFTER UPDATE
AS
-- check if any of the DC_Piece columns have been updated
IF EXISTS (SELECT *
FROM Inserted i
INNER JOIN Deleted d ON i.PrimaryKey = d.PrimaryKey -- link the two pseudo tables on primary key
WHERE i.DC_Piece <> d.DC_Piece -- DC_Piece has changed
AND i.DC_Domaine = 0
AND i.DC_IdCol = 6)
-- if your conditions are met --> just roll back the transaction
-- nothing will be stored, no message is shown to the user
ROLLBACK TRANSACTION
END

Rolling Back A Transaction In T-SQL After Code Has Been Executed

I am trying to find a method to rollback a transaction after it has been executed.
For example:
DECLARE #tsubaki VARCHAR(25);
SET #tsubaki = 'A Transaction';
BEGIN TRANSACTION #tsubaki
UPDATE dbo.Maka SET id = 400, name = 'inu' --notice I didn't put there where clause
COMMIT TRANSACTION
Later on I realize that I updated everything in the databse Maka instead of just the one record I originally intended.
Now I try to write code to roll it back before the update:
DECLARE #tsubaki VARCHAR(25);
SET #tsubaki = 'A Transaction';
ROLLBACK TRANSACTION #tsubaki;
Doesn't work. Bottom line: I am looking for a way to rollback a sql transaction in MS-SQL Server 2008 after the transaction has been commit and the sql has ran.
Thanks in advance.
You can't do that from T-SQL code. You will have to restore to a point in time from the log file. Note that the restore will "undo" everything to a point in time, including your transaction.
In the future you should ALWAYS back up your db before any manual update, small or large. You can also cover yourself with a little trick. Write out your update/delete code like this:
SELECT * FROM dbo.Maka
-- UPDATE dbo.Maka SET id = 400, name = 'inu'
WHERE some_identifier = some_value
Run the SELECT version first which is innocuous and when you can verify the record(s) to be updated select the code from the WHERE clause up to the UPDATE and run it.