I am trying to understand partial roll backs, in TSQL. Following a tutorial i found this example but I do not understand / find can you save to a table the second object if the first fails?
BEGIN TRAN
PRINT 'First Transaction: ' + CONVERT(VARCHAR,##TRANCOUNT)
INSERT INTO People VALUES ('Tom')
BEGIN TRAN
PRINT 'Second Transaction: ' + CONVERT(VARCHAR,##TRANCOUNT)
INSERT INTO People VALUES ('Dick')
ROLLBACK TRAN
PRINT 'Rollback: ' + CONVERT(VARCHAR,##TRANCOUNT)
So if 'Tom' fails can I save 'Dick' ?
I need this, for achieving a many-to-many roll back transaction. So i create Obj A, Obj B and if both succes add Obj C, if not roll back failure, and keep the successing INSERT query.
It looks like you just want to commit the transactions as they succeed. The code below keeps Tom, even if your script chokes on Dick...
CREATE TABLE #People
(
VAL VARCHAR(100)
);
DECLARE #stop BIT = 0;
BEGIN TRY
BEGIN TRAN
PRINT 'First Transaction: ' + CONVERT(VARCHAR,##TRANCOUNT)
INSERT INTO #People VALUES ('Tom')
COMMIT
END TRY
BEGIN CATCH
ROLLBACK TRAN;
SET #stop = 1;
END CATCH;
SELECT * FROM #People;
IF #stop = 0
BEGIN TRY
BEGIN TRAN
PRINT 'Second Transaction: ' + CONVERT(VARCHAR,##TRANCOUNT)
INSERT INTO #People VALUES ('Dick');
SELECT * FROM #People;
RAISERROR(
'Choke on Dick' -- stop snickering
, 16
, 1
);
COMMIT;
END TRY
BEGIN CATCH
PRINT 'Rollback: ' + CONVERT(VARCHAR,##TRANCOUNT)
ROLLBACK TRAN
END CATCH
SELECT * FROM #People;
DROP TABLE #People;
Related
I have a cursor, inside a proc that is run from a db we use to store utilities. I need the cursor to dynamically change databases before is does the FETCH NEXT FROM /INTO #; part so that it loops through the correct rows in the correct database.
CREATE OR ALTER PROCEDURE utils.post_cohesity_restore
AS
Begin
/* PURPOSE: re-attaches orphaned users and grants permissions after a Cohesity restore */
SET NOCOUNT ON;
DECLARE
#juris char(20),
#sql char(400),
#sql_A char(400),
#database sysname,
#users char(20);
SELECT
#database = destination_database_name
FROM
[msdb].[dbo].[restorehistory]
WHERE
restore_date >= dateadd(DAY,-3,getDate())
AND (LOWER(destination_database_name) LIKE '%clientdata%'
OR LOWER(destination_database_name) LIKE '%analysis%'
OR LOWER(destination_database_name) LIKE '%migration%')
SELECT
#juris = LEFT(#database,
CASE
WHEN CHARINDEX('ClientData', #database) <> 0
THEN (CHARINDEX('ClientData', #database)-1)
WHEN CHARINDEX('Migration', #database) <> 0
THEN (CHARINDEX('Migration', #database)-1)
WHEN CHARINDEX('Analysis', #database) <> 0
THEN (CHARINDEX('Analysis', #database)-1)
END)
DECLARE users_cursor CURSOR
FOR SELECT
dp.[name] as username
FROM sys.database_principals AS dp
LEFT JOIN sys.server_principals AS sp
ON dp.[name] = sp.[name]
WHERE
sp.sid <> dp.sid;
OPEN users_cursor;
FETCH NEXT FROM users_cursor INTO
#users;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = ('USE ' + #database
+ '; ALTER USER ' + rtrim(#users) + ' WITH LOGIN = ' + rtrim(#users))
PRINT #sql; -- for testing/seeing the EXEC below
EXEC (#sql);
FETCH NEXT FROM users_cursor INTO
#users;
END;
CLOSE users_cursor;
DEALLOCATE users_cursor;
SET #sql_A = ('USE ' + #database
+ '; GRANT EXECUTE TO [analyst]; GRANT VIEW DEFINITION TO [analyst];'
+ ' ALTER ROLE [ddladmin] ADD MEMBER [analyst]; ALTER ROLE [datawriter] ADD MEMBER [analyst];')
PRINT #sql_A; -- for testing/seeing the EXEC below
EXEC (#sql_A );
END
I need to switch to the db that populates #database right around this point so that the users end up being form that database.
DECLARE users_cursor CURSOR
FOR SELECT
dp.[name] as username
FROM sys.database_principals AS dp
LEFT JOIN sys.server_principals AS sp
ON dp.[name] = sp.[name]
WHERE
sp.sid <> dp.sid;
OPEN users_cursor;
FETCH NEXT FROM users_cursor INTO
#users;
Any thoughts? It is even possible?
I ended up saving an edited version of the proc above in each database then creating an SSA job that a)identifies the database and b)calls that database's version of the proc.
I have a following situation.
I have a table with trigger for insert.
When I insert a row in it, from this trigger I want to insert some rows into a second table.
For each of these rows I want to do it in it's own transaction in case something go wrong.
I want to have original row in first table and all rows (these withous errors) in the second.
A little code to reproduce:
create table test(id int primary key identity(1,1),name nvarchar(10))
create table test2(id int primary key identity(1,1),
state char(1) check (state in ('a','b')))
go
create trigger test_trigger on test for insert
as
begin
declare #char char(1)
declare curs cursor for (
select 'a'
union
select 'c'
union
select 'b')
open curs
fetch next from curs into #char
while ##FETCH_STATUS = 0
begin
begin try
begin transaction
insert into test2(state)
select #char
commit
end try
begin catch
print 'catch block'
rollback
end catch
fetch next from curs into #char
end
close curs
deallocate curs
end
go
insert into test(name) values('test')
select * from test
select * from test2
go
drop table test
drop table test2
So for the sample data from this snippet, I want to have a row with 'test' in test table and two rows in the test2 table ('a' and 'b').
How can I write a code for that?
Looks like finally I got it to work.
Corrected trigger code:
create trigger test_trigger on test for insert
as
begin
declare #char char(1)
set xact_abort off
declare curs cursor for (
select 'a'
union
select 'c'
union
select 'b')
open curs
fetch next from curs into #char
while ##FETCH_STATUS = 0
begin
save transaction t
begin try
insert into test2(state)
select #char
end try
begin catch
print 'catch block'
rollback transaction t
end catch
fetch next from curs into #char
end
close curs
deallocate curs
end
I trying to open the transaction and then delete one record now i need to insert the deleted record into event table. The problem is i can't see the result because it has been deleted.
CREATE procedure [dbo].[TestData] ( #clientid bigint ) As
Begin
print ''abc''
insert into Client_Event_Log values ( getdate(),0,#clientid,100,''B0AE3162-671C-E211-AF2A-00155D051024'',NULL)
BEGIN TRY
BEGIN TRAN
Delete from access_types -- There is only record in the table.
8559230 abc 101 0 2010-01-01 10:25:25.000
select * from access_types -- cann't see the deleted record even before the session.
DECLARE #cGTAEventLog bigint
select #cGTAEventLog=Access_Type_Id from access_types
exec TestData #cGTAEventLog -- Now i am passing 8559230 to the SP to insert into event
table but it has been delete before so can't insert NULL
Commit Tran
END TRY
BEGIN CATCH
ROLLBACK TRAN
--Error message
PRINT 'Error: ' +
CONVERT(VARCHAR,ERROR_NUMBER()) + ' - ' +
CONVERT(VARCHAR,ERROR_SEVERITY()) + ' - ' +
CONVERT(VARCHAR,ERROR_STATE()) + ' - ' +
ERROR_MESSAGE() +
' Raise Error occurred at line ' + CONVERT(VARCHAR,ERROR_LINE())
END CATCH
END
I need to find a way to access the data after deleting so i can insert into a event table.
Assuming you are on SQL Server 2005 or later, you can use the output clause to access the virtual 'deleted' table and park its contents in a table variable. Something like the following
DECLARE #TmpTable TABLE (ID INT, ......)
DELETE
FROM access_types
OUTPUT Deleted.ID,... INTO #TmpTable
select * from #TmpTable
When I try to run the following SQL snippet inside a cursor loop,
set #cmd = N'exec sp_rename ' + #test + N',' +
RIGHT(#test,LEN(#test)-3) + '_Pct' + N',''COLUMN'''
I get the following message,
Msg 15248, Level 11, State 1, Procedure sp_rename, Line 213
Either the parameter #objname is ambiguous or the claimed #objtype (COLUMN) is wrong.
What is wrong and how do I fix it ? I tried wrapping the column name in brackets [], and double quotes "" like some of the search results suggested.
Edit 1 -
Here is the entire script. How do I pass the table name to the rename sp ? I'm not sure how to do that since the column names are in one of many tables.
BEGIN TRANSACTION
declare #cnt int
declare #test nvarchar(128)
declare #cmd nvarchar(500)
declare Tests cursor for
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME LIKE 'pct%' AND TABLE_NAME LIKE 'TestData%'
open Tests
fetch next from Tests into #test
while ##fetch_status = 0
BEGIN
set #cmd = N'exec sp_rename ' + #test + N',' + RIGHT(#test,LEN(#test)-3) + '_Pct' + N', column'
print #cmd
EXEC sp_executeSQL #cmd
fetch next from Tests into #test
END
close Tests
deallocate Tests
ROLLBACK TRANSACTION
--COMMIT TRANSACTION
Edit 2 -
The script is designed to rename columns whose names match a pattern, in this case with a "pct" prefix. The columns occur in a variety of tables within the database. All table names are prefixed with "TestData".
Here is slightly modified version. Changes are noted as code commentary.
BEGIN TRANSACTION
declare #cnt int
declare #test nvarchar(128)
-- variable to hold table name
declare #tableName nvarchar(255)
declare #cmd nvarchar(500)
-- local means the cursor name is private to this code
-- fast_forward enables some speed optimizations
declare Tests cursor local fast_forward for
SELECT COLUMN_NAME, TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE 'pct%'
AND TABLE_NAME LIKE 'TestData%'
open Tests
-- Instead of fetching twice, I rather set up no-exit loop
while 1 = 1
BEGIN
-- And then fetch
fetch next from Tests into #test, #tableName
-- And then, if no row is fetched, exit the loop
if ##fetch_status <> 0
begin
break
end
-- Quotename is needed if you ever use special characters
-- in table/column names. Spaces, reserved words etc.
-- Other changes add apostrophes at right places.
set #cmd = N'exec sp_rename '''
+ quotename(#tableName)
+ '.'
+ quotename(#test)
+ N''','''
+ RIGHT(#test,LEN(#test)-3)
+ '_Pct'''
+ N', ''column'''
print #cmd
EXEC sp_executeSQL #cmd
END
close Tests
deallocate Tests
ROLLBACK TRANSACTION
--COMMIT TRANSACTION
Sup guys, is it possible for INSERT or UPDATE to throw an exception that would stop the procedure? I'm in a little bit of a pickle, because i've hanging transactions in what seems to be a bullet proof code.
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
SET #nError = ##ERROR;
IF (1 = #nChangeMassprintTaskStatus) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF 0 = #nError
BEGIN
COMMIT TRANSACTION;
END
ELSE
BEGIN
ROLLBACK TRANSACTION;
END
Halp, anyone?
Without TRY/CATCH, this is not bullet proof.
Errors can be batch aborting (eg datatype conversions or errors thrown from triggers) which means ROLLBACK does not run.
You have to use TRY/CATCH and I always use SET XACT_ABORT ON too
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
IF (1 = #nChangeMassprintTaskStatus)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 --may already be rolled back by SET XACT_ABORT or a trigger
ROLLBACK TRANSACTION;
RAISERROR [rethrow caught error using ERROR_NUMBER(), ERROR_MESSAGE(), etc]
END CATCH
Mandatory background reading is Erland Sommarskog's "Error Handling in SQL 2005 and Later": we'll test you on it later...
Create a trigger that will raise an exception if insert/update is not correct
example:
create table t (id int)
go
create trigger tr on t
for insert
as
if exists(select 1 from inserted where id = 0)
raiserror('id is not valid', 16, 1)
go
insert t select 1
select ##error
insert t select 0
select ##error