Recording info in SQL Server trigger - sql-server

I have a table called dsReplicated.matDB and a column fee_earner. When that column is updated, I want to record two pieces of information:
dsReplicated.matDB.mt_code
dsReplicated.matDB.fee_earner
from the row where fee_earner has been updated.
I've got the basic syntax for doing something when the column is updated but need a hand with the above to get this over the line.
ALTER TRIGGER [dsReplicated].[tr_mfeModified]
ON [dsReplicated].[matdb]
AFTER UPDATE
AS
BEGIN
IF (UPDATE(fee_earner))
BEGIN
print 'Matter fee earner changed to '
END
END

The problem with triggers in SQL server is that they are called one per SQL statement - not once per row. So if your UPDATE statement updates 10 rows, your trigger is called once, and the Inserted and Deleted pseudo tables inside the trigger each contain 10 rows of data.
In order to see if fee_earner has changed, I'd recommend using this approach instead of the UPDATE() function:
ALTER TRIGGER [dsReplicated].[tr_mfeModified]
ON [dsReplicated].[matdb]
AFTER UPDATE
AS
BEGIN
-- I'm just *speculating* here what you want to do with that information - adapt as needed!
INSERT INTO dbo.AuditTable (Id, TriggerTimeStamp, Mt_Code, Old_Fee_Earner, New_Fee_Earner)
SELECT
i.PrimaryKey, SYSDATETIME(), i.Mt_Code, d.fee_earner, i.fee_earner
FROM Inserted i
-- use the two pseudo tables to detect if the column "fee_earner" has
-- changed with the UPDATE operation
INNER JOIN Deleted d ON i.PrimaryKey = d.PrimaryKey
AND d.fee_earner <> i.fee_earner
END
The Deleted pseudo table contains the values before the UPDATE - so that's why I take the d.fee_earner as the value for the Old_Fee_Earner column in the audit table.
The Inserted pseudo table contains the values after the UPDATE - so that's why I take the other values from that Inserted pseudo-table to insert into the audit table.
Note that you really must have an unchangeable primary key in that table in order for this trigger to work. This is a recommended best practice for any data table in SQL Server anyway.

Related

SQL Server trigger failing for row inserts in quick succession

