rollback a nested transaction in trigger - triggers

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

Related

Trigger after update: How to execute query for every row updated

I wrote trigger to execute a query after the update of the column (retard) in my table, but sometimes there are many rows updated how to solve that?
CREATE OR ALTER TRIGGER notifRetard
ON Taches
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #value INT
IF UPDATE(retard)
-- How to make this for every row updated???
SELECT
#value = inserted.retard
FROM
inserted;
IF #value = 1
-- run SQL query
END
The solution if someone else need it is to use CURSOR.
CREATE or alter TRIGGER notifRetard
ON Taches
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE(retard)
begin
DECLARE #RefTache varchar(50),#RefPhase numeric(4,0),#IDprojet varchar(50),#IDressource varchar(50) #retard bit;
DECLARE TrigTempUpdate_Cursor CURSOR FOR
SELECt RefTache,RefPhase,IDprojet,IDressource,retard
FROM
inserted;
begin
OPEN TrigTempUpdate_Cursor;
FETCH NEXT FROM TrigTempUpdate_Cursor INTO #RefTache, #RefPhase,#IDprojet,#IDressource,#retard
WHILE ##FETCH_STATUS = 0
BEGIN
if #retard=1
--DO QUERY HERE
FETCH NEXT FROM TrigTempUpdate_Cursor INTO #RefTache, #RefPhase,#IDprojet,#IDressource,#retard
END;
end;
CLOSE TrigTempUpdate_Cursor;
DEALLOCATE TrigTempUpdate_Cursor;
end;
end;

How to use a cursor inside a loop in a PostgreSQL script

I want to make a script for a PostgreSQL database which reads a table and prints its content. I have used the following code
declare myCursor cursor for
select col1, col2 from tab1;
begin
loop
DBMS_OUTPUT.PUT_LINE(myCursor .col1+" "+myCursor.col2);
end loop;
end;
But it doesn't work.
Eventually I have found out:
do $$
declare myCursor cursor for
select col1, col2 from tab1;
begin
for myIterator in myCursor loop
raise notice '% %', myIterator.col1, myIterator.col2;
end loop;
end $$;

Some error about cursor in Pl/pgsql

I know the right way to insert value from a table:
insert into city (pop) select pop+2 from city
I just want to know how cursor works in Pl/pgsql .
I wish to use cursor in loop to insert some value:
create or replace function test( ) returns void as
$$
declare
cur cursor for select pop+2 from city order by pop;
b int;
row record;
begin
for row in cur
LOOP
fetch cur into b;
insert into city(pop) select b;
end loop;
end;
$$ language plpgsql
However, when I type select test() and the result table is:
It's very strange that only two rows are inserted. So I want to know what lead to this result?
Update my question in 04/05/2016:
I revise the function like this:
create or replace function test( ) returns void as
$$
declare
cur cursor for select * from city order by pop;
b record;
begin
for b in cur
LOOP
insert into city(pop) select b.pop+2;
RAISE NOTICE ' % IS INSERTED' , b;
end loop;
end;
$$ language plpgsql
Then I get the correct result:
But I still wonder why in the first function, only two rows are inserted.
I finally figure out that why the result is wrong, just like Abelisto's comment . I did two fetchers in loop at each step:
at for row in cur LOOP ,
at fetch cur into b
So the first row where pop=500 and the third row where pop =1000 have already been fetched in for loop, and it can't be fetched by b.

Sql Server Any data from the row itself with Try Catch?

Using Try/Catch with SqlServer 2008R2, is there a trick to getting some information out of the row that caused the error? For example,
BEGIN TRY
INSERT INTO MyTable
SELECT *
FROM #MyTableVar
END TRY
BEGIN CATCH
-- In here, is there some way to know, for example, MyTable.SomeColumn for the offending row?
END CATCH
This is what I ended up doing:
DECLARE #MyResults TABLE (
Id INT IDENTITY( 1, 1 )
TheKey VARCHAR(20),
Success BIT
)
-- Initially set Success to 1 for all rows
INSERT INTO #MyResults
SELECT TheKey, 1
FROM #MyTableVar
DECLARE #CurrentKey VARCHAR(20)
DECLARE #CurrentId INT
DECLARE incoming CURSOR FOR SELECT Id, TheKey FROM #MyResults
OPEN incoming
FETCH incoming into #Id, #CurrentKey
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
INSERT INTO OfficialTable
SELECT *
FROM #MyTableVar TV
WHERE TV.TheKey = #CurrentKey
END TRY
BEGIN CATCH
-- On any failure, update the proper row in #MyResults
UPDATE #MyResults
SET Success = 0
WHERE TheKey = #CurrentKey AND Id = #CurrentId
END CATCH
FETCH NEXT FROM incoming INTO #Id, #CurrentKey
END
CLOSE incoming
SELECT * FROM #MyResults
In the CATCH, I know the key to #MyTableVar, so, I should be able to look up anything I need with that.

Reading inserted column names and values in a TSQL trigger

