I am creating two triggers. One to catch the inserted values for the Rewards table and one to catch the deleted values for the Rewards table. The triggers main function is to record what type of change was made into the the audit table.
Rewards Table:
enter image description here
Audit Table:
enter image description here
The SQL for the audit_insert_trigger is :
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(6)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM inserted)
IF EXISTS (SELECT * FROM inserted)
BEGIN
SELECT #type_change = 'Inserted'
END
INSERT INTO audit_rewards
VALUES (
#category, USER_NAME(), GETDATE(), #type_change)
END
CREATE TRIGGER tr_rewards_delete
ON Rewards AFTER UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(6)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM deleted)
IF EXISTS (SELECT * FROM deleted)
BEGIN
SELECT #type_change = 'Deleted'
END
INSERT INTO audit_rewards
VALUES (
#category, USER_NAME(), GETDATE(), #type_change)
END
My idea was to just replace where "inserted" was and put "deleted". I'm not really understanding 1. the logic as to why that will not work and 2. How to get the deleted values into the audit table and record that it was a delete.
Cannot make it in one trigger. Must be two independent triggers.
Your insert trigger is incorrect. I think it should be:
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
INSERT INTO audit_rewards
SELECT 'INSERTED, USER_NAME(), GETDATE(), category
FROM inserted;
END;
The DELETED trigger would basically be the same. Never assume that inserted and deleted have only a single row.
Figured out the answer:
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(2)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM inserted)
SELECT #type_change = 'Inserted'
INSERT INTO audit_rewards
VALUES (#category, USER_NAME(), GETDATE(), #type_change)
END
CREATE TRIGGER tr_rewards_delete
ON Rewards AFTER DELETE, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(2)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM deleted)
SELECT #type_change =
INSERT INTO audit_rewards
VALUES (#category, USER_NAME(), GETDATE(), #type_change)
END
Related
I'm creating a trigger that would run when a specific row is inserted into TableName AND if it hasn't run yet for the day. To achieve this, what we thought of was to include an insert statement in the trigger that would insert the current date into the LOG table, which should prevent it from running the rest of the day. After this, it would run a batchfile which would do its own thing.
The problem we're having is that the trigger doesn't insert anything in the LOG table, and it just reruns itself perpetually, including the batchfile.
Here's our Trigger:
USE [Database]
GO
DROP TRIGGER [schema].[TriggerName]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [schema].[TriggerName]
ON [schema].[TableName]
AFTER INSERT,UPDATE
AS
SET NOCOUNT ON;
if exists(select * from inserted where Column1 = 'Priority' and STATUS = 'F' )
BEGIN
if not exists(select * from Database.Schema.Logs where [Date] = convert(date,getdate()))
BEGIN
--insert to logs table where [Date] default is [Date] = convert(date,getdate())
BEGIN
insert into Database.Schema.Logs(status) values ('Trigger is running')
END
-- Execute Procedure
BEGIN
EXEC master..xp_CMDShell '"Procedure.bat"'
END
END
END
GO
Make sure that you are inserting a [Date] value into the Logs table (or there is a default for the [Date] column). Every time the trigger fires (and there is no entry for the current date) a new entry is created but with NULL date...and this keeps going on forever.
create table dbo.TableNameTest
(
Column1 varchar(200),
Status char(1),
CreatedOn datetime default(getdate())
);
create table dbo.LogsTest
(
Status varchar(50),
[Date] date default(getdate())
)
go
CREATE OR ALTER TRIGGER dbo.trg_ins_upd_TableNameTest on dbo.TableNameTest
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--no inserted rows...
if not exists(select * from inserted)
begin
--...return
return;
end
--no eligible inserted/updated rows..
if not exists(select * from inserted where Column1 = 'Priority' and STATUS = 'F')
begin
--..return
return
end
--bat already executed for the day
if exists (select * from dbo.LogsTest where [Date] = convert(date,getdate()))
begin
return
end
--there is a concurrency issue here..
--if the bat execution takes a long time...the trigger could fire the bat twice
--maybe insert processing log status and delete the log if cmdshell is not successful ??
declare #res int;
--at this point..execute the bat
print 'bat is going to be executed from within the trigger';
EXEC #res=master..xp_cmdshell 'c:\testnew\whoamii.bat', no_output ;
if #res = 0
begin
insert into dbo.LogsTest(Status, [Date]) values('Trigger executed the bat', getdate())
end
END
go
insert into dbo.TableNameTest (Column1, Status) values ('random', 'R') --bat not executed
insert into dbo.TableNameTest (Column1, Status) values ('Priority', 'F') --bat not executed
go
--2nd time
insert into dbo.TableNameTest (Column1, Status) values ('random', 'R') --bat not executed
insert into dbo.TableNameTest (Column1, Status) values ('Priority', 'F') --bat not executed
go
select *
from dbo.TableNameTest;
select *
from dbo.LogsTest
go
--cleanup
drop table dbo.TableNameTest;
drop table dbo.LogsTest
go
I have a table table1 with some data:
create table table1
(
c1 varchar(20),
c2 varchar(20)
)
insert into table1 values('1','A')
insert into table1 values('2','B')
insert into table1 values('3','C')
insert into table1 values('4','D')
insert into table1 values('5','E')
insert into table1 values('6','F')
Now I created another table with the same structure called table2 :
create table table2
(
c1 varchar(20),
c2 varchar(20)
)
Then I created an After Insert trigger on table2 :
CREATE TRIGGER trgAfterInsert
ON [dbo].[table2]
FOR INSERT
AS
declare #c1 varchar(20);
declare #c2 varchar(20);
declare #audit_action varchar(100);
select #c1 = i.c1 from inserted i;
select #c2 = i.c2 from inserted i;
set #audit_action = 'Inserted Record -- After Insert Trigger.';
insert into table2_Audit(c1, c2, Audit_Action, Audit_Timestamp)
values(#c1, #c2, #audit_action, getdate());
PRINT 'AFTER INSERT trigger fired.'
GO
There is a problem that when i copy all data of table1 to tabe2 then in Audit table only one record show.It not show all inserted record.
I use this query for copy the record in table2:-
insert into table2(c1,c2) select c1,c2 from table1
Your trigger fires once for each insert statement issued against this table. not one for each row inserted in the table. Hence if more than one row is inserted you trigger definition should be able to handle more than one row.
Instead of using variables to capture values from inserted table and then insert them in table two, simply select from the inserted table and insert the data into Table2_audit.
A trigger to handle this would look something like........
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
insert into table2_Audit (c1,c2,Audit_Action,Audit_Timestamp)
SELECT C1
, C2
, 'Inserted Record -- After Insert Trigger.'
, GETDATE()
FROM inserted ;
PRINT 'AFTER INSERT trigger fired.'
END
GO
Trigger is fired once per entire operation, change your code to:
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
DECLARE #audit_action VARCHAR(100) = 'Inserted Record -- After Insert Trigger.';
INSERT INTO table2_Audit(c1,c2,Audit_Action,Audit_Timestamp)
SELECT i.c1, i.c2, #audit_action, GETDATE()
FROM inserted i;
END
Second don't use PRINT inside trigger;
More info
The behavior you are seeing is by design. DML triggers in SQL Server
are statement level triggers - they are fired once after
INSERT/UPDATE/DELETE/MERGE irrespective of how many rows are affected
by the DML statement. So you should write your logic in the trigger to
handle multiple rows in the inserted/deleted tables
I have a stored procedure that is meant to update two tables at once.
My problem here is that the first table has an auto-incrementing ID column ("commentID") and my second table has a relationship on this so I need the newly created ID from the first INSERT in order to make the second INSERT.
I tried the following which I can save without errors but it doesnt execute as it should and does not update the tables as intended.
Can someone tell me what I am doing wrong here ?
My SQL:
ALTER PROCEDURE [dbo].[MOC_UpdateComment]
#imgID int,
#commentID int = '999999',
#comment nvarchar(1000),
#lastUpdate nvarchar(50),
#modBy varchar(50)
AS
BEGIN
DECLARE #temp AS TABLE
(
commentID int
)
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT INTO MOC_BlogComments
(
imgID,
comment
)
OUTPUT inserted.commentID INTO #temp(commentID)
SELECT #imgID,
#comment
INSERT INTO MOC_LogComments
(
commentID,
lastUpdate,
modTime,
modBy
)
SELECT commentID,
#lastUpdate,
GETDATE(),
#modBy
FROM #temp
COMMIT TRANSACTION;
END
DECLARE #imgID INT,
#commentID INT = '999999',
#comment NVARCHAR(1000),
#lastUpdate NVARCHAR(50),
#modBy VARCHAR(50)
DECLARE #MORC_BlogComments AS TABLE
(
id INT IDENTITY(1, 1) NOT NULL,
imgid INT,
comment VARCHAR(100)
)
DECLARE #MORC_LogComments AS TABLE
(
commentid INT,
lastupdate DATETIME,
modtime DATETIME,
modby VARCHAR(100)
)
DECLARE #TEMP AS TABLE
(
commentid INT
)
SET nocount ON;
BEGIN TRANSACTION;
INSERT INTO #MORC_BlogComments
(imgid,
comment)
output inserted.id
INTO #TEMP(commentid)
VALUES (#imgID,
#comment)
INSERT INTO #MORC_LogComments
(commentid,
lastupdate,
modtime,
modby)
SELECT commentid,
#lastUpdate,
Getdate(),
#modBy
FROM #temp
SELECT *
FROM #MORC_LogComments
Function SCOPE_IDENTITY() returns the identity of last insert operation. You can use it to get the value which you need to use in second INSERT statement
You can use it like this in your statement:
INSERT INTO MORC_BlogComments (imgID, comment)
VALUES (#imgID, #comment)
INSERT INTO MORC_LogComments (commentID, lastUpdate, modTime, modBy)
VALUES (SCOPE_IDENTITY(), #lastUpdate, GETDATE(), #modBy)
Im doing a reservation application and was wondering how would i handle the following scenario; if a booking has 2 or more "extra" items on it how do i create a SP or a trigger even to handle this? the SP i have now works fine for a booking with a single extra item on it. Im i making any sense?
ALTER PROCEDURE [dbo].[CreateBooking]
#DateFrom datetime,
#DateTo datetime,
#RoomID int,
#PersonID int,
#ProductID int,
#OrderAmount int
AS
BEGIN TRANSACTION
SET NOCOUNT ON;
INSERT INTO booking(created_on, startdate, enddate, room_id, person_id)
VALUES (getdate(), #DateFrom, #DateTo, #RoomID, #PersonID)
IF ##error <> 0
ROLLBACK TRANSACTION
ELSE
INSERT INTO booking_details (booking_id,prod_id,order_amount)
VALUES (SCOPE_IDENTITY(), #ProductID, #OrderAmount)
COMMIT TRANSACTION
You can pass an xml parameter then loop through it and write them.
CREATE PROCEDURE
SelectByIdList(#productIds xml) AS
DECLARE #Products TABLE (ID int)
INSERT INTO #Products (ID) SELECT
ParamValues.ID.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id')
as ParamValues(ID)
SELECT * FROM
Products INNER JOIN
#Products p ON Products.ProductID = p.ID
I have the following table:
CREATE TABLE FE_USER
(
userid int identity (321,4) CONSTRAINT userid_pk PRIMARY KEY,
username varchar(40)
);
Its corresponding history table is
CREATE TABLE FE_USER_HIST
(
userid int,
username varchar(40),
v_action varchar(50)
);
Every time an insert or update is occurred on FE_USER table, i need to input
this newly inserted record or the updated record into the history table.
How can i write the trigger in t-sql?
Here is my pseducode, but i get alot of errors:
CREATE OR REPLACE TRIGGER user_to_hist
AFTER UPDATE OR DELETE
ON FE_USER
FOR EACH ROW
DECLARE
v_action varchar(50);
BEGIN
v_action := CASE WHEN UPDATING THEN 'UPDATE' ELSE 'DELETE' END;
INSERT INTO FE_USER_HIS(userid, username, v_action)
SELECT :OLD.userid, :OLD.username, v_action
FROM .......;
END;
SQL Server does not support CREATE OR REPLACE unfortunately. You need to use either CREATE or ALTER dependant upon what action you are doing.
Also it does not have row level triggers. All the affected rows are available to you in pseudo tables called INSERTED or DELETED
The simplest way would probably be 2 separate triggers.
For Insert
CREATE TRIGGER dbo.tr_i_FE_USER
ON dbo.FE_USER
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO FE_USER_HIST
SELECT userid,username, 'inserted' AS v_action
FROM INSERTED
END
And for Update
CREATE TRIGGER dbo.tr_u_FE_USER
ON dbo.FE_USER
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO FE_USER_HIST
SELECT userid,username, 'updated' AS v_action
FROM INSERTED /*If you wanted the previous value instead
you could use FROM DELETED */
END
Just following up on the approach I mentioned in the comments
CREATE TRIGGER dbo.tr_iud_FE_USER
ON dbo.FE_USER
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO FE_USER_HIST
SELECT
ISNULL(i.UserId,d.UserId) AS UserId,
CASE WHEN i.UserId IS NULL THEN d.UserName
ELSE i.UserName
END AS UserName,
CASE WHEN i.UserId IS NULL THEN 'deleted'
WHEN d.UserId IS NULL THEN 'inserted'
ELSE 'updated'
END AS v_action
from INSERTED i FULL OUTER JOIN DELETED d
ON i.UserId = d.USerId
END
If you want to capture all 3 actions I'd use 2 triggers rather than one:
CREATE TRIGGER user_to_historyA ON FE_USER
AFTER INSERT, UPDATE
AS
DECLARE #action varchar(50)
SET #action = CASE WHEN UPDATE(username) THEN 'UPDATE' ELSE 'INSERT' END
INSERT INTO FE_USER_HIS(userid, username, v_action)
SELECT userid, username, #action
FROM inserted
GO
CREATE TRIGGER user_to_historyB ON FE_USER
AFTER DELETE
AS
INSERT INTO FE_USER_HIS(userid, username, v_action)
SELECT userid, username, 'DELETE'
FROM deleted
GO
EDIT (per Martin's correct comment regarding use of the useless UPDATE() function and to consolidate into 1 trigger)
ALTER TRIGGER user_to_history ON FE_USER
AFTER INSERT, UPDATE, DELETE
AS
DECLARE #action varchar(50)
IF EXISTS(SELECT username FROM inserted)
BEGIN
SET #action = CASE WHEN EXISTS(SELECT username FROM deleted) THEN 'UPDATE' ELSE 'INSERT' END
INSERT INTO FE_USER_HIS(userid, username, v_action)
SELECT userid, username, #action
FROM inserted
END
ELSE
INSERT INTO FE_USER_HIS(userid, username, v_action)
SELECT userid, username, 'DELETE'
FROM deleted
GO