Get Updating Value of Column in Trigger - sql-server

DECLARE #IsDeleted AS BIT = 0;
SELECT #IsDeleted = IsDeleted from Updated
IF #IsDeleted=1
BEGIN
UPDATE Reviews
SET IsDeleted = #IsDeleted
WHERE CompanyID = 1
END;
I want to update Reviews if the Company IsDeleted column is updated to 1.
How to determine is updating value is '1'
It's error SELECT #IsDeleted = IsDeleted from Updated

The pseudoatables available in a trigger are inserted or deleted, there is no updated table.
Since you stated t-sql, you should understand that in SQL server triggers operate on batches not single rows. This trigger needs to be completely rewriten to handle multiple record updates. Do not set anything to the value of a scalar variable in a trigger from the inserted or deleted tables. JOin to them instead (and of course when you are updating, don't ever write an update statement without excluding any records where the current value matches the new value. Silly to update a million records when only 2 have changed. Performance is critical ina trigger, so it is even more important there not to update things that don't need updating.

Related

Which is more efficient update where or if exists then update

I would like to know which is more efficient and why.
if not exists (select 1 from table where ID = 101 and TT = 5)
begin
update table
set TT = 5
where ID = 101;
end;
or
update table
set TT = 5
where ID = 101 and TT <> 5;
Assume there is a clustered index on ID (nothing more table used default table creation setting)
WHERE, IF EXISTS and IN all have different performance benefits. I would suggest checking out these two articles.
https://www.sqlshack.com/t-sql-commands-performance-comparison-not-vs-not-exists-vs-left-join-vs-except/
https://sqlchitchat.com/sqldev/tsql/semi-joins-in-sql-server/
SQL Server will generally optimize a non-updating UPDATE to not actually issue any updates. Therefore, with a simple table, you are not going to see much difference.
If you have triggers, they will be fired if the UPDATE statement executes, irrelevant of how many rows are updated.
If the UPDATE statement executes over rows, even if they are modified to the same value, they will appear in the trigger.
If rows are filtered out with a WHERE clause, for example and TT <> 5, then the trigger will fire with 0 rows
rowversion and GENERATED AS columns will be updated regardless.
Clustered key columns will cause a delete and insert of the whole row.
If ALLOW_SNAPSHOT_ISOLATION or READ_COMMITTED_SNAPSHOT are on, even if not being used, then due to the way row-versioning works, an actual update will always be made.
If the IF EXISTS is complex, it still may not be worth doing, but in simple cases it usually is.

SQL - trigger insert only when a user made an update

I have a table, customers_accounts, that tracks some basic information about a customer on an account. When the customer's file is opened, I synchronize the information from an external system so our user gets the most updated information.
UPDATE
customers_accounts
SET
first_name = 'bob',
last_name = 'burger'
WHERE
account_number = '12345'
When a user updates the account, I do the same query, but I update a column indicating the last user to make the change
UPDATE
customers_accounts
SET
first_name = 'bob',
last_name = 'burger',
updated_by = 'H Jon Benjamin',
updated_on = GETDATE()
WHERE
account_number = '12345'
Here's the problem I'm trying to solve. I want to track changes in a history table, but only log changes when they're made by a user, not if they're from the external system. So my plan was to create a trigger that inserts a row if the user column is not blank on the insert (since the updated_by is implicitly null above in the first update)
What I tried is this:
ALTER trigger [dbo].[Accounts_Customers_LogUpdate]
ON [dbo].[Accounts_Customers]
AFTER UPDATE
AS
DECLARE #Now AS DATETIME = GETDATE()
DECLARE #User AS NVARCHAR(150)
SELECT #User = (SELECT [updated_by] FROM INSERTED)
IF (#User IS NOT NULL)
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Accounts_Customers-History]
SELECT *, #User, #Now
FROM inserted
END
Accounts_Customers-History is an exact copy of the table with two additional columns, change_made_by and change_made_on
It doesn't behave how i'd expect though. It inserts whatever value is in updated_by into change_made_by, regardless of the value of updated_by in the query. So I'm getting logged activity triggered by both the user and the import.
Use UPDATE() for this:
Returns a Boolean value that indicates whether an INSERT or UPDATE attempt was made on a specified column of a table or view. UPDATE() is used anywhere inside the body of a Transact-SQL INSERT or UPDATE trigger to test whether the trigger should execute certain actions.
This means that the update function will return false for the first update statement in the question, and true for the second update statement - which is exactly what you need.
Also, please note you should always specify the columns list in an insert statement,
and also always specify the columns list in a select statement. (Why?)
A revised version of your trigger might look something like this:
ALTER TRIGGER [dbo].[Accounts_Customers_LogUpdate]
ON [dbo].[Accounts_Customers]
AFTER UPDATE
AS
DECLARE #Now as DateTime = GetDate()
IF UPDATE(updated_by)
BEGIN
-- Always specify the columns list in an insert statement!
insert into [dbo].[Accounts_Customers-History] (<Columns list>)
-- Always specify the columns list in a select statement!
select <columns list>, #Now
from inserted
END
Please note that the UPDATE() function does not give you any indication if the insert or update statement that fired the trigger was successful, nor does it give you an indication if the value of the column has actually changed - it only indicates whether that column was a part of the insert or update statement that fired the trigger - as you can read in the last paragraph of the remarks section:
If a trigger applies to a column, the UPDATED value will return as true or 1, even if the column value remains unchanged. This is by-design, and the trigger should implement business logic that determines if the insert/update/delete operation is permissible or not.

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

How to store existing column value in new table using trigger

I have two tables
Customer
CustomerUpdate
Structure of both tables are like this
Customer table's structure
CustomerName | CustomerId
CustomerUpdate table's structure
NewCustomerName | NewCustomerId | OldCustomerName
I have few values inserted in the Customer table. Whenever I should update the data in this table I want that the existing as well as new data should be triggered into new table CustomerUpdate.
For this I created a trigger but this is only pulling the updated data, it's not pulling the existing data..
CREATE TRIGGER trgAfterUpdate
ON [dbo].Customer
FOR UPDATE
AS
SET NOCOUNT ON
declare #NewCustomerName nchar(20);
declare #NewCustomerId nchar(20);
declare #OldCustomerName nchar(20);
declare #audit_action varchar(100);
select #NewCustomerName = i.CustomerName from inserted i;
select #NewCustomerId = i.CustomerId from inserted i;
select #OldCustomerName = c.CustomerName
from Customer c
where CustomerId = #NewCustomerId;
if update(CustomerName)
set #audit_action='Updated Record -- After Update Trigger.';
if update(CustomerId)
set #audit_action='Updated Record -- After Update Trigger.';
insert into CustomerUpdate(NewCustomerName, NewCustomerId, OldCustomername)
values(#NewCustomerName, #NewCustomerId, #OldCustomerName);
PRINT 'AFTER UPDATE Trigger fired.'
GO
Please help me out
First, selecting from the table being modified when an update trigger is executing will get the new value. These are AFTER triggers (rather than INSTEAD triggers) and therefore the update has already happened by the time the trigger fires (although it can be rolled back). If you need the old value, you should select from the DELETED pseudo-table.
Second, as pointed out by #marc_s in comments, your trigger has the hidden assumption that only one row is affected by each update. This may very well be a valid assumption for your environment, if your application only ever updates one row at a time, but in the general case, every trigger should be ready to handle the case where many rows are affected by a single update. Writing your triggers to handle multiple rows is good practice.
Third, all of your sequentially executing code is pretty much unnecessary. The old value and the new value can be retrieved and inserted all at once:
CREATE TRIGGER trgAfterUpdate
ON [dbo].Customer
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON
insert into CustomerUpdate(NewCustomerName, NewCustomerId, OldCustomername)
-- case 1: ID unchanged
SELECT I.CustomerName, I.CustomerID, D.CustomerName
FROM Inserted I
JOIN Deleted D on I.CustomerID=D.CustomerID
UNION ALL
-- case 2: ID changed, Name unchanged
SELECT I.CustomerName, I.CustomerID, D.CustomerName
FROM Inserted I
JOIN Deleted D on I.CustomerName=D.CustomerName
WHERE I.CustomerID<>D.CustomerID
UNION ALL
--case 3: ID changed, Name changed
SELECT I.CustomerName, I.CustomerID, D.CustomerName
FROM Inserted I
LEFT JOIN Deleted D on I.CustomerID=D.CustomerID OR I.CustomerName=D.CustomerName
WHERE D.CustomerID IS NULL;
END

SQL Server timestamping trigger

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.

Resources