How do I add a "last updated" column (SQL Server)? - sql-server

I have a database called a3lf. On my table players, I'd like to add a column called LastUpdated. When some part of the row is modified, I'd like that the table LastUpdated updates with the time and date info, so I can do some cleaning in a future time.
Is it possible? I was searching for that and I found some code that stills confusing to my knowledge:
ALTER TRIGGER dbo.SetLastUpdatedBusiness
ON dbo.Businesses
AFTER UPDATE -- not insert!
AS
BEGIN
IF NOT UPDATE(LastUpdated)
BEGIN
UPDATE t
SET t.LastUpdated = CURRENT_TIMESTAMP -- not dbo.LastUpdated!
FROM dbo.Businesses AS t -- not b!
INNER JOIN inserted AS i
ON t.ID = i.ID;
END
END
GO
Thank you!

This will be the code for your table.
CREATE TRIGGER dbo.trg_players_Update_LastUpdatedColumn
ON <Your_Schema_name>.players
AFTER UPDATE
AS
UPDATE <Your_Schema_name>.players
SET LastUpdated = GETDATE()
WHERE <ID> IN (SELECT DISTINCT <ID> FROM Inserted).
Generally <ID> is the column, based on which you have updated values (Column used in where filter of an update statement).
Hope this helps..!!

Related

Creating Trigger to get data from one table to another and generating a timestamp

I am trying to track Inventory where data would be entered in an Excel sheet (SQL Spreads) and then updates the SQL table and then gather the sum of that data and put it onto another table that would then generate a timestamp to when it was changed/updated/inserted.
The Pictures with highlighted columns is where I want to have the data in.
(TotalBinLo --> Binlocation)
and then when Binlocation is populated (inserted/updated/deleted) generating a timestamp (MM/DD/YYYY HH:MM:SS)
This is what I've come up so far.
---This Trigger is working when pulling data from one table into another--
Create Trigger tr_BC_totalbinLoc
on bincount
After Update
AS
Begin
update OnHandInv
set OnHandInv.binlocation = bincount.totalbinlo
from bincount
inner join OnHandInv on bincount.partnumber = OnHandInv.PartNumber;
End
---Another Trigger (Works) but enters in date for all rows. (Don't want) (only need for one column.)
Create Trigger tr_totalbinLoc_OHI
On Onhandinv
After Update
AS
Update Onhandinv
set dateupdated = getutcdate()
where PartNumber in (select distinct PartNumber from onhandinv)
totalbinlo
ColNeedToPopu
You need to join the inserted table to OnHandInv. I have assumed that PartNumber is the primary key to join on.
You also need to remove deleted rows which exactly match, in other words rows where no change has actually been made.
CREATE OR ALTER TRIGGER tr_BC_totalbinLoc
ON bincount
AFTER UPDATE
AS
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM inserted)
RETURN;
UPDATE o
SET
binlocation = i.totalbinlo
dateupdated = getutcdate()
FROM (
SELECT partnumber, totalbinlo
FROM inserted i
EXCEPT
SELECT partnumber, totalbinlo
FROM deleted d
) i
INNER JOIN OnHandInv o ON i.partnumber = i.PartNumber;
GO

SQL Server trigger to update one value in a table after insert into another table if criteria are met

Update: Just to eliminate one possible suggestion, I saved the first query as a view and substituted it in the trigger. No change in behavior has been observed. I have updated the code below to reflect the new arrangement.
I have just started working with SQL Server triggers and have had some success. The current bit that I'm working on has me confused.
I am working with two tables. One is an order header and the other contains the line items. The trigger is watching for a new row to be inserted in the line item table and, if the item added meets certain criteria, update a value in the order header table. When I break my query down and run it separately, I get what I expect. A list of orders that contain the items I am watching for.
--QUERY WORKS AS EXPECTED
SELECT ord_no, cmt_cd_1
FROM OrderHeader
WHERE ord_no IN
(SELECT DISTINCT OrderHeader.ord_no
FROM OrderLine INNER JOIN OrderHeader
ON OrderLine.ord_no = OrderHeader.ord_no
WHERE (OrderLine.item_no LIKE 'A%') OR
(OrderLine.item_no LIKE 'B%') OR
(OrderLine.item_no LIKE 'C30%'))
However, when I try to use this in a trigger, it doesn't work.
--TRIGGER DOES NOT UPDATE ANY RECORDS
CREATE TRIGGER add_cmt_cd
ON [OrderLine]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE OrderHeader
SET cmt_cd_1 = 'O'
FROM OrderHeader
INNER JOIN inserted ON OrderHeader.ord_no = inserted.ord_no
WHERE (OrderHeader.ord_no IN (SELECT OrderListView.ord_no
FROM OrderListView))
END
I have verified that the header record is created first and does exist before the line item records are created. I have another trigger on this table that works fine, though the value it updates is in the same table as the trigger.
--TRIGGER WORKS AS DESIGNED
CREATE TRIGGER add_line_cmt
ON [OrderLine]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE OrderLine
SET cmt_1 = 'Comment 1',
cmt_2 = 'Comment 2'
FROM OrderLine
INNER JOIN inserted ON inserted.ord_no = OrderLine.ord_no
WHERE (OrderLine.item_no IN (SELECT CAST(item_no AS CHAR) AS item
FROM ItemTable
WHERE (group = 3)))
END
I've been banging my head against the desk for 3 hours and I know it's something simple I've missed. I'm hoping some other sets of eyes will spot it before I go nuts.