I have looked around on SO and found many similar questions:
SQL Server A trigger to work on multiple row inserts
SQL trigger multiple insert update
Trigger to handle multiple row inserts and updates
update multiple rows with trigger after insert (sql server)
Trigger not working when inserting multiple records
But I am still having issues with my trigger to update multiple rows when Inserting multiple rows into a table.
Outline of code
I have a Reservation table which has a ReservationID and TourComponentID columns respectively. When I insert into the reservations table I have the following trigger to update the TourComponent table with the ReservationID from the row just inserted into the reservation table with matching TourComponentID:
CREATE TRIGGER [TR_Reservation_CurrentReservation] ON [Reservation] AFTER INSERT AS
UPDATE tc
SET tc.[CurrentReservationId] = I.ReservationID
FROM [tour].[TourComponent] tc
JOIN INSERTED I on I.TourComponentID = tc.TourComponentID
End
This trigger works perfectly when updating one tourComponent to have a new reservation (inserting one row into the reservation table). However if I try update multiple tour components (inserting multiple rows into the reservation table to update multiple rows in the TourComponent table) only the first tour component gets updated, any rows.
Other answers and research has shown me that
Triggers are NOT executed once per row but rather as a set based
operation so executed only ONCE for the entire DML operation. So you
need to treat it like any other update date with join statement.
So I would have expected my joining on the INSERTED table to have handled multiple rows or have I misunderstood this?
Interestingly if I log the trigger variables for TourComponentID, ReservationID and INSERTED rowcount to a temp table foo I can see two records are inserted into my temp table, each with a rowcount of 1.
Using sql profiler to catch the actual sql executed at runtime and running this manually against the database I get two rows updated as desired. It is only when using Entity Framework to update the database ie running the application do I find only one row is updated.
I have tried logging the values to a table FOO in the trigger
INSERT INTO FOO (TourComponentID, ReservationID, Rowcounts )
SELECT i.TourComponentID, I.ReservationID, 1 --#ReservationId
FROM
INSERTED I
This logs two rows with a rowcount of 1 each time and the correct tourcomponentsID and reservationID but the TourComponent table still only has one row updated.
Any suggestions greatly appreciated.
UPDATE
Tour component ID's are passed as strings in an Ajax post to the MVC Action where tour component models are populated and then passed to be updated one at a time in the code
public void UpdateTourComponents(IEnumerable<TourComponent> tourComponents)
{
foreach (var tourComponent in tourComponents)
{
UpdateTourComponent(tourComponent);
}
}
here is the call to UpdateTourComponent
public int UpdateTourComponent(TourComponent tourComponent)
{
return TourComponentRepository.Update(tourComponent);
}
and the final call to Update
public virtual int Update(TObject TObject)
{
Dictionary<string, List<string>> newChildKeys;
return Update(TObject, null, out newChildKeys);
}
So the Inserts are happening one at a time, hence my trigger is being called once per TourComponent. This is why when I count the ##Rowcount in INSERTED and log to Foo I get value of 1. When I run the inserts manually I get the correct expected results so I would agree with #Slava Murygin tests that the issue is probably not with the trigger itself. I thought it might be a speed issue if we are firing the requests one after the other so I put a wait in the trigger and in the code but this did not fix it.
Update 2
I have used a sql profiler to capture the sql that is run when only the first insert triggers work.
Interestingly when the EXACT same sql is then run in SQL Management Studio the trigger works as expected and both tour components are updated with the reservation id.
Worth mentioning also that all constraints have been removed off all tables.
Any other suggestions what might be causing this issue?
You have different problem than that particular trigger. Try to look at the table name you are updating "[tour].[TourComponent]" or "[dbo].[TourComponent]".
I've tried your trigger and it perfectly works:
use TestDB
GO
IF object_id('Reservation') is not null DROP TABLE Reservation;
GO
IF object_id('TourComponent') is not null DROP TABLE TourComponent;
GO
CREATE TABLE Reservation (
ReservationID INT IDENTITY(1,1),
TourComponentID INT
);
GO
CREATE TABLE TourComponent (
CurrentReservationId INT,
TourComponentID INT
);
GO
CREATE TRIGGER [TR_Reservation_CurrentReservation] ON [Reservation] AFTER INSERT AS
UPDATE tc
SET tc.[CurrentReservationId] = I.ReservationID
FROM [TourComponent] tc
JOIN INSERTED I on I.TourComponentID = tc.TourComponentID
GO
INSERT INTO TourComponent(TourComponentID)
VALUES (1),(2),(3),(4),(5),(6)
GO
INSERT INTO Reservation(TourComponentID)
VALUES (1),(2),(3),(4),(5),(6)
GO
SELECT * FROM Reservation
SELECT * FROM TourComponent
So the underlying problem was down to Entity Framework.
this.Property(t => t.CurrentReservationId).HasColumnName("CurrentReservationId");
Is one property for the SQL Data access layer. This was being cached and was causing the data being read out of the db to not be the latest current, thus if we have an insert in the Reservations table the second insert will be overwritten by the cached values which in my case were NULL.
Changing the line to this resolves the problem and makes the trigger work as expected.
this.Property(t => t.CurrentReservationId).HasColumnName("CurrentReservationId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
See more info on HasDatabaseGeneratedOption

SQL Server Trigger to alter different table

I’m trying to create a trigger to change the value of a column in table B if it finds the information in a column in table A.
An example of my database is below:
[TableA],
itemID
[TableB],
itemID
itemInStock
Once a user creates an entry in Table A declaring an itemID, the trigger needs to change the TableB.itemInStock column to ‘Yes’
I’m still learning SQL so excuse me if I’ve missed something, let me know if you need any more info.
I understand there are better ways of doing this but I've been told I need to do this using a trigger.
I've attempted a few different things, but as it stands nothing is working, below is the current solution I have however this updates all itemInStock rows to 'Yes', where as I only want the ones to update where the TableB.itemID matches the itemID entered in TableA.
ALTER TRIGGER [itemAvailability] ON [dbo].[TableA] FOR
INSERT
AS
BEGIN
UPDATE [dbo].[TableB] set itemInStock = 'Yes' WHERE
TableB.itemID = itemID
END
Two problems -
you're not looking at the Inserted pseudo table which contains the
newly inserted rows
you're assuming the trigger is called once per row - this is not the
case, the trigger is called once per statement and the Inserted
pseudo table will contain multiple rows - and you need to deal with
that
So, your code should look like this -
ALTER TRIGGER [itemAvailability] ON [dbo].[TableA]
FOR INSERT
AS
UPDATE TB
SET itemInStock = 'Yes'
FROM [dbo].[TableB] TB JOIN inserted I
on TB.itemID = I.itemID

why this trigger always loop?

i try to use trigger , when i insert in table penduduk, then in table muda will insert too, actually this trigger is work, but this trigger become loop, when i insert (123) in penduduk, then insert again (1234) then the result will be (123,1234,123,1234) , am i right to use 'From penduduk' ? or i should use 'FROM inserted' ? cause when i use FROM inserted there is no result
my query like this
CREATE TRIGGER auto_muda
ON penduduk
FOR INSERT
AS
INSERT INTO muda (noktp,tgl_lahir)
SELECT noktp, tgl_lahir FROM penduduk
WHERE umur < 27
You are currently grabbing all matching rows from penduduk rather than the inserted logical table. You might change your trigger to the following:
CREATE TRIGGER auto_muda
ON penduduk
FOR INSERT
AS
INSERT INTO muda (noktp,tgl_lahir)
SELECT noktp, tgl_lahir FROM inserted -- Use inserted, not the base table
WHERE umur < 27
From MSDN:
DML triggers use the deleted and inserted logical (conceptual) tables. They are structurally similar to the table on which the trigger is defined, that is, the table on which the user action is tried. The deleted and inserted tables hold the old values or new values of the rows that may be changed by the user action.
Use
Select noktb, tgl_lahir from inserted
If you're interested in inserting based on the newly inserted data. Your select seems to be capable of returning many items as it stands, which explains the "looping"/duplication.

SQL Server Trigger that works - fires just once

I want to do some calculations when my table data is changed. However, I am updating my table manually and copy pasting about 3000 rows in once. That makes my trigger work 3000 times, but I want it to do the trigger only once.
Is there a way to do that ?
Thanks.
What's your statement?
If you have multiple inserts then it will fire for each insert you are running. If you want it to execute only once for multiple inserts you must:
Write your insert on a single statement such as insert into foo select * from bar
Your trigger can't be for each row
Other possibility might be:
Disable the trigger
Perform your insertions
Put the trigger code in a stored procedure
Run your stored procedure
If by 'manually' you mean you are copying and pasting into some User Interface tool (like an Access dataGrid) or something like that, then the tool may be issuing one insert statement per row, and in that case you are out of luck the database trigger will be executed once per insert statement. As other answers have mentioned, if you can insert the rows directly into the database, using a single insert statement, then the trigger will only fire once.
The issue is caused because you are manually pasting the 3000 rows. You really have 2 solutions. You can turn off the trigger by doing this:
ALTER TABLE tablename DISABLE TRIGGER ALL
-- do work here
ALTER TABLE tablename ENABLE TRIGGER ALL
and then run the contents of your trigger at then end or you can put your 3000 columns into a temp table and then insert them all at once. This will only setup 1 trigger. If this isn't enough please give us more info on what you are trying to do.
Your trigger will not fire 3000 times if you are modifying 3000 rows in a single statement. Your trigger will fire once and there will be 3000 rows in your virtual 'deleted' table.
If you are limited in how you can import the data into the system that does a single insert per row, I would suggest that you import data into an intermediate table then do the insert into the final table from the intermediate one.
There is a better way.
Re-link the tables that your trigger created or altered, compare them against what's expected to be changed or added and avoid the trigger with a simple WHERE clause.
eg - this trigger I use to INSERT a record, but only once based on a column value existing (#ack).
DECLARE #ack INT
SELECT #ack = (SELECT TOP 1 i.CUSTOM_BOOL_1 AS [agent_acknowledged] FROM inserted AS i)
IF #ack = 1
BEGIN
INSERT INTO TABLEA(
COLA, COLB, etc
)
SELECT
COLA, COLB, etc
from inserted as i
LEFT JOIN TABLEA AS chk --relink to the INSERT table to see if the record already exists
ON chk.COLA = i.COLA
AND chk.COLB = i.COLB
AND etc
WHERE chk.ID IS NULL --and here we say if NOT found, then continue to insert
END

SQL Server 2005 insert trigger not inserting enough records

I have a table in a SQL Server 2005 database with a trigger that is supposed to add a record to a different table whenever a new record is inserted. It seems to work fine, but if I execute an Insert Into on the master table that uses a subquery as the source of the values, the trigger only inserts one record in the other table, even though multiple records were added to the master. I want the trigger to fire for each new record added to the master table. Is that possible in 2005?
The insert I'm doing is:
INSERT INTO [tblMenuItems] ([ID], [MenuID], [SortOrder], [ItemReference], [MenuReference], [ConcurrencyID]) SELECT [ID], [MenuID], [SortOrder], [ItemReference], [MenuReference], [ConcurrencyID] FROM [IVEEtblMenuItems]
Here is what the trigger looks like:
CREATE TRIGGER [dbo].[tblMenuItemInsertSecurity] ON [dbo].[tblMenuItems]
FOR INSERT
AS
Declare #iRoleID int
Declare #iMenuItemID int
Select #iMenuItemID = [ID] from Inserted
DECLARE tblUserRoles CURSOR FASTFORWARD FOR SELECT [ID] from tblUserRoles
OPEN tblUserRoles
FETCH NEXT FROM tblUserRoles INTO #iRoleID
WHILE (##FetchStatus = 0)
BEGIN
INSERT INTO tblRestrictedMenuItems(
[RoleID],
[MenuItemID],
[RestrictLevel])
VALUES(
#iRoleID,
#iMenuItemID,
1)
FETCH NEXT FROM tblUserRoles INTO #iRoleID
END
CLOSE tblUserRoles
Deallocate tblUserRoles
Your trigger is only using the first row from 'Inserted'. This is a common misunderstanding when dealing with SQL triggers for the first time. The trigger fires per update not per row.
For example if you do the following:-
update products
set title = 'geoff de geoff'
this would update all the products, but a trigger on the product table would only fire once.
The Inserted 'table' you get in trigger would contain all the rows. You must either loop through Inserted with a cursor, or better join Inserted to the table you are updating.
Please lookup multi row consideration for triggers
What is with the cursor inside a trigger? Learn how to program set based, cursors are Evil in T-SQL and should only be used to defragment/update stats/other maintanance a bunch of tables
The trigger only fires once for each INSERT statment executed - not once for each record inserted.
In your trigger you can access the 'virtual' table called inserted for details of the records inserted.
ie:
SELECT COUNT(*) FROM inserted
Will return the number of inserted records.
I just want to second #Gordon Bell on his answer...
"Catch" the values the very moment they are being inserted. You do not really need the cursor in this situation (or maybe you have a reason?).
A simple TRIGGER might be all you need:
http://dbalink.wordpress.com/2008/06/20/how-to-sql-server-trigger-101/

Resources