Trigger on Views in PL/SQL - sql

I want to write a trigger in PL/SQL. My first aim is to compare two time_stamp data type (A and B). If one of these is bigger than another, for instance A>B, i will update columns on another table. Trigger that i try to write is like below.
CREATE OR REPLACE TRIGGER trigger_name
AFTER INSERT OR UPDATE OR DELETE ON views
FOR EACH ROW
DECLARE
A views.X%TYPE;
B views.Y%TYPE;
C views.Z%TYPE;
BEGIN
SELECT X, Y, Z INTO A, B, C FROM views;
IF A>B THEN
update another_table set D=' ' and E='UNRESOLVED' where column1=A;
ELSE
dbms_output.put_line('ABC: ' || A || ' < ' || 'CDE' || B);
END IF;
END;
If i execute this trigger, i'm getting error like below.
Error report:
ORA-25001: kan inte skapa den här triggertypen i den här typen av vy
25001. 00000 - "cannot create this trigger type on views"
*Cause: Only INSTEAD OF triggers can be created on a view.
*Action: Change the trigger type to INSTEAD OF.
Thanks in advance for your help.

You're nearly there. This is only a syntactic confusion. You cannot create a trigger that fires BEFORE or AFTER an insert or update or delete of a view, but you can create a trigger that fires INSTEAD OF an insert or update or delete:
TABLE BEFORE / AFTER insert or update or delete
VIEW INSTEAD OF insert or update or delete
And, as #Belayer writes, you don't (and shouldn't) use SELECT, use the automatically prepared record called :new for the new values during insert or update, or the record ':old' for the old values during update or delete.
Your trigger would look something like:
CREATE OR REPLACE TRIGGER views_tr
INSTEAD OF INSERT OR UPDATE OR DELETE ON views
FOR EACH ROW
BEGIN
IF :new.x > :new.y THEN
UPDATE another_table SET D=' ', ... WHERE column1 = :new.x;
ELSE
dbms_output.put_line('ABC: ' || :new.x || ' < ' || 'CDE' || :new.y);
END IF;
END views_tr;
/

Related

Why does it raise a mutating table error?

I am attenting to create a trigger that before delete a row in the table CLUBS check if the column END_DATE is null, if it's null the row instead of deleting the row it replaces the value with sysdate and if is not null the row doesn't get deleted.
CREATE OR REPLACE TRIGGER TRANSFER_DATA
BEFORE DELETE ON CLUBS
FOR EACH ROW
BEGIN
IF :old.END_DATE IS NULL
THEN UPDATE CLUBS SET END_DATE = SYSDATE;
ELSE
RAISE_APPLICATION_ERROR(-20033, 'No se puede borrar');
END IF;
END;
I've tried this code but it raises an error:
"table %s.%s is mutating, trigger/function may not see it"
How can I make it work?
By the way, I am working on Oracle sqldeveloper
You need to create a view and a INSTEAD OF trigger:
create or replace view v_CLUBS as
SELECT * FROM CLUBS; -- perhaps add 'WHERE END_DATE IS NULL'
CREATE OR REPLACE TRIGGER TRANSFER_DATA
INSTEAD OF DELETE ON V_CLUBS
FOR EACH ROW
BEGIN
IF :old.END_DATE IS NULL THEN
:new.end_date := sysdate;
ELSE
RAISE_APPLICATION_ERROR(-20033, 'No se puede borrar');
END IF;
END;
Processing through a view and an instead of trigger is differently an effective way to go. It seems your prior attempt approached the task incorrectly. A simple view is fully updateble (generally). But in this case you want to intercept the delete and apply the action to the underlying table. Assuming CLUBS is view you process against and CLUBS_TBL is the actual table the trigger you want is:
-- create trigger to process deletes on the view
create or replace trigger del_clubs_isd
instead of delete on clubs
begin
update clubs_tbl
set end_date = sysdate
where id = :old.id;
end del_clubs_isd;
See a full script at fiddle.
Further, as presented the user never needs to know the club is not actually deleted, but just marked to appear that way.

Creating a trigger that deletes a row when an attribute becomes negative [oracle sql]?