I've been asked to create history tables for every table in a database. Then create a trigger that will write to the history table whenever the primary table is updated.
The history tables have the same structure as the primary table, but with a couple of extra rows ('id' and 'update type')
I've never done anything with triggers before, but I would like to do is dynamically go through the columns in 'Inserted' and construct an insert statement to populate the history table.
However I cannot work out how to read the names of the columns and their individual values.
My half finished trigger currently looks like...
CREATE TRIGGER tr_address_history
ON address
FOR UPDATE
AS
DECLARE #colCount int
DECLARE #maxCols int
SET #colCount = 0
SET #maxCols = (SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted')
PRINT 'Number of columns = ' + CONVERT(varChar(10),#maxCols)
WHILE (#colCount <= #maxCols)
BEGIN
DECLARE #name varchar(255)
SELECT #name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'
DECLARE #value varchar(255)
SELECT #value = #name FROM Inserted
PRINT 'name = ' + #name + ' and value = ' + #value
SET #colCount = #colCount + 1
END
PRINT 'Done';
When the trigger runs it just says "Number of columns = 0"
Can anyone tell me what's wrong with :
SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'
Thanks...
First solution proposed by Beenay25 is good, but you should use affected table instead of 'inserted' pseudotable.
This is:
SELECT #name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'AFFECTED_TABLE'
Instead of 'INSERTED'
Also, you should use dynamic SQL.
This will be a complete working solution:
ALTER TRIGGER [dbo].[tr_address_history]
ON [dbo].[address]
AFTER Insert
AS
DECLARE #ColumnName nvarchar(500)
DECLARE #TableName nvarchar(500)
DECLARE #value nvarchar(500)
DECLARE #Sql nvarchar(500)
Set #TableName='address'
DECLARE ColumnsCursor CURSOR FOR
select column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'address'
OPEN ColumnsCursor
FETCH NEXT FROM ColumnsCursor into #ColumnName
WHILE ##FETCH_STATUS=0
BEGIN
select * into #tmp from inserted
Set #Sql= 'SELECT #value =' + #ColumnName + ' FROM #tmp'
EXEC sp_executesql #Sql, N'#Value nvarchar(500) OUTPUT', #Value OUTPUT
DROP TABLE #TMP
print '[' + #ColumnName +'='+ ltrim(rtrim(#Value))+']'
FETCH NEXT FROM ColumnsCursor into #ColumnName
END
CLOSE ColumnsCursor
DEALLOCATE ColumnsCursor
The 'inserted' table is a pseudo-table; it doesn't appear in INFORMATION_SCHEMA.
There is the UPDATE() operator for use in triggers:
CREATE TRIGGER trigger_name ON tablename
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(Column1) OR UPDATE(Column2))
BEGIN
your sql here
END
COLUMNS_UPDATED
UPDATE()
There is a way to do what the questioner requires:
I have made something inside a trigger that tests whether all the columns of a particular table actually participated in an insert to that table. If they did, I later copied them to a history table. If they did not, then rollback and print only complete rows may be inserted into the report table. Perhaps they could adapt this to their needs:
here it is:
[
if exists (select 1 from inserted) and not exists (select 1 from deleted) -- if an insert has been performed
begin -- and we want to test whether all the columns in the report table were included in the insert
declare #inserted_columncount int, #actual_num_of_columns int, #loop_columns int, #current_columnname nvarchar(300),
#sql_test nvarchar(max), #params nvarchar(max), #is_there bit
set #actual_num_of_columns = (
select count(*) from (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report') as z)
set #inserted_columncount = 0
set #loop_columns = 1
declare inserted_columnnames cursor scroll for -- these are not really the inserted ones, but we are going to test them 1 by 1
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report'
set #params = '#is_there_in bit output'
open inserted_columnnames
fetch next from inserted_columnnames into #current_columnname
select * into #temp_for_dynamic_sql from inserted -- this is necessary because the scope of sp_executesql does not include inserted pseudo table
while (#loop_columns <= #actual_num_of_columns) -- looping with independent integer arithmetic
begin
set #sql_test = '
set #is_there_in = 0
if exists (select ['+#current_columnname+'] from #temp_for_dynamic_sql where ['+#current_columnname+'] is not null)
set #is_there_in = 1'
exec sp_executesql #sql_test, #params, #is_there output
if #is_there = 1
begin
fetch next from inserted_columnnames into #current_columnname
set #inserted_columncount = #inserted_columncount + 1
set #loop_columns = #loop_columns + 1
end
else if #is_there <> 1
begin
fetch next from inserted_columnnames into #current_columnname
set #loop_columns = #loop_columns + 1
end
end
close inserted_columnnames
deallocate inserted_columnnames
-- at this point we hold in two int variables the number of columns participating in the insert and the total number of columns
]
Then you can simply do if #inserted_columncount < #actual_num_of_columns ..........
I did this because i have a sp that inserts 1 complete line to the report table every time it runs. That's fine, but i don't want anyone else touching that table by mistake. not even myself. I also want to keep history. So i made this trigger to keep the history but also to check if an insert was attempted without values for all the columns in the report table, and further down the code it checks if an update or delete was attempted and it rollbacks.
i was thinking of expanding this to allow an update but in which all the columns are set.
this could possibly be done as follows:
if update was attempted,
and exists (
select possibly_excluded.COLUMN_NAME from (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report') as possibly_excluded
group by possibly_excluded.COLUMN_NAME
having COLUMN_NAME not in (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report' and
sys.fn_IsBitSetInBitmask(#ColumnsUpdated, COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), COLUMN_NAME, 'ColumnID')) <> 0)
)
begin
rollback transaction
print 'Only updates that set the values for a complete row are allowed on the report table..'
end