ROLLBACK make data in INSERTED table is removed in AFTER INSERT TRIGGER - sql-server

I give an example to show my problem. I created a table like this:
CREATE TABLE a
(
id INT
)
I then created an AFTER INSERT trigger to not allow insert id = 1 into table a:
CREATE TRIGGER [dbo].[insert_a]
ON [dbo].[a] AFTER INSERT
AS
BEGIN
DECLARE #id INT
SELECT #id = id FROM inserted
IF #id = 1
BEGIN
RAISERROR('1', 12, 1)
ROLLBACK;
END
SELECT * FROM inserted
END
Then I insert id = 1 into table a:
INSERT INTO a VALUES(1)
I get nothing from INSERTED table.
I realize that when I ROLLBACK then + the data in table a was rolled back (I know) and data in INSERTED table is also removed. Why is that?
If I change the AFTER INSERT trigger to an INSTEAD OF INSERT trigger:
ALTER TRIGGER [dbo].[insert_a]
ON [dbo].[a] INSTEAD OF INSERT
AS
BEGIN
DECLARE #id INT
SELECT #id = id FROM inserted
IF #id = 1
BEGIN
RAISERROR('1', 12, 1)
ROLLBACK
END
SELECT * FROM inserted
END
INSERT INTO a VALUES(1)
Then I get the result:
id
1
That means data in INSERTED table is not removed though have been ROLLBACK.
Help me explain deeply what happens inside trigger?

This is the intended behaviour as far as I know. It's just that AFTER may be a bit misleading depending on how you look at it.
"The trigger and the statement that fires it are treated as a single transaction, which can be rolled back from within the trigger. If a severe error is detected, the entire transaction automatically rolls back.".
https://msdn.microsoft.com/en-us/library/ms178110.aspx

Related

Issue with Trigger

I am working with an insert trigger and work fine. I am creating an insert trigger and take a backup in tblHist table.
I have two tables:
tblUser - creating this table for insert,update and delete purpose
tblHist - creating this table for store a record for history purpose
tblUser table design:
tblHist table design:
Then I create an insert and update trigger:
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERINSERT]
ON [dbo].[tblUser]
AFTER INSERT, UPDATE
--,DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
BEGIN
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress,
#countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM tblUser u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER INSERT update trigger fired.'
END
END
When I insert a record into the tblUser table, then it inserts a record into the tblHist table - this is working fine.
See below
Then I update a record then insert a history in tblHist table working fine.
but issue is when I add a code for delete a record functionality in trgr_tblUser_AFTERINSERT then delete functionality not work
And when I create a delete trigger separately then work fine
See below
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERDELETE]
ON [dbo].[tblUser]
FOR DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress, #countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM deleted u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER DELETE trigger fired.'
END
I want to add insert, update, delete trigger functionality In one trigger but not work.
What I am trying:
exist select 1 --- but not work
which place I am doing wrong need help
I highly doubt that your first trigger will work properly .... you're just selecting an arbitrary rows from your tblUser table - not even one that's necessarily just been inserted or updated ....
I would strongly recommend these changes:
creating a separate trigger for each operation - that makes the trigger simpler, since you don't need to first figure out what you're dealing with....
add a ModifiedOn DATETIME2(3) column to your tblHist to record the date & time stamp when the change occurred
also possibly add an Operation column to your tblHist - so that you can understand what operation (insert, update, delete) caused this entry in the history table
properly handle the Inserted and Deleted pseudo tables in your trigger code taking into account they can (and will!) contain multiple rows - handle them in a proper, set-based fashion
drop the PRINT - makes no sense inside a trigger....
Code would be something like:
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterInsert
ON dbo.tblUser
AFTER INSERT
AS
BEGIN
-- do an "INSERT INTO" ...
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
-- based on the "Inserted" pseudo table, and use proper set-based approach
SELECT
SYSDATETIME(),
i.userid, i.username, i.useraddress, i.countryname, i.statename, i.cityname
FROM
Inserted i;
END
END
and
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterDelete
ON dbo.tblUser
AFTER DELETE
AS
BEGIN
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
SELECT
SYSDATETIME(),
d.userid, d.username, d.useraddress, d.countryname, d.statename, d.cityname
FROM
Deleted d;
END
END
If you want to check whether it was an insert, update or delete, you have to examine the row count of the inserted and deleted pseudotables - for an insert, records are only present in inserted, for delete they are only present in deleted. An update has both so you can tell the old values (deleted) and the new (inserted)
Make your life easy; put twice as many columns in your hist (should be called UserHist, no?) table as your user table, and make them e.g. old_username, new_username.. an insert the result of a full outer join between the inserted and deleted tables, this way you can tell if it was an insert, update or delete and particularly for an insert, what changed to what
Alternatively, use something like
IF EXISTS(SELECT null FROM inserted)
IF EXISTS(SELECT null FROM deleted)
--it was an update
ELSE
--it was an insert
END;
ELSE
--it was a delete
END;
Or, make 3 separate triggers
Final point of note, you're doing these queries wrong - you're declaring a bunch of variables (that can only hold a single value)and selecting the values from inserted/deleted into them but those pseudotables can have more than one row, if the query affected multiple rows (such as DELETE FROM user WHERE name = 'John')
You should be doing your operations in a bunch-of-rows way, not a "single row" way:
INSERT INTO tblHist
SELECT * FROM inserted
This can insert multiple rows into hist, and this is the way you should always think about doing things in SQLServer.. Even if you only ever insert one row and your inserted pseudotable has one row, you must get into the habit of treating it as "a collection of rows with one entry" so that any code you write won't fall apart when one day it becomes "a collection of rows with multiple entries"
Note in one of your attempts you did not select from inserted, you selected from the users table - this is wrong:
Of course, an AFTER delete trigger will insert nothing if you just deleted the only row from tblusers, but you shouldn't be using tblusers anyway

