I have a table with 3 fields [ID, Name, LastUpdated].
LastUpdated has a default value of "GetDate() so it automatically fills itself when a new record is added.
When I instead run an UPDATE on TABLE, I would like to have this field reset itself to the current GetDate().
CREATE TRIGGER dbo.Table1_Updated
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Table1 SET LastUpdated = GETDATE()
END
GO
But because I don't have a WHERE Clause, ALL records get updated.
QUESTION:
Where would I get the value of the ID of the updated record on a UPDATE Trigger?
Would the fact that I'm updating a field of the table inside the Trigger, re-call a new Trigger event (and so on) ?
From 'INSERTED', table INSERTED is common to both the INSERT, UPDATE trigger.
CREATE TRIGGER dbo.Table1_Updated
ON dbo.Table1
FOR INSERT, UPDATE /* Fire this trigger when a row is INSERTed or UPDATEd */
AS BEGIN
UPDATE dbo.Table1 SET dbo.Table1.LastUpdated = GETDATE()
FROM INSERTED
WHERE inserted.id=Table1.id
END
Update table1
set LastUpdated = getdate()
from inserted i, table1 a
where i.pk1 = a.pk1
CREATE TRIGGER dbo.refreshModifyDate
ON tStoreCategoriesImages
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
update t set t.ModifyDate = getdate() from tStoreCategoriesImages t
inner join inserted i on i.ID = t.ID
END
GO
Related
A trigger works on the first part of a function but not the second.
I'm trying to set up a trigger that does two things:
Update a field - geom - whenever the fields lat or lon are updated, using those two fields.
Update a field - country - from the geom field by referencing another table.
I've tried different syntaxes of using NEW, OLD, BEFORE and AFTER conditions, but whatever I do, I can only get the first part to work.
Here's the code:
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
update schema.table a set geom = st_setsrid(st_point(a.lon, a.lat), 4326);
update schema.table a set country = b.name
from reference.admin_layers_0 b where st_intersects(a.geom,b.geom)
and a.pk = new.pk;
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
AFTER INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH STATEMENT EXECUTE PROCEDURE update_geometries();
There is no new on a statement level trigger. (well, there is, but it is always Null)
You can either keep the statement level and update the entire a table, i.e. remove the and a.pk = new.pk, or, if only part of the rows are updated, change the trigger for a row-level trigger and only update the affected rows
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.geom = st_setsrid(st_point(NEW.lon, NEW.lat), 4326);
SELECT b.name
INTO NEW.country
FROM reference.admin_layers_0 b
WHERE st_intersects(NEW.geom,b.geom);
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
BEFORE INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH ROW EXECUTE PROCEDURE update_geometries();
I am trying to put together trigger which does multiple things:
before any insert updates automatically columns created_date and created_by for the inserted row and also inserts this row with updated values into archive (history) table
before any update updates automatically values edited_date and edited_by for the updated row and inserts this updated row (including updated values edited_date and edited_by) into archive table
before any delete...
How to write efficiently the before (or after) update trigger using adjusted NEW (or OLD) values? Here is my trigger:
create trigger my_trigger
before insert or update or delete on my_table
for each row execute procedure my_function();
And function where I would like to use updated new row:
create function my_function() returns trigger as $$
begin
if (tg_op = 'INSERT') then
return 'something done here';
elsif (tg_op = 'UPDATE') then
update NEW set edited_date = now(), edited_by = current_user;
insert into my_table_hist select NEW.*;
elsif (tg_op = 'DELETE') then
return 'something done here';
end if;
return null;
end;
$$ language plpgsql;
What I mean by this is before update I update NEW row columns to current time and current user, then the table gets updated to this new row with updated values, and finally this row with updated values is inserted also into archive (history) table. But it clearly doesn't work like that, so how should I rewrite this trigger function to make it work?
There is much amiss here:
trigger functions must return a row, typically (a modified) NEW, or NULL to abort the operation
you do not update NEW since it is not a table, but you assign to its columns, like
NEW.created := current_timestamp;
for BEFORE DELETE triggers NEW is NULL, since there is no new version of the row
Suppose you have a table with two timestamptz fields, created and updated.
table has a trigger like the following:
CREATE TRIGGER trig_table_on_update
BEFORE UPDATE ON table
FOR EACH ROW EXECUTE PROCEDURE func_table_on_update();
func_table_on_update is defined like the following:
CREATE OR REPLACE FUNCTION func_table_on_update()
RETURNS trigger AS
$$
BEGIN
NEW.updated = current_timestamp;
RETURN NEW;
END;
$$
How can I set created := updated from within another function? The problem is created is being set to OLD.updated and then on commit OLD.updated gets overwritten with current_timestamp. I need to maintain equality between the two after commit but they cannot always be set to the same time.
For example,
Initial state:
created |updated |
-------------------|-------------------|
2018-10-09 15:59:23|2018-11-12 16:00:22|
After update:
created |updated |
-------------------|-------------------|
2018-11-12 16:00:22|2019-01-09 16:00:22|
Desired state:
created |updated |
-------------------|-------------------|
2018-11-12 16:00:22|2018-11-12 16:00:22|
The trigger should be defined with the condition:
CREATE TRIGGER trig_table_on_update
BEFORE UPDATE ON my_table
FOR EACH ROW
WHEN (NEW.created = OLD.created)
EXECUTE PROCEDURE func_table_on_update();
Also, add the statement in the trigger function:
NEW.created = OLD.updated;
Now a transaction may look like this:
BEGIN;
UPDATE my_table
SET some_column = 'new value'
-- don't set created here!
WHERE id = 1;
SELECT *
FROM my_table;
-- here created <> updated
UPDATE my_table
SET created = updated
WHERE id = 1;
-- the trigger won't be fired
SELECT *
FROM my_table;
-- here created = updated
COMMIT;
I have a table which doesn't have an unique ID. I want to make a stored procedure which is adding to each row the number of the row as ID, but I don't know how to get the current row number. This is what I have done until now
CREATE OR ALTER PROCEDURE INSERTID_MYTABLE
returns (
cnt integer)
as
declare variable rnaml_count integer;
begin
/* Procedure Text */
Cnt = 1;
for select count(*) from MYTABLE r into:rnaml_count do
while (cnt <= rnaml_count) do
begin
update MYTABLE set id=:cnt
where :cnt = /*how should I get the rownumber here from select??*/
Cnt = Cnt + 1;
suspend;
end
end
I think better way will be:
Add new nullable column (let's call it ID).
Create a generator/sequence (let's call it GEN_ID).
Create a before update/insert trigger that fetches new value from sequence whenever the NEW.ID is null. Example.
Do update table set ID = ID. (This will populate the keys.)
Change the ID column to not null.
A bonus. The trigger can be left there, because it will generate the value in new inserted rows.
Perhaps a stupid question!
If I call a stored proc from an After Insert trigger (T-SQL) - then how do I get the values of the "just inserted" data?
e.g.
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
EXEC createAuditSproc 'I NEED VALUES HERE!'
I don't have any identity columns to worry about - I just want to use some of the "just inserted" values to pass into my sproc.
Edit: For clarification - I need this to call a sproc and not do a direct insert to the table, since the sproc does more than one thing. I'm working with some legacy tables I can't currently amend to do things 'properly' (time/resource/legacy code), so I have to work with what I have :(
You get to the newly 'changed' data by using the INSERTED and DELETED pseudo-tables:
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
INSERT INTO myTableAudit(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
END
Given the example tables
create table myTable
(
ID INT identity(1,1),
Name varchar(10)
)
GO
create table myTableAudit
(
ID INT,
Name varchar(10),
TimeChanged datetime default CURRENT_TIMESTAMP
)
GO
Edit : Apologies, I didn't address the bit about calling a Stored Proc. As per marc_s's comment, note that inserted / deleted can contain multiple rows, which complicates matters with a SPROC. Personally, I would leave the trigger inserting directly into the audit table without the encapsulation of a SPROC. However, if you have SQL 2008, you can use table valued parameters, like so:
CREATE TYPE MyTableType AS TABLE
(
ID INT,
Name varchar(10)
);
GO
CREATE PROC dbo.MyAuditProc #MyTableTypeTVP MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTableAudit(ID, Name)
SELECT mtt.ID, mtt.Name
FROM #MyTableTypeTVP mtt;
END
GO
And then your trigger would be altered as like so:
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MyTableTypeTVP AS MyTableType;
INSERT INTO #MyTableTypeTVP(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
EXEC dbo.MyAuditProc #MyTableTypeTVP;
END
you can then test that this works for both a single and multiple inserts
insert into dbo.MyTable values ('single');
insert into dbo.MyTable
select 'double'
union
select 'insert';
However, if you are using SQL 2005 or lower, you would probably need to use a cursor to loop through inserted passing rows to your SPROC, something too horrible to contemplate.
As a side note, if you have SQL 2008, you might look at Change Data Capture
Edit #2 : Since you need to call the proc, and if you are certain that you only insert one row ...
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SomeInt INT;
DECLARE #SomeName VARCHAR(10);
SELECT TOP 1 #SomeInt = i.ID, #SomeName = i.Name
FROM INSERTED i;
EXEC dbo.MyAuditProc #SomeInt, #SomeName;
END;