I have a situation, have a table of around 30+ columns created a audit table with same number of columns and few more additional columns as description, updated date kind of columns. need a trigger to collect updated columns and collect them as description and need to form a sentence like so and so fields are updated into audit table. Help with sample trigger will be appreciated. Thanks in advance..
ALTER TRIGGER [dbo].[trg_reservationdetail_audit]
ON [dbo].[tblReservationDetails]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT reservationDetails_audits(
reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
isOnHoldResDet,
updated_at,
Operation,
Description)
SELECT
i.reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
i.isOnHoldResDet,
GETDATE(),
CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPD' ELSE 'INS' END
FROM
Inserted I
UNION ALL
SELECT
d.reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
d.isOnHoldResDet,
GETDATE(),
'DEL'
FROM Deleted d
WHERE NOT EXISTS (
SELECT * FROM Inserted
);
END
Expecting a sample trigger with a new column in audit table as description which will form a simple sentence to display to users.
The minor problem is comparing nullable columns. It can be done with an expression
ISNULL(NULLIF(i.Col, d.Col), NULLIF(d.Col, i.Col)) IS NOT NULL which is true if inserted and deleted row differs on Col .
INSERT reservationDetails_audits(
reservationDetailId,
reservationId,
rdCreationDate,
-- ..
updated_at,
Operation,
Description)
SELECT
i.reservationDetailId,
i.reservationId,
i.rdCreationDate,
-- ..
i.isOnHoldResDet,
GETDATE(),
CASE WHEN d.reservationDetailId IS NOT NULL THEN 'UPD' ELSE 'INS' END,
CASE WHEN d.reservationDetailId IS NOT NULL THEN
' updated cols: '
-- assumming reservationId is not nullable
+ CASE i.reservationId <> d.reservationId THEN 'reservationId ' ELSE '' END
-- assumming rdCreationDate is nullable
+ CASE ISNULL(NULLIF(i.rdCreationDate, d.rdCreationDate), NULLIF(d.rdCreationDate, i.rdCreationDate)) IS NOT NULL THEN 'rdCreationDate ' ELSE '' END
-- + ..
ELSE '' END
FROM Inserted I
LEFT JOIN deleted d on d.reservationDetailId = i.reservationDetailId
UNION ALL
-- delete oper query
;
Related
How can I implement this script in sql server:
DELIMITER |
CREATE TRIGGER after_update_animal AFTER UPDATE
ON Animal FOR EACH ROW
BEGIN
INSERT INTO Animal_histo (
id,
sexe,
date_naissance,
nom,
commentaires,
espece_id,
race_id,
mere_id,
pere_id,
disponible,
date_histo,
utilisateur_histo,
evenement_histo)
VALUES (
OLD.id,
OLD.sexe,
OLD.date_naissance,
OLD.nom,
OLD.commentaires,
OLD.espece_id,
OLD.race_id,
OLD.mere_id,
OLD.pere_id,
OLD.disponible,
NOW(),
CURRENT_USER(),
'UPDATE');
END |
DELIMITER ;
The deleted table holds the previous values before the update, the inserted table holds the new ones. For each row can be ommited as this will do a batch operation.
CREATE TRIGGER after_update_animal ON Animal AFTER UPDATE
AS
BEGIN
INSERT INTO Animal_histo (
id,
sexe,
date_naissance,
nom,
commentaires,
espece_id,
race_id,
mere_id,
pere_id,
disponible,
date_histo,
utilisateur_histo,
evenement_histo)
SELECT
OLD.id,
OLD.sexe,
OLD.date_naissance,
OLD.nom,
OLD.commentaires,
OLD.espece_id,
OLD.race_id,
OLD.mere_id,
OLD.pere_id,
OLD.disponible,
GETDATE(),
SYSTEM_USER,
'UPDATE'
FROM
deleted AS OLD
END
I have some trouble with entityFramework 4. Here is the thing :
We have a SQL server database. Every table have 3 instead of triggers for insert, update and delete.
We know EntityFramework has some issues to deal with theses triggers, that's why we added the following code at the end of triggers to force the rowCount :
for insert :
DECLARE #Identifier BIGINT;
SET #Identifier = scope_identity()
SELECT #Identifier AS Identifier
for update/delete :
CREATE TABLE #TempTable (temp INT PRIMARY KEY);
INSERT INTO #TempTable VALUES (1);
DROP TABLE #TempTable
It worked fine until now :
From an instead of insert trigger (let's say table A), I try to update a field of an other table (table B)
I know my update code perfectly work since a manual insert does the work. The issue shows up only when I'm using Entity framework.
I have the solution now, let's make a school case of this with a full example. :)
In this example, our application is an addressBook. We want to update the business Activity (IsActive column in Business)
everytime we add, update or delete a contact on this business. The business is considered as active if at least one of the contact
of the business is active. We record every state changements on the business in a table to have the full history.
So, we have 3 tables :
table Business (Identifier (PK Identity), Name, IsActive),
table Contact (Identifier (PK Identity), Name, IsActive, IdentifierBusiness)
table BusinessHistory (Identifier (PK Identity), IsActive, Date, IdentifierBusiness)
Here's are the triggers one we are interested in :
table Contact (trigger IoInsert):
-- inserting the new rows
INSERT INTO Contact
(
Name
,IsActive
,IdentifierBusiness
)
SELECT
t0.Name
,t0.IsActive
,t0.IdentifierBusiness
FROM
inserted AS t0
-- Updating the business
UPDATE
Business
SET
IsActive = CASE WHEN
(
(t0.IsActive = 1 AND Business.IsActive = 1)
OR
(t0.IsActive = 1 AND Business.IsActive = 0)
) THEN 1 ELSE 0
FROM
inserted AS t0
WHERE
Business.Identifier = t0.IdentifierBusiness
AND
t0.IsActive = 1
AND
Business.IsActive = 0
-- Forcing rowCount for EntityFramework
DECLARE #Identifier BIGINT;
SET #Identifier = scope_identity()
SELECT #Identifier AS Identifier
Table Business (trigger IoUpdate)
UPDATE
Business
SET
IsActive = 1
FROM
Contact AS t0
WHERE
Business.Identifier = t0.IdentifierBusiness
AND
t0.IsActive = 1
AND
Business.IsActive = 0
---- Updating BusinessHistory
INSERT INTO BusinessHistory
(
Date
,IsActive
,IdentifierBusiness
)
SELECT
DATE()
,t0.IsActive
,t0.Identifier
FROM
inserted AS t0
INNER JOIN
deleted AS t1 ON t0.Identifier = t1.Identifier
WHERE
(t0.Identifier <> t1.Identifier)
-- Forcing rowCount for EntityFramework
CREATE TABLE #TempTable (temp INT PRIMARY KEY);
INSERT INTO #TempTable VALUES (1);
DROP TABLE #TempTable
Table BusinessHistory :
-- Updating the business
UPDATE
Business
SET
IsActive = CASE WHEN
(
(t0.IsActive = 1 AND Business.IsActive = 1)
OR
(t0.IsActive = 1 AND Business.IsActive = 0)
) THEN 1 ELSE 0
FROM
inserted AS t0
WHERE
Business.Identifier = t0.IdentifierBusiness
AND
t0.IsActive = 1
AND
Business.IsActive = 0
-- inserting the new rows
INSERT INTO BusinessHistory
(
Date
,IsActive
,IdentifierBusiness
)
SELECT
DATE()
,t0.IsActive
,t0.Identifier
FROM
inserted AS t0
-- Forcing rowCount for EntityFramework
DECLARE #Identifier BIGINT;
SET #Identifier = scope_identity()
SELECT #Identifier AS Identifier
So, in a nutshell, what happened ?
We have 2 tables, Business and Contact. Contact is updating table Business on insert and update.
When Business is updated, it does an insert into BusinessHistory, which is storing the history of updates of table Business
,when the field IsActive is updated.
the thing is, even if I don't insert a new row in BusinessHistory, I launch an insert instruction and so, I go inside the instead of insert trigger of the table BusinessHistory. Of course, in the end of this one, there is a scope_identity(). You can use scope_identity only once, and it gives back the last identity inserted.
So, since I did not inserted any BusinessHistory, it was consuming the scope_identity of my newly inserted contact : the scope_identity of the instead of
insert of the contact table was empty !
How to isolate the issue ?
Using the profiler, you figure out that there are insert instruction in BusinessHistory when it should not be any of them.
Using the debugging, you will eventually end in the an insert trigger your are not supposed to be in.
How to fix it ?
Several alternatives here. What I did was to surround in table Business the insert of BusinessHistory by an If condition :
I want the insert to be inserted only if the statut "IsActive" has changed :
IF EXISTS
(
SELECT
1
FROM
inserted AS t0
INNER JOIN
deleted AS t1 ON t0.Identifier = t1.Identifier
WHERE
(t0.IsActive <> t1.IsActive)
)
BEGIN
INSERT INTO BusinessHistory
(
Date
,IsActive
,IdentifierBusiness
)
SELECT
DATE()
,t0.IsActive
,t0.Identifier
FROM
inserted AS t0
INNER JOIN
deleted AS t1 ON t0.Identifier = t1.Identifier
WHERE
(t0.IsActive <> t1.IsActive)
END
An other possibility is, in the trigger instead of insert of the table BusinessHistory, to surround the whole trigger by an IF EXISTS condition
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
----Trigger's code here !
END
How to avoid it ?
Well, use one of these fixes !
Avoiding scope_identity(), ##IDENTITY is more than enough in most of the cases ! In my company, we only use scope_identity because of EF 4 !
I know my english is not perfect, I can edit if it's not good enough, or if someone want to add something on this subject !
I have done a lot of looking around and have yet to find exactly what I am looking for. Sorry if this is a duplicate question, I did not see one that matched my needs.
I have an Application Table [UserInRoles] that holds 2 columns [UserID] and [RoleID]. The application manages this table with Inserts for new Users and Updates when a user switches to a different Role.
I have a History Table [UserRoleHistory] that has 4 columns [UserID], [RoleID_New], [RoleID_Old], [DateOfChange]
What I need to figure out is how to create a trigger that will insert a new row into [UserRoleHistory] every time [UserInRoles].[RoleId] changes. I want the old RoleId to be stored in the column [RoleId_Old] and the new RoleId to be stored in the column [RoleId_New]. Also the [UserId] needs to be stored and GETDATE() used to add value to [DateOfChange]
Also, (is this possible) I would like the trigger to notice when an Insert is made to the application table [UserInRoles] and save the data [UserId], [RoleId],GETDATE() into the History Table as [UserId], [RoleId_New] , GETDATE() and leave the [RoleId_Old] as a null value.
I am very new to triggers and am not sure how to proceed. I do not have permissions to remove a trigger if I mess it up so I have not tried to create one yet. Just wanted to get some expert input first. Thanks in advance for any and all who take the time to read and answer/comment on this.
****EDIT****
I have used your recommendations and this is what I finally came up with. This trigger has a bit more info in it that I originally asked for but after working with it and found the info I wanted in the history table was easy to get to I added it as I saw fit.
CREATE TRIGGER [dbo].[UserInRoles_Insert_Delete_Update] ON [dbo].[UsersInRoles]
AFTER INSERT,DELETE,UPDATE
AS
IF (SELECT COUNT(*) FROM inserted) > 0
BEGIN
IF (SELECT COUNT(*) FROM deleted) > 0
BEGIN
-- update!
INSERT INTO [dbo].[UserRole_History](WinNTLogin,UserID,RoleID_Old,RoleID_New,RoleName,DateOfChange,Operation)
SELECT (select distinct[LoweredUserName] from [dbo].[Users]
where (inserted.UserId = Users.UserId) or (deleted.UserId = Users.UserId))
,CASE
WHEN inserted.UserID IS NOT NULL THEN inserted.UserID
ELSE deleted.UserID
END
,deleted.RoleID
,inserted.RoleID
,(select distinct[RoleName] from [dbo].[Roles]
where (inserted.RoleId = Roles.RoleId) or (deleted.RoleId = Roles.RoleId))
,GETDATE()
,'U'
FROM inserted
FULL JOIN deleted
ON inserted.UserID = deleted.UserID
END
ELSE
BEGIN
-- insert!
INSERT INTO [dbo].[UserRole_History](WinNTLogin,UserID,RoleID_Old,RoleID_New,RoleName,DateOfChange,Operation)
SELECT (select distinct[LoweredUserName] from [dbo].[Users]
where (inserted.UserId = Users.UserId) or (deleted.UserId = Users.UserId))
,CASE
WHEN inserted.UserID IS NOT NULL THEN inserted.UserID
ELSE deleted.UserID
END
,deleted.RoleID
,inserted.RoleID
,(select distinct[RoleName] from [dbo].[Roles]
where (inserted.RoleId = Roles.RoleId) or (deleted.RoleId = Roles.RoleId))
,GETDATE()
,'I'
FROM inserted
FULL JOIN deleted
ON inserted.UserID = deleted.UserID
END
END
ELSE
BEGIN
-- delete!
INSERT INTO [dbo].[UserRole_History](WinNTLogin,UserID,RoleID_Old,RoleID_New,RoleName,DateOfChange,Operation)
SELECT (select distinct[LoweredUserName] from [dbo].[Users]
where (inserted.UserId = Users.UserId) or (deleted.UserId = Users.UserId))
,CASE
WHEN inserted.UserID IS NOT NULL THEN inserted.UserID
ELSE deleted.UserID
END
,deleted.RoleID
,inserted.RoleID
,(select distinct[RoleName] from [dbo].[Roles]
where (inserted.RoleId = Roles.RoleId) or (deleted.RoleId = Roles.RoleId))
,GETDATE()
,'D'
FROM inserted
FULL JOIN deleted
ON inserted.UserID = deleted.UserID
END
GO
NOTE: I used the same table names you did so do NOT run this against your database with the actual tables because I delete them.
Try this out. This will work for any update,insert, or delete. If a UserID is changed(which probably shouldn't happen), then it will simply look like a new UserId with a new RoleID was inserted. Let me know if you need anything else.
--If the tables exist, delete them
IF OBJECT_ID('UserInRoles') IS NOT NULL
DROP TABLE UserInRoles;
IF OBJECT_ID('UserRoleHistory') IS NOT NULL
DROP TABLE UserRoleHistory;
CREATE TABLE UserInRoles
(
UserID INT PRIMARY KEY,
RoleID INT
);
GO
CREATE TABLE UserRoleHistory
(
UserID INT,
RoleID_Old INT,
RoleID_New INT,
DateOfChange DATETIME
);
GO
CREATE TRIGGER trg_History ON UserInRoles
AFTER INSERT,DELETE,UPDATE
AS
INSERT INTO UserRoleHistory(UserID,RoleID_Old,RoleID_New,DateOfChange)
SELECT CASE
WHEN inserted.UserID IS NOT NULL THEN inserted.UserID
ELSE deleted.UserID
END,
deleted.RoleID,
inserted.RoleID,
GETDATE()
FROM inserted
FULL JOIN deleted
ON inserted.UserID = deleted.UserID
GO
INSERT INTO UserInRoles
VALUES (1,1);
INSERT INTO UserInRoles
VALUES (2,1);
INSERT INTO UserInRoles
VALUES (3,2);
GO
UPDATE UserInRoles
SET RoleID = 111
WHERE RoleID = 1;
GO
UPDATE UserInRoles
SET RoleID = 222
WHERE RoleID = 2;
GO
DELETE
FROM UserInRoles
WHERE UserID >= 1
GO
SELECT *
FROM UserRoleHistory
ORDER BY DateOfChange
Results:
UserID RoleID_Old RoleID_New DateOfChange
----------- ----------- ----------- -----------------------
1 NULL 1 2015-03-20 13:06:37.010
2 NULL 1 2015-03-20 13:06:37.010
3 NULL 2 2015-03-20 13:06:37.010
2 1 111 2015-03-20 13:06:37.047
1 1 111 2015-03-20 13:06:37.047
3 2 222 2015-03-20 13:06:37.050
3 222 NULL 2015-03-20 13:06:37.063
2 111 NULL 2015-03-20 13:06:37.063
The MSDN create trigger documentation (https://msdn.microsoft.com/en-GB/library/ms189799.aspx) has some handy examples towards the end. I've shamelessly copy/pasted the first one below as an example:
IF OBJECT_ID ('Sales.reminder2','TR') IS NOT NULL
DROP TRIGGER Sales.reminder2;
GO
CREATE TRIGGER reminder2
ON Sales.Customer
AFTER INSERT, UPDATE, DELETE
AS
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'AdventureWorks2012 Administrator',
#recipients = 'danw#Adventure-Works.com',
#body = 'Don''t forget to print a report for the sales force.',
#subject = 'Reminder';
GO
This is relevant to you because it shows
How to fire the trigger after a transaction has occurred (specifically one whic should have resulted in a change - more on that later)
How to fire off your own sql afterwards. In your case this would be inserting into another table rather than sending an email.
Shows you how to drop/re-create a trigger if it already exists (for testing purposes - assuming you're testing this first, not applying straight to live).
If you need to specifically compare a value to make absolutely certain that the value has changed, then there's a previous post on something similar here. Essentialy you write
INSTEAD OF INSERT
in the place of
AFTER INSERT
then you can do any conditional logic or CRUD operations as necessary.
Lastly because you can pop a procedure in a trigger (as shown above), you can add some additional complexity to any triggers you'll create, thus abstracting out any logic that would otherwise be in the trigger itself.
I have the following SQL Merge statement.
DECLARE #TmpTable TABLE (BusinessBaseID int, BatchID uniqueidentifier, SupplierID int, SupplierVenueID varchar(200), AddressID int,[Action] varchar(50))
DECLARE #noop int; -- needed for the NO-OP below
Declare #TestOp Varchar(max)
Set #TestOp = 't'
-- Insert into BusinessBase retrieving all inserted BusinessBaseIDs mappings via tmptable
-- Another SQL blck goes here to insert records into TEMP table
MERGE Business.Address AS t
USING (SELECT tmp.BusinessBaseID, tmp.BatchID, tmp.SupplierID, tmp.SupplierVenueID,
v.Name, v.AddressLine1, v.AddressLine2, v.City, v.County, v.PostalCode,
v.Latitude,
v.Longitude,
dbo.GetVenueId(v.AddressLine1, v.AddressLine2, v.City, v.County, v.PostalCode, 'GB', v.Latitude, v.Longitude) as VenueId
FROM #TmpTable as tmp INNER JOIN Supplier.VenueImport as v
ON tmp.BatchID = v.BatchID AND tmp.SupplierID = v.SupplierID AND tmp.SupplierVenueID = v.SupplierVenueID
WHERE (tmp.BatchID = 'D7F369F1-A66A-4440-8D4B-2F521F672916') AND (tmp.SupplierID = 17)
) AS s
ON S.VenueId >0
WHEN MATCHED THEN
UPDATE SET #TestOp = #TestOp + ':' +convert(varchar, S.VenueId)+'|'+Convert(varchar,t.AddressId) -- the NO-OP instead of update
WHEN NOT MATCHED BY TARGET THEN
INSERT (AddressLine1, AddressLine2, City, StateProvince,PostalCode,CountryCode,Lat,Long)
VALUES (AddressLine1, AddressLine2, City, County, PostalCode, 'GB', Latitude, Longitude)
OUTPUT s.BusinessBaseID, s.BatchID, s.SupplierID, s.SupplierVenueID,ISNULL(INSERTED.AddressID,deleted.addressId),$action INTO #TmpTable;
Select #TestOp;
Select #Temp where [Action] = 'Update'
Above query returning all the rows (except newly inserted records). Where as it suppose to return only 1 record as S.VenueId is greater than 0 for only one record.
dbo.GetVenueId is a function which returns an integer. It will be > 0 for existing records and -1 for not existing records.
Could somebody point me where I am doing wrong.
Thanks,
Naresh
Change the call dbo.GetVenueId to the following
CASE dbo.GetVenueId(v.AddressLine1, v.AddressLine2, v.City, v.County, v.PostalCode, 'GB', v.Latitude, v.Longitude) WHEN -1 THEN -1 ELSE 0 END as VenueId
So It will act well
I am rather confused, because my trigger in SQL Server cannot insert the value what I expected it would. The situation is as follows :
I have transaction table which can have two types of transactions in it - saldo and buy. If it is saldo, the trigger in transaction table will insert the amount of the transaction total to saldo table, but with Debit in its saldo_type field.
So if the case in transaction table is buy, the same amount will be inserted in saldo table, but with credit in its saldo_type field.
What confuses me is that the trigger will only insert the correct amount of value if the situation is saldo, but not if the situation is buy
What did I do wrong? Here is the code:
declare #last_saldo int
declare #transaction_ammount int
set #last_saldo = (select sum(saldo_ammount) from saldo)
if #last_saldo is null set #last_saldo=0
set #transaction_ammount = (select transaction_ammount from inserted)
IF (select transaction_type from inserted) = 'Saldo'
begin
/* this will insert correct amount */
INSERT INTO saldo
(id_transaction,transaction_type,saldo_ammount,saldo)
SELECT id_transaction,'Debit',#transaction_ammount,#last_saldo + #transaction_ammount
FROM inserted
RETURN
END else IF (select transaction_type from inserted)='Buy'
begin
/* this will not insert the correct ammount. It will always zero! */
INSERT INTO saldo
(id_transaction,transaction_type,saldo_ammount,saldo)
SELECT id_transaction,'Credit',#transction_ammount,(#last_saldo - #transaction_ammount)
FROM inserted
RETURN
END
Many Thanks!
Perhaps you can refactor your trigger to be a bit simpler:
declare #last_saldo int
select #last_saldo = ISNULL(sum(saldo_ammount),0)
from saldo
INSERT INTO saldo
(id_transaction,transaction_type,saldo_ammount,saldo)
SELECT id_transaction,
CASE WHEN transaction_type = 'Saldo'
THEN 'Debit'
ELSE 'Credit'
END,
transaction_ammount,
CASE WHEN transaction_type = 'Saldo'
THEN (#last_saldo + transaction_ammount)
ELSE (#last_saldo - transaction_ammount)
END
FROM inserted
RETURN
Is the problem of zero solved with this code? If not, determine what #last_saldo and transaction_ammount values are. That'll lead you to the root of your problem.
Caveat: be aware that inserted can have more than one row!