How to create a roll back of a table that you updated in sql

I have an sql query that updates a table based on a condition.I am creating a migration file in visual studio, how do i go about adding a roll back to ensure that the changes i updated into a file goes back to how it was.
INSERT INTO Table(ID,Name,SiteID,Surname)
SELECT
(SELECT MAX(ID) FROM Table) + ROW_NUMBER()OVER (ORDER BY ID),
Name,
10100,
Surname,
FROM Table
WHERE SiteID = 10000 --so it will copy this data 10000 and make a new entry of 10100
Can you advise on how to create a rollback so that it will delete all the 10100 entries and go back to been how it was
can I just say ?
delete
from table
where siteID=10100
Is this efficient? for a roll back
A DELETE statement, is just that, a DELETE statement. Rolling back something means undoing what has so far been done in the uncommitted transaction. This may not be "deleting", it might be undoing an UPDATE, or returning a row that was previously deleted, or even DDL changes.
In your case, if you want to remove the row you inserted earlier, then a DELETE statement is what you're after. That's not rolling back though. Here's an example of a ROLLBACK (and COMMIT):
--BEGIN a Transaction
BEGIN TRANSACTION Creation;
--Create a table
CREATE TABLE #Sample (ID int IDENTITY(1,1), String varchar(10));
-- insert a row
INSERT INTO #Sample (String)
VALUES ('Hello');
--Rollback the transactions
ROLLBACK TRANSACTION Creation;
--Now, not only has the row never been inserted, the table was not created!
--This will error
SELECT *
FROM #Sample;
GO
--Now, let's create and COMMIT that table this time:
BEGIN TRANSACTION Creation2;
--Create a table
CREATE TABLE #Sample (ID int IDENTITY(1,1), String varchar(10));
-- insert a row
INSERT INTO #Sample (String)
VALUES ('Hello');
--And commit
COMMIT TRANSACTION Creation2;
GO
--Hazaar! data
SELECT *
FROM #Sample;
GO
--And finally, a little play around with some data
BEGIN TRANSACTION Data1;
INSERT INTO #Sample (String)
VALUES ('These'),('are'),('more'),('values');
--Let's Delete the Hello as well
DELETE
FROM #Sample
WHERE ID = 1;
--Inspect mid transction
SELECT *
FROM #Sample;
--Rollback!
ROLLBACK TRANSACTION Data1;
--Oh, the values have gone!
SELECT *
FROM #Sample;
--Notice, however, the ID still increments:
INSERT INTO #Sample (String)
VALUES ('Goodbye');
--Goodbye is ID 6
SELECT *
FROM #Sample;
GO
DROP TABLE #Sample;
Hope that helps explain what a ROLLBACK is for you in SQL Server terms.