I would like to create a trigger that will delete a row when one of its attributes becomes negative. So far I have this, but it doesn't appear to be valid sql:
CREATE OR REPLACE TRIGGER ZERO_COPIES_TRIGGER
after
update of counter_attribute
on my_table
referencing new as new
for each row when(new.copies < 0)
begin
delete from my_table where my_table.id = :new.id;
end;
This is not going to work. You can't perform DML on a table which is being manipulated by a row-level trigger. You will get a "mutating table" error.
To get the result you want, your best bet is to have a flag or indicator column to identify that the record is to be deleted. Then, have a separate job or process or whatever to actually perform the delete.
For the sake of completeness: another option is to have a statement trigger which scans the table and then performs the delete, as in:
CREATE OR REPLACE TRIGGER ZERO_COPIES_TRIGGER
AFTER UPDATE OF COUNTER_ATTRIBUTE ON MY_TABLE
BEGIN
FOR aROW IN (SELECT ID
FROM MY_TABLE
WHERE COPIES < 0)
LOOP
DELETE FROM MY_TABLE
WHERE ID = aROW.ID;
END LOOP;
END ZERO_COPIES_TRIGGER;
Or, if you really want to have some fun, you could use a compound trigger to handle the deletes, without requiring a table scan, while still avoiding the dreaded "MUTATING TABLE" error, as in:
CREATE OR REPLACE TRIGGER COMPOUND_ZERO_COPIES_TRIGGER
FOR UPDATE OF COUNTER_ATTRIBUTE ON MY_TABLE
COMPOUND TRIGGER
TYPE NUMBER_TABLE IS TABLE OF NUMBER;
tblDELETE_IDS NUMBER_TABLE;
BEFORE STATEMENT IS
BEGIN
tblDELETE_IDS := NUMBER_TABLE();
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
IF :NEW.COPIES < 0 THEN
tblDELETE_IDS.EXTEND;
tblDELETE_IDS(tblDELETE_IDS.LAST) := :NEW.ID;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
IF tblDELETE_IDS.COUNT > 0 THEN
FOR I IN tblDELETE_IDS.FIRST..tblDELETE_IDS.LAST LOOP
DELETE FROM MY_TABLE
WHERE ID = tblDELETE_IDS(I);
END LOOP;
END IF;
END AFTER STATEMENT;
END COMPOUND_ZERO_COPIES_TRIGGER;
Share and enjoy.

Oracle APEX Database Trigger - Problems with referencing database columns

