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

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.

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"

Transaction doesn't fail even if one of operations fails

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);

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.

Multiple statements Delphi TZquery (Zeos) error

im trying to make a multiple statement query like this :
// without the second insert the query works fine.
// i need 2 querys to work because later, i'll do inserts on different kind of tables.
// that's why i need 2 querys, not a single query which insert 2 records.
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
i got this error message : SQL Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insert into client (name,age) values ('doe','21')' at line 2;
i already check the manual, The components TZQuery and TZUpdateSql (from zeos lib ) provide the possibility to execute multiple statements, internally.
EDIT [SOLVED]
Thank you GregD, after run several tests, transactions works fine for me!
that's how i use, to help others in the future.
try
ZConnection.AutoCommit := True;
ZConnection.StartTransaction;
With ZQuery Do
begin
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+')');
ExecSQL;
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+')');
ExecSQL;
end;
ZConnection.Commit;
except
ZConnection.Rollback
end;
This is how AutoCommit property actually works in Zeos:
when AutoCommit is True, then the transactions are commited automatically after each executed SQL statement, but you can use the StartTransaction command explicitely to prevent this auto commiting, until you explicitely call Commit.
when AutoCommit is False, you should not call StartTransaction. Then the transaction is started automatically, but it will not commit automatically after every executed statement.
procedure StartTransaction The StartTransaction procedure starts a new transaction within the connected database. It should be only used when AutoCommit property is TRUE. Whenever you try to call it with AutoCommit set to false, an SInvalidOpInNonAutoCommit will be raised. This behaviour is expected, as StartTransaction should be used as a escape to the AutoCommit mode. When you call StartTransaction, the AutoCommit is "turned off", and then, when you call Commit or Rollback, the AutoCommit is "turned on" again. If you're working with AutoCommit set to false, new transactions are created automatically and you choose how you will close them (Commit or Rollback).
procedure Commit Commit current statements to the database. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. Commiting finishes the current transaction, if there's any. If you don't want to save your satatements to the database, you should use the Rollback procedure.
procedure Rollback Rolls back all previous statements in current transaction. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. The Rollback finishes the current transaction, if there's any. If you don't want to loose your satatements, you should use the Commit procedure.
I have no idea about Zeos and multiple statements, but that's not really the problem here. You've caused a major security issue with your query (SQL injection) and a slow method of performing them at all (concatenated strings that can't be cached and reused).
If you properly stop using string concatenation to form your queries, and use parameterized statements instead, you don't need to worry about the multiple statements at all:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age)');
SQL.Add('values (:Name, :Age);'
ParamByName('Name').AsString := 'John';
ParamByName('Age').AsInteger := 20;
ExecSQL;
ParamByName('Name').AsString := 'Doe';
ParamByName('Age').AsInteger :- 21;
ExecSQL;
end;
The query will now run faster (because the DBMS can compile it once and reuse it multiple times (the "caching" I mentioned), you don't have the SQL injection risk any longer, and the multiple statements are no longer needed.
Try this code and let us know if the same problem arises:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+'),('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
This way you can also speed up the MySQL handling of this INSERT query, as it does in one batch and not twice.
EDIT #1:
I'm not an expert in Zeos, but with other languages, you could try to execute the query one by one:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
ExecSQL;
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
EDIT #2: Transactions
One question on Stackoverflow has many good examples about using transactions in MySQL. Although, the examples are written for PHP, I'm sure you could find some good pointers there. Make sure that your tables on the MySQL server are InnoDB not MyISAM.
I'm not an expert in ZEOS either, but looking at the source, have you set MultiStatements property of TZUpdateSQL to true?
Have you tried TZSQLProcessor? Said that the component was made for such needs ( as in ZSqlProcessor.pas unit):
{**
Implements a unidatabase component which parses and executes SQL Scripts.
}

Variable persistence inside SQL server transactions

I've got a large T-SQL script that opens and closes a few transactions. I have read that committing a batch with a GO statement effectively clears all variables out from it's scope.
Given the script below, will #MyImportantVariable be defined after the transaction is committed?
Is this an issue, if so, how do I overcome it?
DECLARE #MyImportantVariable INT;
SET #MyImportantVariable = 42;
DECLARE #Counter INT;
SET #Counter = 0
DECLARE UpdateGarmentCursor CURSOR FAST_FORWARD
FOR
SELECT
MyColumn
FROM
MyWonderfulTable
BEGIN TRANSACTION
WHILE ##TRAN_STATUS = 0
BEGIN
-- Do interesting work in here
SET #Counter = #Counter +1
IF(#Counter>10)
BEGIN
COMMIT TRANSACTION
-- What happens here to #MyImportantVariable?
SET #Counter = 0
BEGIN TRANSACTION
END
END
-- Close the transaction one last time
COMMIT TRANSACTION
The variable will still exist.
Your example contains no GO commands, only transactions.
GO signals the end of a batch... batches and transactions are two different things entirely.
From MSDN:
GO is not a Transact-SQL statement; it is a command recognized by the
sqlcmd and osql utilities and SQL Server Management Studio Code
editor.
SQL Server utilities interpret GO as a signal that they should send
the current batch of Transact-SQL statements to an instance of SQL
Server. The current batch of statements is composed of all statements
entered since the last GO, or since the start of the ad hoc session or
script if this is the first GO.