SQL Server trigger multiple update rows

I am using a trigger on my table for update.
When I was updating 1 row, it works. But when I update 20 rows at once, the log only show 1 history update.
I want to show all updated rows. How to make it?
This my simple sql:
create trigger Triger_Update_Product
on product
for update
as begin
declare #first char(10)
declare #after char(10)
select #first = name from deleted
select #after = name from inserted
insert into historyproduk
select #first, #after, getdate()
end
You need to write the trigger in a set-based fashion and be aware that inserted and deleted will contain multiple rows - so these lines of code are really bad:
select #first = name from deleted
select #after = name from inserted
These select one arbitrary row from the update set - and they ignore all other rows. Don't do this!!
Try this instead:
create trigger Trigger_Update_Product
on product
for update
as begin
insert into historyproduk
select d.name, i.name, getdate()
from deleted d
inner join inserted i on d.PrimaryKey = i.PrimaryKey
end
You need to join the Inserted and Deleted pseudo tables on the primary key and then grab the Name from the old (deleted) and new (Inserted) and insert those - along with the date/time stamp - into the history table in a single INSERT ... SELECT .... statement to handle multiple rows being updated

trigger updates entire table even on single-row update

The following triggers are meant to automate updates when a row is updated in a locations table. Changes can occur one row at a time, or 1-n many rows at a time. However, when updating a single row, while the "locations_geteditdate" is enabled, a new time stamp is written to all 28K rows in the locations table. I know I'm missing something obvious here, thanks for the help.
ALTER TRIGGER [dbo].[locations_geteditdate]
ON [dbo].[TBL_LOCATIONS]
instead of update
AS
begin
declare #recs INT
select #recs = COUNT(*)
from dbo.TBL_LOCATIONS a
join inserted i on i.Location_ID = a.Location_ID
if #recs > 0
update dbo.TBL_LOCATIONS
SET EditDate = GETDATE()
end
GO
alter TRIGGER [dbo].[locations_move_topo]
ON [dbo].[TBL_LOCATIONS]
for update
AS
BEGIN
update dbo.TBL_LOCATIONS
set topo_name = dbo.TLU_TOPO_BOUNDS.name
FROM dbo.TBL_LOCATIONS
inner join dbo.TLU_TOPO_BOUNDS
on dbo.TBL_LOCATIONS.Location_ID = dbo.TBL_LOCATIONS.Location_ID
where (TLU_TOPO_BOUNDS.Shape.STContains(TBL_LOCATIONS.SHAPE) = 1) ;
END
Accepted answer:
alter TRIGGER [dbo].[locations_geteditdate]
ON [dbo].[TBL_LOCATIONS]
for update
as
begin
update dbo.TBL_LOCATIONS
SET EditDate = GETDATE()
from dbo.TBL_LOCATIONS locn
inner join inserted i on i.location_id = locn.Location_ID
end
GO
In your if condition (in locations_geteditdate) you have no where clause; therefore it is including all records:
if #recs > 0
update dbo.TBL_LOCATIONS
SET EditDate = GETDATE()
WHERE ???
end
You correctly used the inserted table to see what had been updated but only to identify a record count
So reading the code you've put in the trigger, it looks like what you're trying to do is just apply a timestamp to the table to show that when it has been updated.
You have at least these options for this:
1. If you don't actually need a recognisable datetime in there you can use a timestamp field instead of a datetime and get it automatically updated.
2. If you can control where updates are performed to the table you can just set EditDate there (i.e. in stored procedures)
However, assuming that you want a recognisable datetime and you can't control where updates to the table are happening which is why you're implementing a trigger rather than just have a proc set EditDate, you need to go forward with one of the two types of trigger:
A) So if you persist with an "instead of" trigger you need to understand that it replaces the update that would have happened. So its incumbent upon you to then do the work that it was going to. You check column by column what has changed:
e.g.
IF UPDATE (price)
BEGIN
UPDATE t
SET price = i.price
FROM TBL_LOCATIONS t join inserted i
ON i.locn_id = t.locn_id
END
... repeat for each column (you can merge the updates if it makes sense)
B) Alternatively you can change to an "after" trigger, allow the update to happen (so you don't have to code column by column to check what's been updated) BUT YOU MUST then have a check on the EditDate column and NOT perform an update if its the EditDate column that has changed. If you don't do this you'll be in an infinite loop - your proc calls the trigger which calls the trigger etc
i.e. something like:
IF NOT UPDATE(EditDate)
BEGIN
UPDATE dbo.TBL_LOCATIONS
SET EditDate = GETDATE()
FROM dbo.TBL_LOCATIONS locn
INNER JOIN inserted i on i.locn_id = locn.locn_id
END