I have a colon delimited list of values being stored in a varchar2 column, ORDER_PARTS_LIST, of my Oracle database.
(I understand that storing data in a list like this might not be best practice but for now just ignore that fact.)
Here are the relevant table columns:
ORDER_TABLE(
ORDER_NUMBER number,
ORDER_PARTS_LIST varchar(4000))
PARTS_TABLE(
PART_NUMBER varchar(20),
ASSIGNED_ORDER_NUMBER number)
I have a conditional trigger:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update or delete on "ORDER_TABLE"
for each row
begin
if :new.ORDER_PARTS_LIST LIKE '%'+PART_NUMBER+'%' then
update PARTS_TABLE set ASSIGNED_ORDER_NUMBER = :ORDER_NUMBER;
end if;
end;
When I run this trigger I get the following error:
PLS-00201: identifier 'PART_NUMBER' must be declared
What is supposed to happen is that the trigger checks which PART_NUMBERs, in PARTS_TABLE, are included in the ORDER_PARTS_LIST, in the ORDER_TABLE, and then inserts the ORDER_NUMBER, for the affected row in ORDER_TABLE, into the ASSIGNED_ORDER_NUMBER column, of PARTS_TABLE.
In the end, all the PARTS in an ORDER should be flagged with that ORDER's NUMBER.
Does that make ANY sense???
I am not certain exactly how to properly define the variables in this trigger so that it runs and honestly I have a few doubts as to whether or not the trigger would do what I think it should even if those worked. ANY suggestions or help in getting the trigger functioing like I have defined it should would be great. Thanks in advance.
You can do string matching to test each row:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update on "ORDER_TABLE"
for each row
begin
update PARTS_TABLE p
set p.ASSIGNED_ORDER_NUMBER = :new.ORDER_NUMBER
where instr(':' || :new.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') > 0;
end;
So for example, if ORDER_PARTS_LIST is '123:456:789', the INSTR will find matches for the ids 123, 456 and 789, but not 124, 45 or 8, for example.
When parts are removed from an order you will need a different trigger to NULL the appropriate fields in PARTS_TABLE:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
update on "ORDER_TABLE"
for each row
begin
update PARTS_TABLE p
set p.ASSIGNED_ORDER_NUMBER = NULL
where instr(':' || :new.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') = 0
and instr(':' || :old.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') > 0;
end;
You are creating a trigger on the ORDER_TABLE. Since the ORDER_TABLE does not contain a column named PART_NUMBER, Oracle is unable to find the identifier 'PART_NUMBER' as it belongs to the PARTS_TABLE.
You will need to write a separate query in your trigger to access the PART_NUMBER in PARTS_TABLE.
Not entirely sure how this all fits together (seems like this won't account for the same part on multiple orders), but it looks like what you're trying to do is something like this:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update or delete on "ORDER_TABLE"
for each row
begin
update parts_table
set assigned_order_number = :new.ORDER_NUMBER
where part_number in (:new.order_parts_list);
end;

SQL/Oracle 10g - Troubles with triggers/getting values from a table

I'm currently having an issue with a trigger I'm writing. I want to do a simple trigger in which after an update to table STATEMENT with the status field set to 'Sent', it would create a new row in the table NOTICE with fields such as id, date, user and the last field being a message which takes certain field values to create a "notice".
If it will help, my STATEMENT table contains the following fields:
id
List item
Title
Others not needed to know
So, with the last field of the NOTICE to be inserted, I want to create like a message, perhaps saying "The statement, (id) - (title), issued on (date) has been sent."
I currently have at the moment:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
begin
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001', 'the notice
im having trouble constructing');
end send_notice;
I have tested this trigger in a database and everything seems to work fine. Another thing I was just wondering is if the formatting or if there is anything missing that might help with this trigger? And also, I would I go about creating that notice, which takes field values from STATEMENT?
Any help is appreciated
You can refer to new STATEMENT column values in the trigger using :new., and concatenate them into your text:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
begin
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001',
'The statement, ' || :new.id || ' - ' || :new.title || ', issued on '
|| :new.issue_date || ' has been sent');
end send_notice;
Sometimes concatenating a lot of text and values can get confusing, and you may find it easier to use this "template" approach:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
declare
l_text varchar2(500);
begin
l_text := 'The statement, #ID# - #TITLE#, issued on #DATE# has been sent';
l_text := replace (l_text, '#ID#', :new.id);
l_text := replace (l_text, '#TITLE#', :new.title);
l_text := replace (l_text, '#DATE#', :new.issue_date);
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001', l_text);
end send_notice;

triggers in new and old columns

Why can't we use :new and :old columns in a statement level trigger?
Because it might be the case that the statement is inserting/deleting/updating more than one row. So there is no new or old column.
Example:
update FOO set a = 12 where b = 9;
Or:
delete from FOO where b = 9;
Or:
insert into FOO (a, b) select 12, x from BAR;
If FOO table had a statement trigger, in these three sentences there is no way to tell if you are operating on none, single or multiple rows.
Because the DML could have been set-based, affecting multiple rows in the table. In fact, as SQL is properly set-based that should be the usual case. Consequently there is no way for the statement level triggers to determine which :OLD and which :NEW values you mean.
As said before statement level triggers can be for one to many row changes so :new and :old aren't available.
If you need to track the :new and :old values and need access to them at the statement trigger you can create a row level trigger that stores the new and old values for use by the statement level. Here is one way we have solved this problem before
The package:
create or replace package table_trigger_helper is
subtype subtype_rowtype is table_name$rowtype;
type table_rowtype is table of subtype_rowtype;
v_old table_rowtype := table_rowtype();
v_new table_rowtype := table_rowtype();
end table_trigger_helper;
/
The row level trigger:
create or replace trigger row_level_trigger_name
after insert or delete or update
on table_name
for each row
declare
r_old table_trigger_helper.table_rowtype := NULL;
r_new table_trigger_helper.table_rowtype := NULL;
i pls_integer;
begin
if update or deleting then
r_old.column_one := :old.column_one
...
end if;
if update or inserting then
r_new.column_one := :new.column_one
end if;
table_trigger_helper.v_old.extend();
table_trigger_helper.v_new.extend();
i := table_trigger_helper.v_old.last;
table_trigger_helper.v_old( i ) := r_old;
table_trigger_helper.v_new( i ) := r_new;
end row_level_trigger_name;
/
The statement level trigger:
create or replace trigger statement_level_trigger_name
after insert or delete or update
on table_name
declare
begin
--process through your new and old records;
--table_trigger_helper.v_old
--table_trigger_helper.v_new
end statement_level_trigger_name;
/