I'm trying to add a trigger on a VIEW in PostgreSQL 9.6.
This is my view:
CREATE VIEW names AS
SELECT one.name AS name_one, two.name AS name_two, three.name AS name_three
FROM table_one one
LEFT JOIN table_two two ON one.id = two.id
LEFT JOIN table_three three ON two.id = three.id;
This is my trigger function:
CREATE OR REPLACE FUNCTION notify_name_changed() RETURNS trigger AS $BODY$
BEGIN
PERFORM pg_notify('name_changed', row_to_json(NEW)::text);
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
And my CREATE TRIGGER:
CREATE TRIGGER notify_name_changed INSTEAD OF INSERT OR UPDATE OR DELETE ON "names"
FOR EACH ROW EXECUTE PROCEDURE notify_name_changed();
This doesn't fire any changes whenever something happens in one of the base tables.
However, creating 3 individual triggers does, but is somewhat unrelated to the view:
CREATE TRIGGER notify_name_changed AFTER INSERT OR UPDATE OR DELETE ON "one"
FOR EACH ROW EXECUTE PROCEDURE notify_name_changed();
CREATE TRIGGER notify_name_changed AFTER INSERT OR UPDATE OR DELETE ON "two"
FOR EACH ROW EXECUTE PROCEDURE notify_name_changed();
CREATE TRIGGER notify_name_changed AFTER INSERT OR UPDATE OR DELETE ON "three"
FOR EACH ROW EXECUTE PROCEDURE notify_name_changed();
Isn't it possible to add a trigger directly on a view, which fires in the event of any changes in base tables used in that view?
I think you misunderstand the concept of a view.
A view does not hold any data, you can see it as a “crystallized SQL statement” that has a name. Whenever a view is used in a query, it is replaced by its definition in the “query rewrite” step.
An INSTEAD OF trigger for UPDATE on a view is triggered only if you update the view itself, not the underlying tables. For that, you'd have to define triggers on those tables.
The point that you are probably missing is that if something changes in the underlying tables, it is immediately changed in the view, since the view is just a query on the base table.
Related
I want to insert into a child table every time the parent table is updated. But when this happens, all of the new records inserted into the child table should have the same id. The ids will only increment if the parent table is separately updated another time. How can I do this?
In this case, I want to insert into the child table every new My_Date field from when the parent table is updated. Below is an example of what this would look like.
When parent table gains two new rows...
My_Date
old
old
new
new
Child table gains two new rows, both assigned to same ID (the ID autoincrements in table definition)
My_Date ID
...
new 4
new 4
When parent table gains two new rows again...
My_Date
old
old
old
old
new
new
Child table gains two new rows, both assigned to same new ID
My_Date ID
...
old 4
old 4
new 5
new 5
Here is what I have so far.
CREATE or replace FUNCTION update_child() RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO child
SET My_Date = NEW.My_Date /*Not sure if this is correct*/
/*Give every row the same ID*/
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER update_child_after_update
AFTER UPDATE
ON parent
FOR EACH ROW
EXECUTE PROCEDURE update_child();
I believe what you want is a combination of a sequence and a statement-level (as opposed to row level) trigger:
https://www.postgresql.org/docs/current/trigger-definition.html#:~:text=PostgreSQL%20offers%20both%20per%2Drow,statement%20that%20fired%20the%20trigger.
PostgreSQL offers both per-row triggers and per-statement triggers.
With a per-row trigger, the trigger function is invoked once for each
row that is affected by the statement that fired the trigger. In
contrast, a per-statement trigger is invoked only once when an
appropriate statement is executed, regardless of the number of rows
affected by that statement. In particular, a statement that affects
zero rows will still result in the execution of any applicable
per-statement triggers. These two types of triggers are sometimes
called row-level triggers and statement-level triggers, respectively.
This is really bare bones, but I think it will demonstrate the desired behavior you described in your question.
create sequence child_id;
CREATE or replace FUNCTION update_child()
RETURNS trigger AS
$BODY$
DECLARE
r1 record;
new_id int;
BEGIN
new_id := nextval ('child_id');
FOR r1 IN SELECT * FROM new_table
LOOP
insert into child
select r1.my_date, new_id;
END loop;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER update_child_after_update
AFTER UPDATE
ON parent
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH statement
EXECUTE PROCEDURE update_child();
CREATE TRIGGER update_child_after_insert
AFTER INSERT
ON parent
REFERENCING NEW TABLE AS new_table
FOR EACH statement
EXECUTE PROCEDURE update_child();
There are a lot of restrictions on statement-level triggers, and it's worth reading up on them. For example, only the "after" is supported.
Presumably your parent table also has some form of PK, which you would also be passing to the child, but I'm hopeful that's easy enough to see where you would insert that in the code example above.
Imagine the following example with the table t and the view v.
create table t (a int, b int, c int);
insert into t values (1, 2, 3);
create view v as select * from t;
Now I want the write an "instead of update" trigger for the view in that way, that all updates of the view will update the table. I know that I do not need it in this simplified example, because PostgreSQL can do it automatically. But in my real world use case it is necessary.
Is this the correct way to implement the update trigger?
create function u () returns trigger as $$
begin
update t set a = new.a, b = new.b, c = new.c;
return new;
end;
$$ language plpgsql;
create trigger t instead of update on v
for each row execute procedure u ();
I am not sure, because I am wondering whether there is a difference between the update of the table and the view:
update t set a = 0 where a = 1;
update v set a = -1 where a = 0;
I expect that the update of the table updates only one column. But I fear that the update of the view updates three columns in the table.
Is this the case? And if so how to work around this?
Any update on a table, no matter how many rows it modifies, will always write the same amount of data. The reason is that an UPDATE creates a new version of the complete row.
The UPDATE on the view will take longer, because calling a trigger is some overhead.
By the way, you don't need that trigger at all. Simple views like that are automatically updateable, even without a trigger.
I have a table with 3 fields
id, name , value
I want to add a 4th colum calcValue. The calculation of calcValue is based on the filed value in the whole table, changing it in one row can cause changes in all of the others.
I want to write a trigger that updates calcValue everytime there is insert, delete or update in the table.
What i'm worry about is that the trigger itself is going to have an Update command. Will it case another call to this trigger? Will I be stuck with infinte loop?
To describe it better:
CREATE TRIGGER x
AFTER INSERT OR UPDATE OR DELETE
ON a
FOR EACH ROW
EXECUTE PROCEDURE dosomething();
and:
CREATE OR REPLACE FUNCTION dosomething()
RETURNS trigger AS
$BODY$
begin
code + calculation of result...
for row in
QUERY
Loop
Update a set calValue=result where id=...
end loop;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
Will the update of a in dosomething() will cause another invoke of the trigger x? If it does is there a way to handle it so it won't stuck in infinte loop?
My goal is to do dosomething() once per update/insert/delete action of a . I don't want dosomething() to be called again because of the update a in the trigger.
Edit: Since i'm updating in the trigger a diffrent column I can do that:
CREATE TRIGGER x
BEFORE UPDATE OF value ON a
FOR EACH ROW
EXECUTE PROCEDURE dosomething();
this should solve my problem as the trigger updates calcValue and the tigger isn't set invoke on value column. However I would still like to know if there is an answer to my original question... suppose that the trigger would have update the same column.
Another alternative would be to add a when clause, along the lines of:
CREATE TRIGGER x
AFTER INSERT OR UPDATE OR DELETE
ON a
FOR EACH ROW
WHEN NEW IS NULL OR OLD IS NULL OR NEW.value <> OLD.value
EXECUTE PROCEDURE dosomething();
It will only execute the trigger when there's a change in the value field. Therefore when inside the trigger you update calcValue, the trigger is not called again, preventing an infinite "loop".
In my Oracle DB I've a table called tableA where an application write data. My program reads from tableA and when I've processed the data I delete them. The question is I want to keep a log of every data I've processed and I can't keep them in tableA because I've no control over application A and it might not work if I keep my processed data on that table, so I've created a table identical to tableA called tableB and I've put this trigger on tableA:
create or replace
trigger tableA_delete_trigger
BEFORE DELETE ON tableA
FOR EACH ROW
BEGIN
INSERT INTO tableB
( a,
b,
c,
)
VALUES
( :old.a,
:old.b,
:old.c,
sysdate);
END;
This system work quite well, the real problem is when I need to alter something in tableA I have to replicate by hand the same modification in tableB and if I add/remove coloumn I have to update the trigger.
Is there a better way to do this?
An alternative approach might be to rename TableA and create a view named TableA for application A to use. You would then logically delete rows by whatever means seem appropriate and only expose in the view the rows that are not deleted.
You would still need to modify the view if the table structure changes, but at least you won't need to worry about the trigger.
What about an alter trigger:
CREATE OR REPLACE TRIGGER ddl_trigger AFTER ALTER ON schema
DECLARE
cmd VARCHAR2(32000);
BEGIN
SELECT
upper(sql_text)
INTO
cmd
FROM
v$open_cursor
WHERE
upper(sql_text) LIKE 'ALTER TABLE TABLEA%' ;
SELECT
REPLACE(cmd, 'TABLEA', 'TABLEB')
INTO
cmd
FROM
dual;
EXECUTE IMMEDIATE cmd;
END;
Not sure that will work because of the recursion.
How do I write an Oracle trigger, than when a user deletes a certain record, the delete doesnt actually happen, but instead performs an update on those rows and sets the status of the record to 'D'?
I tried:
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
UPDATE
M_ITEM_H
SET
ITEM_STAT = 'D'
WHERE
CUST_CODE = 'TEST'
AND ITEM_CODE = 'GDAY'
;
raise_application_error(-20000,'Cannot delete item');
END;
But I am getting mutating table errors. Is this possible?
If you really need a trigger, the more logical approach would be to create a view, create an INSEAD OF DELETE trigger on the view, and to force the applications to issue their deletes against the view rather than against the base table.
CREATE VIEW vw_m_item_h
AS
SELECT *
FROM m_item_h;
CREATE OR REPLACE TRIGGER t4m_item_ondelete
INSTEAD OF DELETE ON vw_m_item_h
FOR EACH ROW
AS
BEGIN
UPDATE m_item_h
SET item_stat = 'D'
WHERE <<primary key>> = :old.<<primary key>>;
END;
Better yet, you would dispense with the trigger, create a delete_item procedure that your application would call rather than issuing a DELETE and that procedure would simply update the row to set the item_stat column rather than deleting the row.
If you really, really, really want a solution that involves a trigger on the table itself, you could
Create a package with a member that is a collection of records that map to the data in the m_item_h table
Create a before delete statement-level trigger that empties this collection
Create a before delete row-level trigger that inserts the :old.<<primary key>> and all the other :old values into the collection
Create an after delete statement-level trigger that iterates through the collection, re-inserts the rows into the table, and sets the item_stat column.
This would involve more work than an instead of trigger since you'd have to delete and then re-insert the row and it would involve way more moving pieces so it would be much less elegant. But it would work.
First of all the trigger you wrote would throw a mutating table error. Technically what you are asking is not possible i.e. delete wouldn't delete but rather update, unless you raise an exception in the middle which could be an ugly way of doing it. I would think users using some sort of application front end which lets them delete data using a delete button, so you may use an update statement there instead of a delete statement.
Another option would be to create a log table, where you could insert the record before deleting it from the actual table and then join the log table with the actual table to retrieve deleted records. Something like-
CRETAE TABLE M_ITEM_H_DEL_LOG as SELECT * FROM M_ITEM_H WHERE 1=2;
And then
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
INSERT INTO
M_ITEM_H_DEL_LOG
VALUES (:old.col1, :old.col2,.....) --col1, col2...are columns in M_ITEM_H
;
END;