SQL Triggers - Deleted or Updated? or maybe something else? - sql

I am trying to figure out which i need to use here: deleted, inserted or updated.
basically.
I need to write some data to the history table, when the main table is updated, and only if the status changes from something to either pending or active.
This is what I have now:
ALTER TRIGGER [dbo].[trg_SourceHistory] ON [dbo].[tblSource]
FOR UPDATE AS
DECLARE #statusOldValue char(1)
DECLARE #statusNewValue char(1)
SELECT #statusOldValue = statusCode FROM deleted
SELECT #statusNewValue= statusCode FROM updated
IF (#statusOldValue <> #statusNewValue) AND
(#statusOldValue = 'P' or #statusOldValue = 'A')
BEGIN TRY
INSERT * INTO tblHistoryTable)
select * from [DELETED]
so I want the new data to stay in the main table, the the history table to be updated with what is being overwritten... right now it just copies the same info over. so after update, both my tables have the same data.

There are only the Inserted and Deleted pseudo tables - there's no Updated.
For an UPDATE, Inserted contains the new values (after the update) while Deleted contains the old values before the update.
Also be aware that the triggers is fired once per batch - not once for each row. So both pseudo tables will potentially contain multiple rows! Don't just assume a single row and assign this to a variable - this
SELECT #statusOldValue = statusCode FROM deleted
SELECT #statusNewValue= statusCode FROM updated
will fail if you have multiple rows ! You need to write your triggers in such a fashion that they work with multiple rows in Inserted and Deleted !
Update: yes - there IS a much better way to write this:
ALTER TRIGGER [dbo].[trg_SourceHistory] ON [dbo].[tblSource]
FOR UPDATE
AS
INSERT INTO dbo.tblHistoryTable(Col1, Col2, Col3, ...., ColN)
SELECT Col1, COl2, Col3, ..... ColN
FROM Deleted d
INNER JOIN Inserted i ON i.PrimaryKey = d.PrimaryKey
WHERE i.statusCode <> d.statusCode
AND d.statusCode IN ('A', 'P')
Basically:
explicitly specify the columns you want to insert - both in the INSERT statement as well as the SELECT statement retrieving the data to insert - to avoid any nasty surprises
create an INNER JOIN between Inserted and Deleted pseudo-tables to get all rows that were updated
specify all other conditions (different status codes etc.) in the WHERE clause of the SELECT
This solution works for batches of rows being updated - it won't fail on a multi-row update....

You need to use both the inserted and deleted tables together to check for records that:
1. Already existed (to check it's not an insert)
2. Still exists (to check it's not a delete)
3. The Status field changed
You also need to make sure you do that in a set based approach, as per marc_s's answer, triggers are not single record processes.
INSERT INTO
tblHistoryTable
SELECT
deleted.*
FROM
inserted
INNER JOIN
deleted
ON inserted.PrimaryKey = deleted.PrimaryKey
WHERE
inserted.StatusCode <> deleted.StatusCode
AND (inserted.StatusCode = 'P' OR inserted.StatusCode = 'A')
inserted = the new values
deleted = the old values

There is no updated table, you are looking for inserted.

Related

SQL Server : make update trigger don't activate with no changing value

I want to track the update changes in a table via a trigger:
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted
However in real production some of the update queries select rows with vague conditions and update them all regardless of whether they are actually changed, like
UPDATE Targettable
SET customer_type = 'VIP'
WHERE 1 = 1
--or is_obsolete = 0 or register_date < '20160101' something
But due to table size and to analyze, I only want to choose those actually modified data for tracking. How to achieve this goal?
My track table has many columns (so I do not prefer checking inserted and deleted column one by one) but it seldom changes structure.
I guess the following code will be useful.
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT *
FROM Inserted
EXCEPT
SELECT *
FROM Deleted
I realize this post is a couple months old now, but for anyone looking for a well-rounded answer:
To exit the trigger if no rows were affected on SQL Server 2016 and up, Microsoft recommends using the built-in ROWCOUNT_BIG() function in the Optimizing DML Triggers section of the Create Trigger documentation.
Usage:
IF ROWCOUNT_BIG() = 0
RETURN;
To ensure you are excluding rows that were not changed, you'll need to do a compare of the inserted and deleted tables inside the trigger. Taking your example code:
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted i
INNER JOIN deleted d
ON d.[SomePrimaryKeyCol]=i.[SomePrimaryKeyCol] AND
i.customer_type<>d.customer_type
Microsoft documentation and w3schools are great resources for learning how to leverage various types of queries and trigger best practices.
Prevent trigger from doing anything if no rows changed.
Writing-triggers-the-right-way
CREATE TRIGGER the_trigger on dbo.Data
after update
as
begin
if ##ROWCOUNT = 0
return
set nocount on
/* Some Code Here */
end
Get a list of rows that changed:
CREATE TRIGGER the_trigger on dbo.data
AFTER UPDATE
AS
SELECT * from inserted
Previous stack overflow on triggers
#anna - as per #Oded's answer, when an update is performed, the rows are in the deleted table with the old information, and the inserted table with the new information –

Update trigger select fields from same row

i want an update trigger an a specific field. if that field value is changed i want to insert into a different table selecting all values of the row where update was made even though it was just for one field .
example
id--------value1--------value2
1-----------abc ----------efg
if value1 is updated to hij, i want to select id(1), value1(hij) and value2(efg) and insert into a different table.
i cannot do inserted.Id or inserted.value2 since both fields are not updated.
NOTE: please note only 1 field is updated, other field values are the same before and after, in my question i have just used an example, but in real life a record will be inserted and i am expected to insert the same values onto a different table. but upon insert the record wont be approved until later when approved field value is changed thats when i am expected the bring the values from other fields to different table.
In your UPDATE trigger, you have access to the Deleted and Inserted pseudo tables which contain the old values (before the UPDATE) and the new ones after the UPDATE.
So you should be able to write something like this:
CREATE TRIGGER trg_Updated
ON dbo.YourTableName
FOR UPDATE
AS
INSERT INTO dbo.ThisOtherTableOfYours(Id, Value1, Value2)
SELECT
i.Id, i.Value1, i.Value2
FROM
Inserted i
INNER JOIN
Deleted d ON i.Id = d.Id
WHERE
i.Value1 <> d.Value1
The SELECT basically joins the two pseudo tables with the old and new values, and selects those rows which have a difference in the Value1 column.
From those columns, the new values after the update are being inserted into your other table. And the Inserted table does contain ALL columns (with their new values) from your table - not just those that have been actually updated - ALL of them!
You should use a simple trigger:
create or replace trigger NAME on TABLENAME after update as
if :new.value1 = 'hij'{
insert into OTHERTABLE (id, value1, value2) values (:old.id, :old.value1, :old.value2);
}

SQL Server insert trigger not working

This is my first time using triggers.
My trigger is not being triggered please help.
CREATE TRIGGER sbhack_autoban
ON LOG_CONNECT201211
FOR INSERT
AS
BEGIN
/* query to run if single or multiple data is
inserted into LOG_CONNECT201211 table */
UPDATE login.dbo.USER_CHECK_LOGIN
SET login.dbo.USER_CHECK_LOGIN.CHECKLOGIN = 2
WHERE login.dbo.USER_CHECK_LOGIN.USER_KEY IN
(SELECT e.USER_KEY
FROM game.dbo.CHAR_DATA0 AS e
INNER JOIN gamelogs.dbo.LOG_USING_DEPOT201211 AS p
ON e.CHAR_KEY = p.CHAR_KEY
WHERE p.GATENUM = 150)
AND login.dbo.USER_CHECK_LOGIN.CHECKLOGIN = 0
AND login.dbo.USER_CHECK_LOGIN.USER_KEY != 51;
END
This is suppose to run the query inside the BEGIN : END if an entry is inserted into the LOG_CONNECT201211 table. But nothing is happening even when I have inserted multiple data into LOG_CONNECT201211.
When your INSERT trigger fires - then at least one new row has been inserted! That's a fact.
Now the question is: given that a single or multiple new rows have been inserted - what do you want to do with this knowledge??
Typically, you could e.g. set a column to a value you cannot specify as a default constraint - or you could insert the fact that the row has been inserted into an audit table or something....
So you'd have something like this:
CREATE TRIGGER sbhack_autoban
ON LOG_CONNECT201211
FOR INSERT
AS
INSERT INTO LogAudit(InsertedDate, UserKey)
SELECT
GETDATE(), i.User_Key
FROM
Inserted i
or something like that....
Update: ok, so you want to run that UPDATE statement when the rows have been inserted - not 100% clear, what columns/values from the inserted rows you want to use - looks like the e.UserKey column only - correct?
Then the UPDATE would be:
UPDATE login.dbo.USER_CHECK_LOGIN
SET login.dbo.USER_CHECK_LOGIN.CHECKLOGIN = 2
WHERE
login.dbo.USER_CHECK_LOGIN.USER_KEY IN
(SELECT USER_KEY FROM Inserted)
AND login.dbo.USER_CHECK_LOGIN.CHECKLOGIN = 0
AND login.dbo.USER_CHECK_LOGIN.USER_KEY != 51;
Update #2:
The point I still don't understand is : why do you want to run an update that uses the USER_CHECK_LOGIN, CHAR_DATA0 and LOG_USING_DEPOT201211 tables, when some rows are getting inserted into a totally separate, unrelated table LOG_CONNECT201211 ??
A trigger is used when you want to do something because rows have been inserted into that table - but in that case, you typically want to do something with the rows and their values that have been inserted...
I just don't see any connection between the rows being inserted into LOG_CONNECT201211 event, and the tables you are then querying from and updating. Where's the link?? WHY do you need to run *this UPDATE when data is inserted into LOG_CONNECT201211 ?? It would make sense if data where inserted into one of the tables involved in the UPDATE - but like this, it just totally doesn't make any sense .....

Delete and Insert or Select and Update

We have a status table. When the status changes we currently delete the old record and insert a new.
We are wondering if it would be faster to do a select to check if it exists followed by an insert or update.
Although similar to the following question, it is not the same, since we are changing individual records and the other question was doing a total table refresh.
DELETE, INSERT vs UPDATE || INSERT
Since you're talking SQL Server 2008, have you considered MERGE? It's a single statement that allows you to do an update or insert:
create table T1 (
ID int not null,
Val1 varchar(10) not null
)
go
insert into T1 (ID,Val1)
select 1,'abc'
go
merge into T1
using (select 1 as ID,'def' as Val1) upd on T1.ID = upd.ID --<-- These identify the row you want to update/insert and the new value you want to set. They could be #parameters
when matched then update set Val1 = upd.Val1
when not matched then insert (ID,Val1) values (upd.ID,upd.Val1);
What about INSERT ... ON DUPLICATE KEY? First doing a select to check if a record exists and checking in your program the result of that creates a race condition. That might not be important in your case if there is only a single instance of the program however.
INSERT INTO users (username, email) VALUES ('Jo', 'jo#email.com')
ON DUPLICATE KEY UPDATE email = 'jo#email.com'
You can use ##ROWCOUNT and perform UPDATE. If it was 0 rows affected - then perform INSERT after, nothing otherwise.
Your suggestion would mean always two instructions for each status change. The usual way is to do an UPDATE and then check if the operation changed any rows (Most databases have a variable like ROWCOUNT which should be greater than 0 if something changed). If it didn't, do an INSERT.
Search for UPSERT for find patterns for your specific DBMS
Personally, I think the UPDATE method is the best. Instead of doing a SELECT first to check if a record already exists, you can first attempt an UPDATE but if no rows are affected (using ##ROWCOUNT) you can do an INSERT.
The reason for this is that sooner or later you might want to track status changes, and the best way to do this would be to keep an audit trail of all changes using a trigger on the status table.

what is the correct syntax for creating a database trigger for insert, modify and delete

i have what seems like a basic scenario for a db trigger in SQL server and i am running into an issue.
i have table Users (id, name, phone, etc) and i have tables UsersHistory (id, user_id action, fields, timestamp)
i want a database trigger where anytime inserts, updates or deletes into Users, i want a new record created in UsersHistory with the user id and the action that was done (insert new, updated fields, deleted id. Basically an audit log table.
this is how far i got, but i can't figure out how to:
Get the id on modify and deletes and also
How to get a list of fields that have changed and the action that was committed (insert, delete, update)
CREATE TRIGGER Update_Users_History
ON Users
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- Insert statements for trigger here
insert into UsersHistory (user_id, [action], [fields], timestamp)
select max(id) as user_id, {action ??},{fields??} getdate() from Users)
END
GO
any suggestions?
The easiest might be to just simply create three triggers - one for each operation:
CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
INSERT INTO dbo.UserHistory............
END
CREATE TRIGGER trgUserDelete
ON dbo.User AFTER DELETE
AS BEGIN
INSERT INTO dbo.UserHistory............
END
CREATE TRIGGER trgUserUpdate
ON dbo.User AFTER UPDATE
AS BEGIN
INSERT INTO dbo.UserHistory............
END
That way, things are simple and you easily understand what you're doing, plus it gives you the ability to turn off a trigger for a single operation, if you e.g. need to insert or delete a huge list of items.
Inside the trigger, you have two "pseudo-tables" - Inserted (for INSERT and UPDATE) and Deleted (for UPDATE and DELETE). These pseudo tables contain the values for the newly inserted values (or the updated ones in UPDATE), or the ones that were deleted (for DELETE) or have been updated (the old values, before the update, for the UPDATE operation).
You need to be aware that a trigger will be called once even if you update a huge number of rows, e.g. Inserted and Deleted will typically contain multiple rows.
As a sample, you could write a "AFTER INSERT" trigger like this (just guessing what your table structure might be....):
CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
INSERT INTO
dbo.UserHistory(UserID, Action, DateTimeStamp, AuditMessage)
SELECT
i.UserID, 'INSERT', getdate(), 'User inserted into table'
FROM
Inserted i
END
You are looking for a way to find out which "action" this trigger caused? I don't see any way to do this - another reason to keep the three trigger separate. The only way to find this out would be to count the rows in the Inserted and Updated tables:
if both counts are larger than zero, it's an UPDATE
if the Inserted table has rows, but the Deleted does not, it's an INSERT
if the Inserted table has no rows, but the Deleted does, it's a DELETE
You're also looking for a "list of fields that were updated" - again, you won't have any simple solution, really. You could either just loop through the fields in the "Users" table that are of interest, and check
IF UPDATE(fieldname) ......
but that gets a bit tedious.
Or you could use the COLUMNS_UPDATED() function - this however doesn't give you a nice list of column names, but a VARBINARY in which each column is basically one bit, and if it's turned on, that column was updated. Not very easy to use.....
If you really want to create a single, big trigger, this could serve as a basis - it detects what operation has caused the trigger to fire, and will insert entries into your User_History table:
CREATE TRIGGER trgUser_Universal
ON dbo.Users
AFTER INSERT, UPDATE, DELETE
AS BEGIN
DECLARE #InsHasRows BIT = 0
DECLARE #DelHasRows BIT = 0
IF EXISTS(SELECT TOP 1 * FROM INSERTED)
SET #InsHasRows = 1
IF EXISTS(SELECT TOP 1 * FROM DELETED)
SET #DelHasRows = 1
DECLARE #TriggerAction VARCHAR(20)
IF #InsHasRows = 1 AND #DelHasRows = 1
SET #TriggerAction = 'UPDATE'
ELSE
IF #InsHasRows = 1
SET #TriggerAction = 'INSERT'
ELSE
SET #TriggerAction = 'DELETE'
IF #InsHasRows = 1
INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
SELECT i.UserId, #TriggerAction, null, getdate()
FROM INSERTED i
ELSE
INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
SELECT d.UserId, #TriggerAction, null, getdate()
FROM DELETED d
END
I haven't included the figuring out which fields have been updated part just yet - that's left as an exercise to the reader :-)
Does that help at all?
There are two "tables" that are used in the trigger. One is DELETED and one is INSERTED. When you delete a row, that row is captured in the DELETED table. When you insert a row, that row is captured in the INSERTED table. When you update a row, the old row is in the DELETED table, and the new row is in the INSERTED table. The DELETED and INSERTED tables have the same schema as the table on which you are adding the trigger.
You might check out this solution that will create a query for you that will make all the auditing triggers you want, as well as the table in which to store the audits, excluding any selected tables. It will only do UPDATE triggers, but could easily be modified to make INSERT and DELETE triggers as well.