SQL Server timestamping trigger - sql-server

My knowledge of SQL is pretty limited as I mostly focus in backend Ruby development. However, due to architectural changes and wanting to keep things well designed; I have decided to set up timestamping on the database level rather than on the backend level.
As it goes right now, all of my tables have two columns: CreatedAt and UpdatedAt, both with a default value of GETDATE().
However, I now need to set up a timestamping trigger for UpdatedAt, so that every time a row (or rows) are updated, the UpdatedAt column for those rows gets a brand new timestamp.
I am having trouble with the following trigger I wrote. I am getting an error:
Incorrect Syntax near '='
I am testing out my trigger on my Orders table first, and then I plan to move the functionality to all tables.
CREATE TRIGGER dbo.trgTimestampAfterUpdate
ON Dbo.Orders
AFTER UPDATE
AS
BEGIN
IF EXISTS (SELECT * FROM inserted)
BEGIN
SET UpdatedAt = GETDATE()
END
END
I know that I can access the inserted, and deleted virtual tables when using a trigger. My thought with this query was that I would use inserted in order to distinguish which rows have been updated. If anyone can help that would be great, and also if you wouldn't mind explaining to me what I messed up with my syntax or line of thinking would be greatly appreciated.

You can't really access the inserted tables quite like that. Having just Set UpdatedAt =... is an incomplete statement. Implicitly it makes sense to you but even in your trigger, you have to make complete SQL statements.
The way to do this is to JOIN to the INSERTED table (in the example below, I'm using a semi-join) You can then use the contents of the INSERTED table to perform another update.
CREATE TRIGGER [dbo].[trgTimestampAfterUpdate] ON dbo.orders
FOR UPDATE
AS
BEGIN
IF NOT(UPDATE(UpdatedAt)) --Avoid triggers firing triggers
BEGIN
UPDATE dbo.orders
SET UpdatedAt = GETDATE()
WHERE id IN ( SELECT id
FROM inserted )
END
END
Two REALLY important things to note in this code example. First Updating the table with the trigger on it will cause the trigger to fire again (creating a loop that will increase until you reach the max level of nested triggers on your system.) I put a check to make sure it terminates if you're only updating the updatedat column.
Second, never ever assume there is only one row in the inserted table. Something like the code below is a very common mistake
DECLARE #id INT
SELECT #id = id FROM INSERTED
UPDATE MyTable
SET UpdatedAT = GETDATE()
WHERE id = #id
--DON'T DO THIS!
This looks right, and is a common mistake, but it will only ever update 1 record and there could be any number of records in the INSERTED table.

Related

SQL Server UPDATE Trigger - breaking things

I have an UPDATE statement that, long story short, runs every half minute. This statement is in Powershell so don't mind the variable syntax...
UPDATE dbo.MobileLeases
SET IPAddress = '$($l.IPAddress)',
OwnerName = '$($l.OwnerName)',
Building = '$($l.Building)',
TimeOn = '$($l.Time)',
LeaseExpiry = '$($l.LeaseExpiry)',
Phone = '$($l.Phone)',
OwnerEmail = '$($l.OwnerEmail)'
WHERE PhysicalAddress = '$($l.DeviceID)';
This part works great and I have no problem updating these columns. It updates multiple rows each time it runs.
I have an update trigger I am trying to put on this table, dbo.MobileLeases. When the Building column is updated in dbo.MobileLeases, I want to perform an INSERT into another table, dbo.LeaseAudit.
The insert into seems to work for the first update after the trigger is in place. After that, the scheduled UPDATE statement (from Powershell) stops working! Which is confusing to me, but here is the trigger...
CREATE TRIGGER [dbo].[trigger_LeaseAudit]
ON [dbo].[MobileLeases]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE (Building)
BEGIN
INSERT INTO dbo.LeaseAudit(PhysicalAddress, DeviceName, OwnerName, Building, TimeOn, IPAddress)
SELECT
i.PhysicalAddress, i.DeviceName, i.OwnerName, i.Building,
i.TimeOn, i.IPAddress
FROM
MobileLeases AS m
INNER JOIN
inserted AS i on m.PhysicalAddress = i.PhysicalAddress
INNER JOIN
deleted AS d on m.PhysicalAddress = d.PhysicalAddress
WHERE
m.Building <> d.Building
END
END
As you can see I am trying to INSERT INTO dbo.LeaseAudit when (Building) is updated on dbo.MobileLeases, WHERE the old update value (d.Building) is different to the new value (m.Building).
I guess my biggest question is how is this trigger breaking the initial update query? With the trigger in place, the scheduled update statement stops working. When I remove the trigger, everything works again.
Thank you for any advice.
The answer is that the UPDATE transaction was failing as a whole.
The update trigger configured on tableA is designed to insert a new row on tableB, when tableA.column1 is updated.
The insert statement includes a column that was set as a PK on tableB. Obviously, the insert statement could not insert a duplicate value in the PK column, so the entire update was bombing.
The solution for me was to remove the PK attribute from tableB.PK. It is an audit table, so I should not miss it.
Thank you

Searching for an explanation for a trigger SQL

I have the trigger below and am trying to understand what it is supposed to do. I am not a dba but am trying to understand the script below.
Does it mean every time an update (insert, delete, update) happens with the IT01_Incorporates table, the DateUpdated column is updated with the current date?
I actually would like to know, by applying the trigger below, when is the DateUpdated column supposed to be updated? insert, delete and update?
I also would like to understand the meaning of : set nocount on.
Thank you very much
DateUpdated column is datetime type
CREATE trigger [dbo].[TR_IT01_Update]
on [dbo].[IT01_Incorporates]
for update
as
set nocount on
begin
update IT01_Incorporates
set DateUpdated = getdate()
where exists (
select 1 from inserted where incorpuid = IT01_Incorporates.incorpuid
)
end
If you look at the script, you will see in the header lines, the clause for update. This indicates that it is for update only.
To make for inserts and updates, you would have to change it to say for insert, update. You could also add , delete to make it fire on DELETEs as well, but the trigger will not work in that case because there would no longer be any record to UPDATE.
Lets say the following SQL statement was added to a .NET program one year ago:
UPDATE IT01_Incorporates set field1=#Field1, field2=#Field2, Field3=#Field3 where id=#id
Lets say one of your DBA colleagues altered the table yesterday as follows:
ALTER TABLE IT01_Incorporates ADD DateUpdated datetime
Instead of modifying the code; he/she may create a trigger as a temporary measure to ensure that DateUpdated is also updated with every update.

SQL server GetDate in trigger called sequentially has the same value

I have a trigger on a table for insert, delete, update that on the first line gets the current date with GetDate() method.
The trigger will compare the deleted and inserted table to determine what field has been changed and stores in another table the id, datetime and the field changed. This combination must be unique
A stored procedure does an insert and an update sequentially on the table. Sometimes I get a violation of primary key and I suspect that the GetDate() returns the same value.
How can I make the GetDate() return different values in the trigger.
EDIT
Here is the code of the trigger
CREATE TRIGGER dbo.TR
ON table
FOR DELETE, INSERT, UPDATE
AS
BEGIN
SET NoCount ON
DECLARE #dt Datetime
SELECT #dt = GetDate()
insert tableLog (id, date, field, old, new)
select I.id, #dt, 'field', D.field, I.field
from INSERTED I LEFT JOIN DELETED D ON I.id=D.id
where IsNull(I.field, -1) <> IsNull(D.field, -1)
END
and the code of the calls
...
insert into table ( anotherfield)
values (#anotherfield)
if ##rowcount=1 SET #ID=##Identity
...
update table
set field = #field
where Id = #ID
...
Sometimes the GetDate() between the 2 calls (insert and update) takes 7 milliseconds and sometimes it has the same value.
That's not exactly full solution but try using SYSDATETIME instead and of course make sure that target table can store up datetime2 up to microseconds.
Note that you can't force different datetime regardless of precision (unless you will start counting up to ticks) as stuff can just happen at the same time wihthin given precision.
If stretching up to microseconds won't solve the issue on practical level, I think you will have to either redesign this logging schema (perhaps add identity column on top of what you have) or add some dirty trick - like make this insert in try catch block and add like microsecond (nanosecond?) in a loop until you insert successfully. Definitely not s.t. I would recommend.
Look at this answer: SQL Server: intrigued by GETDATE()
If you are inserting multiple ROWS, they will all use the same value of GetDate(), so you can try wrapping it in a UDF to get unique values. But as I said, this is just a guess unless you post the code of your trigger so we can see what you are actually doing?
It sounds like you're trying to create an audit trail - but now you want to forge some of the entries?
I'd suggest instead adding a rowversion column to the table and including that in your uniqueness criteria - either instead of or as well as the datetime value that is being recorded.
In this way, even if two rows are inserted with identical date/time data, you can still tell the actual insertion order.

Query help to track daily updates made to table(for specific column)

I have 2 tables Individual(IndividualId is primary key) and IndividualAudit. Every time update is made on individual table
record goes to audit table. There are many columns that can be modified but i am interested only in picking up records where SSN is modified.
I m using below query:
Select DI.IndividualId,DI.ssn FRom Individual I
INNER JOIN IndividualAudit A
ON(I.IndividualId = A.IndividualId and A.UpdateDate = GETDATE())
where i.updatedate = GETDATE() and I.ssn <> a.ssn
group by I.IndividualId,I.ssn
Can someone please tell me whether my approach is correct.
Actually i was searching on google and got scared looking at below link:
Query help when using audit table
the person who answered similar query on this post seem to be very good with sql and comparing with his answer my approach looks quite naive.
so i just want to know where am i wrong in my understanding.
Thanks a lot
Rather than fixing the query, I'd suggest instead using an update trigger aimed specifically at changes to that SSN column you're concerned about. The query you've supplied won't work because of the date comparison (as user2159471 has pointed out). But even after you get the query fixed, you'll still have to run it in order to see which SSNs have been updated.
Instead use a SQL update trigger that, perhaps, inserts an entry into a third table each time an individual's SSN get changed. Then you can look at that table any time you, or run a report against it, to see who's been changed.
The trigger code looks like this:
CREATE TRIGGER MyCoolNewTrigger ON Individual
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(SSN))
BEGIN
Declare #oldSSN as varchar(40)
Declare #NewSSN as varchar(40)
set #oldSSN = deleted.SSN --holds the old SSN being changes
Set #NewSSN = inserted.SSN -- holds the new SSN inserted
Insert into IndividualUpdateLog (NewSSN, OldSSN, ChangeDate)
values (#NewSSN, #oldSSN, getdate)
END

T-SQL Insert or update

I have a question regarding performance of SQL Server.
Suppose I have a table persons with the following columns: id, name, surname.
Now, I want to insert a new row in this table. The rule is the following:
If id is not present in the table, then insert the row.
If id is present, then update.
I have two solutions here:
First:
update persons
set id=#p_id, name=#p_name, surname=#p_surname
where id=#p_id
if ##ROWCOUNT = 0
insert into persons(id, name, surname)
values (#p_id, #p_name, #p_surname)
Second:
if exists (select id from persons where id = #p_id)
update persons
set id=#p_id, name=#p_name, surname=#p_surname
where id=#p_id
else
insert into persons(id, name, surname)
values (#p_id, #p_name, #p_surname)
What is a better approach? It seems like in the second choice, to update a row, it has to be searched two times, whereas in the first option - just once. Are there any other solutions to the problem? I am using MS SQL 2000.
Both work fine, but I usually use option 2 (pre-mssql 2008) since it reads a bit more clearly. I wouldn't stress about the performance here either...If it becomes an issue, you can use NOLOCK in the exists clause. Though before you start using NOLOCK everywhere, make sure you've covered all your bases (indexes and big picture architecture stuff). If you know you will be updating every item more than once, then it might pay to consider option 1.
Option 3 is to not use destructive updates. It takes more work, but basically you insert a new row every time the data changes (never update or delete from the table) and have a view that selects all the most recent rows. It's useful if you want the table to contain a history of all its previous states, but it can also be overkill.
Option 1 seems good. However, if you're on SQL Server 2008, you could also use MERGE, which may perform good for such UPSERT tasks.
Note that you may want to use an explicit transaction and the XACT_ABORT option for such tasks, so that the transaction consistency remains in the case of a problem or concurrent change.
I tend to use option 1. If there is record in a table, you save one search. If there isn't, you don't loose anything. Moreover, in the second option you may run into funny locking and deadlocking issues related to locks incompatibility.
There's some more info on my blog:
http://sqlblogcasts.com/blogs/piotr_rodak/archive/2010/01/04/updlock-holdlock-and-deadlocks.aspx
You could just use ##RowCount to see if the update did anything. Something like:
UPDATE MyTable
SET SomeData = 'Some Data' WHERE ID = 1
IF ##ROWCOUNT = 0
BEGIN
INSERT MyTable
SELECT 1, 'Some Data'
END
Aiming to be a little more DRY, I avoid writing out the values list twice.
begin tran
insert into persons (id)
select #p_id from persons
where not exists (select * from persons where id = #p_id)
update persons
set name=#p_name, surname=#p_surname
where id = #p_id
commit
Columns name and surname have to be nullable.
The transaction means no other user will ever see the "blank" record.
Edit: cleanup

Resources