How to prevent updates to a table, with an exception for one situation

I have a table that contains records that can become part of a bill. I can tell which ones are already part of a bill because the table has a BillId column that gets updated by the application code when that happens. I want to prevent updates to any record that has a non-null BillId. I'm thinking that the following should take care of that:
CREATE TRIGGER [Item_Update_AnyBilled]
ON [dbo].[Item]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #AnyBilled BIT;
SELECT TOP(1) #AnyBilled = 1
FROM inserted i
JOIN deleted d ON i.ItemId = d.ItemId
WHERE d.BillId IS NOT NULL;
IF COALESCE(#AnyBilled, 0) = 1 BEGIN
RAISERROR(2870486, 16, 1); -- Cannot update a record that is part of a bill.
ROLLBACK TRANSACTION;
END;
END;
However, there is one more wrinkle. The Item table also has a DATETIME Modified column, and a trigger that updates it.
CREATE TRIGGER [dbo].Item_Update_Modified
ON [dbo].[Item]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE a
SET Modified = getdate()
FROM Item a JOIN inserted i ON i.ItemId = a.ItemId
END
With these triggers in place, adding an Item to a Bill always causes the RAISERROR to fire. Presumably because when the BillId is populated, Item_Update_AnyBilled lets it through because the deleted.BillId is NULL, but the Item_Update_Modified then gets executed, and that secondary change causes Item_Update_AnyBilled to get executed again, and this time deleted.BillId is no longer NULL.
How can I prevent updates to the Item table except in the case where the BillId is being populated or when the only change is to the Modified column?
I'd prefer a solution that didn't require me to compare the inserted and deleted values of every column (or use COLUMNS_UPDATED()) as that would create a maintenance issue (someone would have to remember to update the trigger any time a new column is added to or deleted from the table). I am using SQL Server 2005.
Why not use an INSTEAD OF trigger? It requires a bit more work (namely a repeated UPDATE statement) but any time you can prevent work, instead of letting it happen and then rolling it back, you're going to be better off.
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS
(
SELECT 1 FROM inserted i
JOIN deleted AS d ON i.ItemId = d.ItemId
WHERE d.BillId IS NULL -- it was NULL before, may not be NULL now
)
BEGIN
UPDATE src
SET col1 = i.col1 --, ... other columns
ModifiedDate = CURRENT_TIMESTAMP -- this eliminates need for other trigger
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON i.ItemId = src.ItemId
AND (criteria to determine if at least one column has changed);
END
ELSE
BEGIN
RAISERROR(...);
END
END
GO
This doesn't fit perfectly. The criteria I've left out is left out for a reason: it can be complex to determine if a column value has changed, as it depends on the datatype, whether the column can be NULL, etc. AFAIK the built-in trigger functions can only tell if a certain column was specified, not whether the value actually changed from before.
EDIT considering that you're only concerned about the other columns that are updated due to the after trigger, I think the following INSTEAD OF trigger can replace both of your existing triggers and also deal with multiple rows updated at once (some without meeting your criteria):
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE src SET col1 = i.col1 --, ... other columns,
ModifiedDate = CURRENT_TIMESTAMP
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON src.ItemID = i.ItemID
INNER JOIN deleted AS d
ON i.ItemID = d.ItemID
WHERE d.BillID IS NULL;
IF ##ROWCOUNT = 0
BEGIN
RAISERROR(...);
END
END
GO

Resources