Null values INSERTED in trigger

I want to copy content of one table to another table in the same database.
For this I wrote trigger on source table which triggered on AFTER INSERT UPDATE, there are 2 uniqueidentifier fields in the table which generates values based on newid() as default binding. Based on this uniqueidentifier I am checking whether the record is present on the destination table or not if present then it will update and if not present then insert dataset into the table.
Problem is when i insert a new record the INSERTED in trigger give me NULL values for the uniqueidentifier fields.
In may case only one row is either update or insert so cursor is not used.
Below is my code, I am getting null values in #OriginalTable_MoveDataUID and #OriginalTable_ProcedureUID. Both the MoveDataUID and ProcedureUID are uniqueidentifier fileds.
Please share your thoughts or any alternative for this.
ALTER TRIGGER [dbo].[spec_ref_movedata_procedures_ToUpdate]
ON [dbo].[spec_ref_movedata_procedures]
AFTER INSERT, UPDATE
AS
BEGIN
SET XACT_ABORT ON
BEGIN DISTRIBUTED TRANSACTION
DECLARE #OriginalTable_MoveDataUID NVarchar (100)
DECLARE #OriginalTable_ProcedureUID NVarchar (100)
DECLARE #PresentInHistoryYesNo int
SELECT #OriginalTable_MoveDataUID= MoveDataUID,#OriginalTable_ProcedureUID=ProcedureUID FROM INSERTED
-- inserted for checking purpose
INSERT INTO ERP_Test_NK_spec_ref_movedata_procedures_history_2 (MovedataUID,ProcedureUID) VALUES
(#OriginalTable_MoveDataUID,#OriginalTable_ProcedureUID)
SELECT #PresentInHistoryYesNo = count(*) from spec_ref_movedata_procedures_history WHERE MoveDataUID=#OriginalTable_MoveDataUID AND ProcedureUID=#OriginalTable_ProcedureUID
IF #PresentInHistoryYesNo = 0
BEGIN
-- insert opertions
print 'insert record'
END
ELSE IF #PresentInHistoryYesNo = 1
BEGIN
-- update opertions
print 'update record'
END
COMMIT TRANSACTION
SET XACT_ABORT OFF
END
Instead of using variables, you could do this:
INSERT INTO ERP_Test_NK_spec_ref_movedata_procedures_history_2 (MovedataUID,ProcedureUID)
SELECT MoveDataUID,ProcedureUID FROM INSERTED

What is the expected result table during a SQL update trigger?

During a SQL trigger update trigger is there an easy way to get the whole expected result table (that is, what the table would look like after the trigger executes?)
Here's the only thing I could think of (which boils down to the table MINUS the deleted table + the inserted table):
SELECT *
FROM TheTable t
WHERE
NOT EXISTS
(
SELECT 1
FROM DELETED d
WHERE
d.primaryKey1 = t.primaryKey1
AND d.primaryKey2 = t.primaryKey2
-- ...
)
UNION ALL
SELECT *
FROM INSERTED
UPDATE:
The above is unnecessarily complicated inside of a FOR/AFTER trigger. It suffices just to query the table itself. (Thanks to #usr for the wake-up call.) However, for an instead of trigger you would do something similar to get the resulting table, although it's likely you would actually want to be constructing the table during it's execution.
Use an AFTER trigger to look at the table in the final state.
As already suggested you can use an after insert/update trigger. In this trigger context you have the table with the new values, but the insert or update is not really over, so any throw will rollback the operation. Example:
-- drop table testConstraint
create table testConstraint
(
Id INT,
Name varchar(10)
-- CONSTRAINT CK_testConstraint_misc CHECK (dbo.checkTest(Id, Name) <> 0)
)
create TRIGGER trgConstraint
ON dbo.testConstraint
AFTER INSERT, UPDATE AS
BEGIN
IF EXISTS (SELECT 1 FROM testConstraint where Id > 10)
THROW 51000, 'Invalid record found', 1;
END
GO
-- ok
insert into testConstraint (Id, Name) values (1, 'n1'), (2, 'n2'), (3, 'n3')
go
select * from testConstraint
go
-- will fail
insert into testConstraint values (11, 'n11')
go
select * from testConstraint
GO
-- will fail
update testConstraint set Id = 20 where Id = 2
go
select * from testConstraint
GO

Creating a trigger on SQL Server 2008 R2 to catch a record update

Using Great Plains here and one of our users keeps screwing up customer data so we want to put a trigger on the customer table and so we can find out who it is.
Anyway, I created a table called audit_RM00101 as follows:
DATE nchar(10)
CUSTNMBR char(15)
CUSTNAME char(65)
UPSZONE char(3)
SALSTERR char(15)
USERID nchar(100)
I want to capture those same fields from the table I want to audit so I wrote the trigger as follows:
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
FOR UPDATE
AS
DECLARE #UserID VARCHAR(128)
SELECT #UserID = system_user
INSERT INTO audit_RM00101
SELECT DATE, CUSTNMBR, CUSTNAME, UPSZONE, SALSTERR, #UserID FROM UPDATED
The trigger gets created just fine but when I try to test it by updating a customer record in Great Plains, Great Plains throws up an ugly error and the trigger doesn't get fired.
What am I doing wrong here?
Thanks.
in a trigger, you get the DELETED and INSERTED tables, there is no UPDATED, so replace FROM UPDATED with FROM INSERTED
also try to fix your USERID column, your audit_RM00101.USERID is a nchar(100) while #UserID is a VARCHAR(128).
EDIT based on OPs comment: Ah, so there is no way to audit when a table is updated by using a trigger?
in a trigger when deleting, DELETED is populated, but INSERTED is empty
in a trigger when updating, DELETED is populated with the original value, and INSERTED is populated with the newly updated values
in a trigger when inserting, DELETED is empty, but INSERTED has the newly inserted values
There is no UPDATED in SQL Server; just inserted and deleted.
Also, it makes sense to add IF ##ROWCOUNT = 0 RETURN in the very beginning of triger's body.
When UPDATE takes place, both inserted and deleted tables are not empty. You may add the following code to make sure you handle UPDATE, not insert/delete:
IF EXISTS(SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
-- handle update
END ;
It's not really important for your trigger because you specify just FOR UPDATE, it would be important if you had, for instance, FOR UPDATE, INSERT, DELETE.
we have only two magic tables called INSERTED and DELETED
update indirectly is a Delete statement followed by Insert statement. so you have to update the column's value which is present in INSERTED.
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
AFTER UPDATE
AS
BEGIN
DECLARE #INSERTED INT, #DELETED INT
SET #INSERTED = SELECT COUNT(*) FROM INSERTED
SET #DELETED = SELECT COUNT(*) FROM DELETED
IF #INSERTED = 1 AND #DELETED = 1
BEGIN
UPDATE TABLE1
SET COL1 = INSERTED_COL1
WHERE IDCOL = INSERTED_IDCOL
END
END

Resources