Oracle (11g) compound trigger not updating CLOB data field - plsql

I tried searching for answers to this but couldn't really find anything useful. Either there isn't or my searching capabilities took a knock. Regardless, here's my situation:
I have a case where I've got 2 identical tables, Z_TEST and Z_TEST2. Call Z_TEST2 an audit table if you'd like.
Both have 2 columns, ID (number) and CFIELD (CLOB). Z_TEST has a trigger which will either insert or update Z_TEST2 (in effect only the CLOB field).
Scenario 1:
If I create the following trigger on Z_TEST, which is a normal trigger, the audit table gets updated too:
CREATE OR REPLACE TRIGGER Z_TEST_TRIG
AFTER INSERT OR UPDATE ON Z_TEST
FOR EACH ROW
Begin
If inserting then
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
end;
end if;
if updating then
begin
update Z_TEST2 Set CFIELD = :new.CFIELD where ID = :new.id;
end;
end if;
End;
Scenario 2:
If I create the following trigger which is a compound trigger and make use of its "After each row" block, the audit table's CLOB updates with nothing, null:
create or replace trigger Z_TEST_TRIG
FOR UPDATE OR INSERT ON Z_TEST
COMPOUND TRIGGER
AFTER EACH ROW IS
Begin
If inserting then
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
end;
end if;
if updating then
begin
update Z_TEST2 Set CFIELD = :new.CFIELD where ID = :new.id;
end;
end if;
END AFTER EACH ROW;
END Z_TEST_TRIG;
Does anyone have any idea why this would work for scenario 1, but not 2? Our WHOLE framework is based on scenario 2 (compound triggers) and I've only recently come across the need to use CLOB data types, and with it came this problem.
Why the difference and is my code missing something?

Having no time to linger on this issue I solved it by making use of a variable.
Declaring a CLOB variable in the declaration section and assigning the value of the :new.clob_field to it either in BEFORE EACH ROW, or AFTER EACH ROW, and using the variable in your insert/update statement rather than :new.clob_field within the trigger solves this issue.
I came across a lot of posts by people battling with this (compound triggers specifically, not simple triggers), so I hope the time i spent on this helps someone else and saves them time.
It would really be helpful to my sanity if anyone comes across this post who knows the reason why :new.clob_field loses its value in a compound trigger when used in insert/update statements in the BEFORE/AFTER each row section. It would be awful dying one day with this thought stuck in my mind...
I'll also make the assumption that this would work for BLOB as well (if that causes an issue).

Something like this.... you just need to add update clause
CREATE OR REPLACE TRIGGER after_row_insert
AFTER INSERT
ON Z_TEST
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
End;

Related

PL/SQL: Run package after checking all rows in trigger

I have an inventory table with expected quantities and actually received quantities. Let's say inv.q_ex and inv.q_rd.
The INSERT to the table has a positive value in q_ex and a zero in q_rd because it hasn't arrived yet. I'd like to run a package when I detect that the q_rd value changes from 0 to something else, indicating it's been received and stored.
Making a trigger to detect after update and checking each row is easy, but I'm not sure how to ensure it only runs once.
The skeleton is:
CREATE OR REPLACE TRIGGER example
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
pkg.proc();
END IF;
END;
/
The problem I see is I only want it to run one time. I just need to identify when it needs to be executed. Ideally, on the first row where my condition is met, I would exit the loop (seems like a waste to keep checking when I already know I need to execute) and call my procedure.
I couldn't find a way to "exit" the for each and treat it as a normal AFTER UPDATE, so then I tried using both BEFORE UPDATE and AFTER UPDATE. The BEFORE portion would check each row and update a boolean. The AFTER portion would wait for that to happen and if it was true, call the procedure.
CREATE OR REPLACE TRIGGER example
BEFORE UPDATE ON inv
FOR EACH ROW
DECLARE
shouldExecute BOOLEAN;
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
shouldExecute := TRUE;
END IF;
END;
AFTER UPDATE ON inv
BEGIN
IF shouldExecute THEN
pkg.proc();
END IF;
END;
/
I suspect this wouldn't work anyway because, according to the syntax, it redeclares the boolean variable on each row. I thought that maybe I could make it "global" but regardless, turns out I can't add both BEFORE and AFTER to the same trigger for some reason (unless I didn't research enough), so I broke it out into two triggers. The problem now is I can't share that boolean between the two triggers. Can I share the value, or am I going about this all wrong?
There's a lot of little questions here so I'll try to answer them all :)
Regarding "FOR EACH ROW", Oracle triggers support two different triggering methods, STATEMENT or ROW. If you include the "FOR EACH ROW" in the definition, you'll get a row trigger, which is fired once per row affected by the query, which is what you seem to want here. Statement level triggers get fired only once for each query. An advantage to using row triggers is that you can use the :OLD and :NEW metavariables which refer to the previous and current row values.
As you've discovered, you can't add BEFORE and AFTER to the same trigger - you'll need to break them out into two triggers.
Unfortunately there isn't a simple way of sharing the boolean variable between the two triggers. The easiest way is probably to create a package with a public variable, which you can set in the BEFORE trigger, and check in the AFTER trigger.
The package would look something like this:
CREATE OR REPLACE PACKAGE PCKG_DEMO
AS
shouldExecute BOOLEAN;
END;
/
Then your BEFORE UPDATE trigger:
CREATE OR REPLACE TRIGGER beforeexample
BEFORE UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
PCKG_DEMO.shouldExecute := TRUE;
END IF;
END;
/
And your AFTER UPDATE trigger:
CREATE OR REPLACE TRIGGER afterexample
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF PCKG_DEMO.shouldExecute THEN
pkg.proc();
END IF;
END;
/
Those are a bit pseudocode-y - I don't have access to an Oracle database right now. You can read more about triggers here. Hope this helps!
You need to do more research. The link above takes you to trigger discussion for Oracle 9. I hope you are not actually using that version; support ended in 2007. Since version 11g Oracle has provided "Compound Triggers" where you can fire the same trigger both Before and After for both statement and row level. Compound triggers do allow sharing variables between the different invocations.

