I need to create an update trigger on a table that it also updates the table if certain inserted values are present. Is this possible? I have tried and when the inserted value is present and fires the trigger the trigger does an update and fires itself again. Eventually - deadlock. Is there a way to accomplish this safely?
The Code:
CREATE TRIGGER dbo.trg_updateaddress on dbo.Customers
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #original_source varchar(50)
SELECT #original_source = address_source FROM deleted
IF EXISTS (SELECT * FROM inserted i
INNER JOIN deleted d
ON i.cust_id = d.cust_id
WHERE i.address_source IS NOT NULL
AND (i.[address] <> d.[address]
OR i.address2 <> d.address2
OR i.city <> d.city
OR i.[state] <> d.[state]
OR i.zip <> d.zip
OR i.country <> d.country
OR i.phone <> d.phone
OR i.cell <> d.cell
OR i.email <> d.email))
BEGIN
UPDATE customers
SET address_changed = GETDATE()
END
ELSE
BEGIN
UPDATE customers
SET address_source = #original_source
END
END
One thing that you can do is to disable trigger recursion in your database:
USE [master]
GO
ALTER DATABASE [yourDbName] SET RECURSIVE_TRIGGERS OFF WITH NO_WAIT
GO
This will prevent all triggers in the database from triggering themselves directly, while still allowing them to fire other triggers.
I normally recommend this setting anyway, as recursive triggers is not normally what you want, and a very specialized behavior.
You can prevent the trigger from firing on itself with NESTED_LEVEL
Put this after SET NOCOUNT ON;
IF trigger_nestlevel() < 2
This will prevent the trigger from firing on it's own update.
Note: trigger_nestlevel starts counting at 1 for the first update, so if you want the trigger to only fire once then set it to 2, if you want it to fire twice then set this to 3.
Related
I am running into what seems to be a permission issue.
I have a database table that I created with a trigger that runs after a row update. These are the commands I used to create the trigger:
CREATE TRIGGER [dbo].[my_table_u]
ON [dbo].[my_table]
after UPDATE
AS
BEGIN
SET nocount ON;
UPDATE my_table
SET
last_updated_by = ( Host_name() + Suser_name() ),
last_updated_dt = Getdate()
FROM my_table
INNER JOIN inserted ON my_table.id = inserted.id
END
ALTER TABLE [dbo].[my_table] ENABLE TRIGGER [my_table_u]
GO
I am able to insert and delete rows from my_table, but whenever I go to update my_table, I get the following SQL error:
Msg 1088, Level 16, State 13, Procedure my_table_u, Line 16
[Batch Start Line 0] Cannot find the object "my_table"
because it does not exist or you do not have permissions.
However, if I drop and recreate the trigger without the last ENABLE TRIGGER command, like so:
CREATE TRIGGER [dbo].[my_table_u]
ON [dbo].[my_table]
after UPDATE
AS
BEGIN
SET nocount ON;
UPDATE my_table
SET
last_updated_by = ( Host_name() + Suser_name() ),
last_updated_dt = Getdate()
FROM my_table
INNER JOIN inserted ON my_table.id = inserted.id
END
/*** NO ENABLE TRIGGER COMMAND HERE ***/
GO
Then I am able to update rows without error.
Would anyone have any ideas what is happening here? Maybe some tips where I can start investigating?
Answering my own question.
The issue was that the ALTER TABLE [dbo].[my_table] ENABLE TRIGGER [my_table_u] line in the original CREATE TIGGER command ended up being part of the update trigger. That is, whenever this trigger ran, it also tried to run ALTER TABLE...ENABLE TRIGGER as well, and my user didn't have ALTER permissions granted.
This was confirmed when I granted the user ALTER permissions on my_table, and the problem went away.
The "correct" solution, as pointed out by Dale K in the comments, is to leave out the ENABLE TRIGGER command completely, or, alternatively, make sure it comes after the GO statement that creates the trigger.
I am completely new to SQL Server 2008 and I wrote a trigger and would like to be executed only of hassubproduct and spdisplaytype columns are updated or inserted and if they have a value and are not empty.
Any help is appreciated.
CREATE TRIGGER [dbo].[hassubproductcheck]
ON [dbo].[products]
WITH EXECUTE AS CALLER
FOR INSERT, UPDATE
AS
BEGIN
UPDATE products
SET hassubproduct = LTRIM(RTRIM(hassubproduct))
UPDATE products
SET spdisplaytype = LTRIM(RTRIM(spdisplaytype))
END
GO
Something along these lines is probably more like what you want.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].[products]
WITH EXECUTE AS CALLER FOR INSERT, UPDATE
AS BEGIN
Update p
set hassubproduct = LTRIM(RTRIM(i.hassubproduct))
, spdisplaytype = LTRIM(RTRIM(i.spdisplaytype))
from Products p
join inserted i on i.PrimaryKey = p.PrimaryKey
where i.hassubproduct > ''
OR i.spdisplaytype > ''
END
You have to use the below code to define the trigger. This will help for updated. The same way we need to create one for inserted as well.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE(PREVIOUS)' [TABLE-UPDATE] FROM DELETED
GO
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE (LATEST)' [TABLE-UPDATE] FROM DELETED
GO
Instead of trigger i am planning to write a procedure which we can run using job which will work same way as TRIGGER
with these two tables in the same way.
how can i do that?
here are my tables with column names
1.tblcal
ID(int,not null)
UID(varchar(10),null)
Desc(varchar(200),null)
Date(datetime,null)
avbl(varchar(5),null)
2.tblEvent
ID(int,notnull)
UID(varchar(10),null)
Desc(varchar(200),null)
Date(datetime,null)
Down is my trigger on tblEvent..
ALTER TRIGGER [dbo].[trU] ON [dbo].[tblEvent]
FOR INSERT
AS
Declare #CuID char(6),
#CuDesc char(40),
#CuDate datetime
SET NOCOUNT ON
Select #CuID = i.UID , #CuDesc=i.Desc, #CuDate=i.Date From Inserted i
If(#CuDesc !='available')
Begin
Update tblCal set avbl='Out', Desc=#CurDesc where cadate=#CuDate and UID=#CuID
ENd
SET NOCOUNT OFF
I have another problem with Desc column.Desc which are going to be in and out Basically we need to update tblcal differently for different descriptions;in that case I don't think trigger is that reliable;Means for example for 10 Desc we need to update in and for other 10 we need to update out
Actually every thursday on the tblevent data is loaded once its loaded it fired a trigger and will update in tblcal.
but my client is looking for a procedure which we can schedule as a job after the tblevent entry done on Thursday.
How can i do with stored procedure?
Procedure
CREATE PROCEDURE dbo.usp_UpdateEventData
AS
BEGIN
SET NOCOUNT ON;
UPDATE C
SET c.avbl = 'Out'
,c.[Desc] = e.[Desc]
FROM [dbo].tblCal C
INNER JOIN [dbo].[tblEvent] e ON c.[UID] = e.[UID]
AND c.cadate = e.[Date] --<-- check if you only want
WHERE e.[Desc] <> 'available' -- to join on date not datetime
END -- CAST both columns to DATE
Also if you are keeping your Trigger as it is you will need to modify the trigger definition to handle multiple Inserts, You can use the same logic as in this procedure to update your trigger definition.
Trigger Fix
ALTER TRIGGER [dbo].[trU] ON [dbo].[tblEvent]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE C
SET c.avbl = 'Out'
,c.[Desc] = i.[Desc]
FROM [dbo].tblCal C
INNER JOIN inserted i ON c.[UID] = i.[UID]
AND c.cadate = i.[Date]
WHERE i.[Desc] <> 'available'
END
basically i am looking for logic to generate insert or update statement in string variable from trigger. suppose when people just update 'N' of fields like update statement....then my trigger will fire and from that trigger i want build what update statement was issued by user and store in string variable
the same way i want to build insert statement from trigger too but i am not getting logic. so if anyone has any idea or sample code then please share with me to achieve my goal.
ALTER TRIGGER WSContent AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
DECLARE #Action VARCHAR(10)
DECLARE #PKValue INT
DECLARE #TableName VARCHAR(50)
SET #TableName='website_content'
IF EXISTS(SELECT * FROM INSERTED)
BEGIN
IF EXISTS(SELECT * FROM DELETED)
BEGIN
SET #Action ='U';
SELECT #PKValue=ContentNumber from DELETED
END
ELSE
BEGIN
SET #Action ='I';
SELECT #PKValue=ContentNumber from INSERTED
END
END
ELSE
BEGIN
SET #Action = 'D';
SELECT #PKValue=ContentNumber from DELETED
END;
INSERT INTO [ContentChangeLog]([PkValue],[TableName],[Action],ActionDate)
VALUES(#PKValue,#TableName,#Action,GetDate())
SET NOCOUNT OFF;
Like many people, you misunderstand how a trigger works. When you insert, update or delete multiple records, the trigger is called once and the tables deleted/inserted can contain multiple records, not 1 for each record effected. You need to rewrite this assuming you have multiple records in those tables.
INSERT INTO [ContentChangeLog]([PkValue],[TableName],[Action],ActionDate)
SELECT ContentNumber, #TableName, 'I', GETDATE()
FROM INSERTED i
WHERE not exists(SELECT TOP 1 1 FROM DELETED WHERE ContentNumber = i.ContentNumber)
Here is an example of the Inserted records only, you will want to do something similar for your updates and deletes.
Here is my trigger
Create TRIGGER [dbo].[tri_before_update]
ON [dbo].[test]
instead of update
AS
BEGIN
SET NOCOUNT ON;
if update (test_a)
begin
*.. my update & insert query*
end
END
create TRIGGER [dbo].[tri_before_update_price]
ON [dbo].[co_ticket]
instead of update
AS
BEGIN
SET NOCOUNT ON;
if update (t_price)
begin
insert into old_price_log (t_id,insert_time,process_id,old_t_price)
select i.t_id,getdate(),2,t_price
from Inserted i,co_ticket t where i.t_id = t.t_id
update t set t_price = i.t_price
from co_ticket t, inserted i
where t.t_id = i.t_id
end
else
begin
-- if update other then (t_price) then the update comand not execute.
-- example when i update t_cancel_flag or t_quantity and etc. end
END
This trigger execute perfectly when i update on column "test_a". HOWEVER, when i update other than column "test_a" it won't be execute. I know i can put "else" command, but i got a lot of column. sometimes will update two other column , sometimes three or four column. I don't wish to update all column everytime. Is it possible ELSE "then execute original query"?
I tried a lot different way but still can't work. :( Please HELP!
create TRIGGER [dbo].[tri_on_update_price]
ON [dbo].[co_ticket]
AS
BEGIN
SET NOCOUNT ON;
if update (t_price)
begin
insert into old_price_log (t_id,insert_time,process_id,old_t_price)
select d.t_id, getutcdate(),2,d.price
from deleted d
END
end
An ordinary after trigger will do just what you want: insert a log of the price change, if the price was updated. No need for INSTEAD OF. You need to look into the deleted pseudo-table to get the old price. Never store local times in a database.