I have two tables - card and journal. The card's id (code) is a linker in journal(card-code)
.One card can have many journals and I need to update a field(time) in this card table from journal with trigger - I have wrote the current trigger but it has errors.Please help me to locate it.
AS
DECLARE VARIABLE currentTimeOfChanging timestamp;
begin
select current_timestamp from rdb$database into currentTimeOfChanging;
update card
set card.lastupdate = currentTimeOfChanging;//!error
where card.code = journal.cardcode
end
I think you want trigger like this:
CREATE TRIGGER SetLastUpdateTS FOR journal
ACTIVE AFTER UPDATE
AS
BEGIN
UPDATE card SET lastupdate = CURRENT_TIMESTAMP WHERE code = NEW.cardcode;
END
Some comments:
you don't need the currentTimeOfChanging temp variable, you can use the current_timestamp directly in the UPDATE statement;
you have to prefix the variables with ":" and there is no ";" after the variable in the UPDATE statement in the PSQL code. So your original statement should have been
update card set card.lastupdate = :currentTimeOfChanging where ...
instead of journal.cardcode you use NEW and/or OLD context variables to refer the values of the current statement (which triggered the trigger).
Related
For updating a field called 'last_modified' I'm trying to update this field automatically using a trigger, in stead of changing all update-statements. The field should only be updated when a field value has changed. Problem here is that you have to maintain the triggers if a table field is added, removed or renamed. The fields are stored in rdb$relation_fields, no problem. But building a query comparing the old and new value dynamically is.
create trigger test for test_table active before update position 0 as
declare variable fn char(31);
begin
for select rdb$field_name from rdb$relation_fields where rdb$relation_name = 'test_table' into :fn do
begin
if ('old.'||:fn <> 'new.'||:fn) then
begin
new.last_modified = current_timestamp;
break;
end
end
end
Problem here is that 'old.'||:fn and 'new.':fn not really are comparing values, but literal strings, so the value of the fields cannot be compared. I've seen over here Firebird - get all modified fields inside a trigger that a trigger is attached to a system table, something I don't want to.
Is this fully-automated way of updating the 'last_modified' field not possible in this way? Or do I have to create a stored procedure that deletes al triggers and then recreates them with new fields, once I perform a update on the database (using this code http://www.firebirdfaq.org/faq133/).
I have an SQL trigger as below
GO
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END
If i am right as per the trigger for every record inserted/updated when trigger runs it will check for the YEAR In the ExpirationDate column of that record and if the value is less than 1971 then it will update it with the date in the update query.
The weird thing is it is not working as expected.
The if condition does not seem to work.
Is anything wrong with this particular trigger.
YES - there's definitely something fundamentally wrong with the way you wrote this trigger.
SQL Server (assuming that's what you're using) will fire the trigger not once per row (as many folks including yourself) seem to think - the trigger is fired once per batch which might update or insert 10, 20, 50 rows at once.
Therefore, the Inserted pseudo table inside the trigger can (and will!) contain multiple rows - and in that case - what exactly does your statement here select?
Select #expDate = inserted.ExpirationDate from inserted
Either you'll just get one randon row (out of 50) and handle that (and ignore all 49 other rows), or you'll get an error....
You need to write your triggers with that in mind - you MUST always assume that Inserted (and Deleted) will contain multiple rows!
So you need to change your trigger to be something like:
CREATE TRIGGER ExpDateCheckCard
ON dbo.Card
FOR Insert, Update
AS
UPDATE dbo.Card
SET ExpirationDate = '1900-01-01 00:00:00'
FROM dbo.Card c
INNER JOIN inserted i ON i.RecordID = c.RecordID
WHERE YEAR(i.ExpirationDate) < 1971
(I've also changed your old-style JOIN syntax (comma-separated list of tables) to the ANSI standard that's been in place since 1992 - please do not use the comma-separated list of tables! Use proper ANSI JOINs. See this blog post for more background info: Bad habits to kick : using old-style JOINs)
try with this :
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
BEGIN
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 ) Begin
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
end
END
You have to try this code
CREATE TRIGGER ExpDateCheckCard AFTER INSERT,Update ON Card
FOR EACH ROW
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
update Card
set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END
I have the following trigger
First trigger:
ALTER TRIGGER [dbo].[DIENSTLEISTUNG_Update]
ON [dbo].[DIENSTLEISTUNG]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #intNewID int
INSERT INTO [DIENSTLEISTUNG]
(DESCRIPTION, QUANTITY,
PRICE, AZ_MO, AZ_DI,AZ_MI,AZ_DO,AZ_FR,
AZ_SA,AZ_SO,DIENSTLEISTUNGSART_ID,
UPDATE_USER, UPDATE_DATE,
PERMISSIONS, KONTRAKTPOSITION,ITEMNUMBER,
PRIORITY, VALID)
SELECT i.DESCRIPTION, i.QUANTITY, i.PRICE, i.AZ_MO,
i.AZ_DI,i.AZ_MI,i.AZ_DO,i.AZ_FR,
i.AZ_SA,i.AZ_SO,i.SERVICETYPE_ID, i.UPDATE_USER,GETDATE(),
i.PERMISSIONS, i.KONTRAKTPOSITION,i.ITEMNUMBER, i.PRIORITY, 'Y'
FROM INSERTED i
JOIN deleted d ON i.ID=d.ID
WHERE i.PRICE<>d.PRICE
or i.DESCRIPTION<>d.DESCRIPTION
IF ( UPDATE (PRICE) OR UPDATE (DESCRIPTION) )
UPDATE S
SET s.VALID = 'N'
FROM SERVICE s
JOIN INSERTED i ON I.ID = S.ID
IF UPDATE(PRIORITY)
UPDATE s
SET s.PRIORITY= i.PRIORITY
FROM SERVICE s
JOIN INSERTED i ON i.ID = s.ID
SET NOCOUNT OFF;
END
The first Trigger copies an entire row with a new ID if a change in the original row happens, also the trigger set a flag. The old row gets the flag VALID = 'N' and the new row gets the flag VALID = 'Y'. The trigger only creates a new row if PRICE or DESCRIPTION are updated. So far so good.
My problem is that if I want to update the PRIORITY in the new row the trigger fires again and sets the flag to VALID = 'N'. That should not happen. I want only to update the priority without creating a new row or update a another column.
Thanks for help
You cannot prevent a trigger from firing - if it's present and not disabled, it will fire. That's how triggers work.
What you can do is check inside your trigger which columns have been updated. So you could do something like this in your one single trigger:
CREATE TRIGGER [dbo].[DIENSTLEISTUNG_Update]
ON [dbo].[DIENSTLEISTUNG]
FOR UPDATE
AS
IF UPDATE(PRICE)
... (do what you need to do if PRICE is updated)...
IF UPDATE(DESCRIPTION)
... (do what you need to do if DESCRIPTION is updated)...
IF UPDATE(PRIORITY)
... (do what you need to do if PRIORITY is updated)...
Use the UPDATE() function to check whether a given column has been updated - and if so, act on it. See the MSDN docs on how to use the UPDATE() function.
You can make triggers fire only on certain columns or one colomn.
like this.
CREATE TRIGGER tr_something ON myTable
FOR INSERT, UPDATE
AS
IF UPDATE(myColumn)
BEGIN
-- do what you want
END
post below gives more details, seems I'm to slow :)
What you can do is set the context info of the session which you are in like this:
SET Context_Info 0x55555
And then in your trigger check for the context info to decide what to do:
DECLARE #Cinfo VARBINARY(128)
SELECT #Cinfo = Context_Info()
IF #Cinfo = 0x55555
RETURN
I need a SQL trigger that would zero pad a cell whenever its inserted or updated. Was curious if its best practice to append two strings together like I'm doing in the update command. Is this be best way to do it?
CREATE TRIGGER PadColumnTenCharsInserted ON Table
AFTER INSERT
AS
DECLARE
#pad_characters VARCHAR(10),
#target_column NVARCHAR(255)
SET #pad_characters = '0000000000'
SET #target_column = 'IndexField1'
IF UPDATE(IndexField1)
BEGIN
UPDATE Table
SET IndexField1 = RIGHT(#pad_characters + IndexField1, 10)
END
GO
Your padding code looks fine.
Instead of updating every row in the table like this:
UPDATE Table
update just the row that triggered the trigger:
UPDATE updated
Also, you've still got some extraneous code -- everything involving #target_column. And it looks like you're not sure if this is an INSERT trigger or an UPDATE trigger. I see AFTER INSERT and IF UPDATE.
Two questions:
What are you doing with #target_column? You declare it and set it with a column name, but then you never use it. If you intend to use the variable in your subsequent SQL statements, you may need to wrap the statements in an EXECUTE() or use sp_executesql().
The syntax "UPDATE Table..." is OK for your update statement assuming that "Table" is the name of the table you are updating. What seems to be missing is a filter of some kind. Or did you really intend for that column to be updated for every row in the whole table?
One way to handle this would be to declare another variable and set it with the PK of the row that is updated, then use a where clause to limit the update to just that row. Something like this:
DECLARE #id int
SELECT #id = Record_ID FROM INSERTED
-- body of your trigger here
WHERE Record_ID = #id
I like your padding code. It looks good to me.
I've got the following trigger on a table for a SQL Server 2008 database. It's recursing, so I need to stop it.
After I insert or update a record, I'm trying to simply update a single field on that table.
Here's the trigger :
ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
ON [dbo].[tblMedia]
BEFORE INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #IdMedia INTEGER,
#NewSubject NVARCHAR(200)
SELECT #IdMedia = IdMedia, #NewSubject = Title
FROM INSERTED
-- Now update the unique subject field.
-- NOTE: dbo.CreateUniqueSubject is my own function.
-- It just does some string manipulation.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(#NewSubject) +
CAST((IdMedia) AS VARCHAR(10))
WHERE tblMedia.IdMedia = #IdMedia
END
Can anyone tell me how I can prevent the trigger's insert from kicking off another trigger again?
Not sure if it is pertinent to the OP's question anymore, but in case you came here to find out how to prevent recursion or mutual recursion from happening in a trigger, you can test for this like so:
IF TRIGGER_NESTLEVEL() <= 1/*this update is not coming from some other trigger*/
MSDN link
I see three possibilities:
Disable trigger recursion:
This will prevent a trigger fired to call another trigger or calling itself again. To do this, execute this command:
ALTER DATABASE MyDataBase SET RECURSIVE_TRIGGERS OFF
GO
Use a trigger INSTEAD OF UPDATE, INSERT
Using a INSTEAD OF trigger you can control any column being updated/inserted, and even replacing before calling the command.
Control the trigger by preventing using IF UPDATE
Testing the column will tell you with a reasonable accuracy if you trigger is calling itself. To do this use the IF UPDATE() clause like:
ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
ON [dbo].[tblMedia]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #IdMedia INTEGER,
#NewSubject NVARCHAR(200)
IF UPDATE(UniqueTitle)
RETURN;
-- What is the new subject being inserted?
SELECT #IdMedia = IdMedia, #NewSubject = Title
FROM INSERTED
-- Now update the unique subject field.
-- NOTE: dbo.CreateUniqueSubject is my own function.
-- It just does some string manipulation.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(#NewSubject) +
CAST((IdMedia) AS VARCHAR(10))
WHERE tblMedia.IdMedia = #IdMedia
END
TRIGGER_NESTLEVEL can be used to prevent recursion of a specific trigger, but it is important to pass the object id of the trigger into the function. Otherwise you will also prevent the trigger from firing when an insert or update is made by another trigger:
IF TRIGGER_NESTLEVEL(OBJECT_ID('dbo.mytrigger')) > 1
BEGIN
PRINT 'mytrigger exiting because TRIGGER_NESTLEVEL > 1 ';
RETURN;
END;
From MSDN:
When no parameters are specified, TRIGGER_NESTLEVEL returns the total
number of triggers on the call stack. This includes itself.
Reference:
Avoiding recursive triggers
ALTER DATABASE <dbname> SET RECURSIVE_TRIGGERS OFF
RECURSIVE_TRIGGERS { ON | OFF }
ON Recursive firing of AFTER triggers is allowed.
OFF Only direct recursive firing of AFTER triggers is not allowed. To
also disable indirect recursion of
AFTER triggers, set the nested
triggers server option to 0 by using
sp_configure.
Only direct recursion is prevented when RECURSIVE_TRIGGERS is set to OFF.
To disable indirect recursion, you
must also set the nested triggers
server option to 0.
The status of this option can be determined by examining the
is_recursive_triggers_on column in the
sys.databases catalog view or the
IsRecursiveTriggersEnabled property of
the DATABASEPROPERTYEX function.
I think i got it :)
When the title is getting 'updated' (read: inserted or updated), then update the unique subject. When the trigger gets ran a second time, the uniquesubject field is getting updated, so it stop and leaves the trigger.
Also, i've made it handle MULTIPLE rows that get changed -> I always forget about this with triggers.
ALTER TRIGGER [dbo].[tblMediaAfterInsert]
ON [dbo].[tblMedia]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
-- If the Title is getting inserted OR updated then update the unique subject.
IF UPDATE(Title) BEGIN
-- Now update all the unique subject fields that have been inserted or updated.
UPDATE tblMedia
SET UniqueTitle = dbo.CreateUniqueSubject(b.Title) +
CAST((b.IdMedia) AS VARCHAR(10))
FROM tblMedia a
INNER JOIN INSERTED b on a.IdMedia = b.IdMedia
END
END
You can have a separate NULLABLE column indicating whether the UniqueTitle was set.
Set it to true value in a trigger, and have the trigger do nothing if it's value is true in "INSERTED"
For completeness sake, I will add a few things. If you have a particular after trigger that you only want to run once, you can set it up to run last using sp_settriggerorder.
I would also consider if it might not be best to combine the triggers that are doing the recursion into one trigger.