Statement level trigger

I have a trigger for a table called A_TAB which as only one column A of character type. and I have a another table DUP_A which has same column as TAB_A.Trigger for A_TAB as follows
create or replace trigger A_trig
before insert on A_TAB
for each row
declare
num1 varchar2(50):= null;
pragma autonomous_transaction;
begin
select Wm_Concat(a) into num1 from DUP_A;
if num1 is null
then
num1:=:new.a;
else
num1:=num1||','||:new.a;
end if;
insert into DUP_A values(num1);
delete from DUP_A where rowid<>(select max(rowid) from DUP_A);
commit;
end;
Here I'm concatenating all the values that in newly inserted and inserting it to a single column of DUP_A.
It's working fine but, I need statement level trigger with same functionality
Thank you
You cannot reference data (:new and :old) in a statement level trigger.
Removing "for each row" clause will change row level trigger to statement level trigger .But PL/SQL runtime engine creates and populates
two data structures(:new and :old) that functions much like records only When a row-level trigger is fired

How to fix the mutating trigger in oracle

I wrote the trigger for updating the column value in the same table. For Ex I wrote a trigger on metermaster table after update of assettype column , with in the trigger i am trying to update the instantaneousinterval column in the same metermaster table. Its throws the error like this
ERROR: ORA-04091: table PSEB.METERMASTER is mutating, trigger/function
may not see it.
my trigger code is as follows:
CREATE OR REPLACE TRIGGER PSEB.spiupdate
AFTER
update of assettype
ON pseb.metermaster
referencing new as new old as old
for each row
DECLARE
vassettype number;
resval number(10);
vassettypename varchar2(50);
vmeterid number;
begin
select :new.assettype,:new.meterid INTO vassettype,vmeterid from dual;
select assettypename into vassettypename from pseb.METERASSETINSTTYPE where ASSETTYPEID=vassettype;
select case when assettypename like 'DT' then 86400 when assettypename like 'HT' then 3600 when assettypename like 'FSB' then 86400 end into resval from pseb.meterassetinsttype where assettypename =vassettypename;
update pseb.metermaster set instantaneousinterval=resval where meterid=vmeterid;
end;
I tried to use the
pragma autonomous_transaction;
but it gives the deadlock condition.
ERROR: ORA-00060: deadlock detected while waiting for resource
ORA-06512:
pls help me to fix this issue.
instead of this update statement
update pseb.metermaster set instantaneousinterval=resval where meterid=vmeterid;
use
:new.instantaneousinterval=resval;
A mutating table occurs when a statement causes a trigger to fire and that trigger references the table that caused the trigger. The best way to avoid such problems is to not use triggers, but I suspect the DBA didn’t take the time to do that. He could have done one of the following:
Changed the trigger to an after trigger.
Changed it from a row level trigger to a statement level trigger.
Convert to a Compound Trigger.
Modified the structure of the triggers to use a combination of row and statement level triggers.
Made the trigger autonomous with a commit in it.
Try this pragma autonomous_transaction; with Commit
Since the trigger is updating the same table on which it is defined, why don't you update the two columns in the first update statement itself?
i.e, Instead of using an update like
UPDATE pseb.metermaster
SET assettype = '<v_assettype>';
and relying on trigger to update the instantaneousinterval column, why don't you use an update statement like the following (code is not tested)
UPDATE pseb.metermaster
SET assettype = '<v_assettype>',
instantaneousinterval = (SELECT CASE
WHEN assettypename LIKE 'DT' THEN 86400
WHEN assettypename LIKE 'HT' THEN 3600
WHEN assettypename LIKE 'FSB' THEN 86400
END
FROM pseb.meterassetinsttype
WHERE assettypeid = '<v_assettype>');
In my opinion, using a trigger and autonomous_transaction in this case would be a wrong approach. To know why this is wrong, please search http://asktom.oracle.com/ for this error.

Value of a NEW variable on a trigger not changing, on plsql

I'm loading data through Oracle Apex utilities using a datasheet.
I want to make a trigger that checks for a value on the table from the data loaded, and then changes it depending on what it gets.
The table has 4 columns: id,name,email,type
The data to load is something like this: name,email,type
Now my trigger:
create or replace TRIGGER BI_USER
before insert ON USER
for each row
declare
begin
if :NEW.ID is null then
select USERID_SEQ.nextval into :NEW.ID from dual;
end if;
:NEW.TYPE := 'something else';
end;
The ID works great, it takes a number from the sequence, but :new.type isn't working, it doesn't change.
I also run the SQL insert separately and the same happens.
EDIT:
new.type type is char(1), I wrote it like this just for testing yet it doesn't change...
aah I'm dissapoint of myself, it throws the error just after reading the data and never fires the trigger.
What I was trying to do is that it will have the name of the TYPE column, and put the id from that table into the NEW.type
Is there a way to change the NEW type?
I see what you're trying to do. You want your table to accept an inserted record containing data that will not fit in the width of one of the fields, and you want to use a trigger to "fix" the data so that it will fit.
Unfortunately, this trigger will not help you because the data is validated before your triggers are fired.
An alternative way to get around this may be to use a view with an instead-of trigger. The view would have a column "TYPE" which is based on a string of length 9; the instead-of trigger would convert this to the CHAR(1) for insert into the underlying table.
Try this instead:
select 'something else' into :NEW.TYPE from dual;
If this syntax worked for ID it should also work for TYPE

Increase performance on insert cursor?

I would like to ask you how would you increase the performance on Insert cursor in this code?
I need to use dynamic plsql to fetch data but dont know how to improve the INSERT in best way. like Bulk Insert maybe?
Please let me know with code example if possible.
// This is how i use cur_handle:
cur_HANDLE integer;
cur_HANDLE := dbms_sql.open_cursor;
DBMS_SQL.PARSE(cur_HANDLE, W_STMT, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur_HANDLE, W_NO_OF_COLS, W_DESC_TAB);
LOOP
-- Fetch a row
IF DBMS_SQL.FETCH_ROWS(cur_HANDLE) > 0 THEN
DBMS_SQL.column_value(cur_HANDLE, 9, cont_ID);
DBMS_SQL.COLUMN_VALUE(cur_HANDLE, 3, proj_NR);
ELSE
EXIT;
END IF;
Insert into w_Contracts values(counter, cont_ID, proj_NR);
counter := counter + 1;
END LOOP;
You should do database actions in sets whenever possible, rather than row-by-row inserts. You don't tell us what CUR_HANDLE is, so I can't really rewrite this, but you should probably do something like:
INSERT INTO w_contracts
SELECT ROWNUM, cont_id, proj_nr
FROM ( ... some table or joined tables or whatever... )
Though if your first value there is a primary key, it would probably be better to assign it from a sequence.
Solution 1) You can populate inside the loop a PL/SQL array and then just after the loop insert the whole array in one step using:
FORALL i in contracts_tab.first .. contracts_tab.last
INSERT INTO w_contracts VALUES contracts_tab(i);
Solution 2) if the v_stmt contains a valid SQL statement you can directly insert data into the table using
EXECUTE IMMEDIATE 'INSERT INTO w_contracts (counter, cont_id, proj_nr)
SELECT rownum, 9, 3 FROM ('||v_stmt||')';
"select statement is assembled from a website, ex if user choose to
include more detailed search then the select statement is changed and
the result looks different in the end. The whole application is a web
site build on dinamic plsql code."
This is a dangerous proposition, because it opens your database to SQL injection. This is the scenario in which Bad People subvert your parameters to expand the data they can retrieve or to escalate privileges. At the very least you need to be using DBMS_ASSERT to validate user input. Find out more.
Of course, if you are allowing users to pass whole SQL strings (you haven't provided any information regarding the construction of W_STMT) then all bets are off. DBMS_ASSERT won't help you there.
Anyway, as you have failed to give the additional information we actually need, please let me spell it out for you:
will the SELECT statement always have the same column names from the same table name, or can the user change those two?
will you always be interested in the third and ninth columns?
how is the W_STMT string assembled? How much control do you have over its projection?

Resources