PLS-00049: bad bind variable Triggers - oracle

I'm trying to make this trigger work. What it does is, this will execute before deleting data from a table and its affiliated table, then insert that deleted data into 2 tables (which I named into tb1_arch and tb2_arch). I've been searching possible fixes in google but I really have no idea how to make this work
CREATE OR REPLACE TRIGGER trig
BEFORE DELETE ON tb1 FOR EACH ROW
FETCH
INSERT INTO tb1_arch VALUES (:old.OrderNum, :old.tb1Date,:old.CustomerName);
DBMS_OUTPUT.PUT_LINE('Data archived.');
END;
the trigger was created above. but when I added this line
INSERT INTO tb2_arch VALUES (:old.OrderNum, :old.ItemNum, :old.Pieces);
after INSERT INTO tb1_arch VALUES, it gives me an error
"Error at line 5: PLS-00049: bad bind variable 'OLD.PIECES'"
Any help would be appreciated!
EDIT: as Mr. Vijayakumar suggested, I did the following:
CREATE OR REPLACE TRIGGER trig
BEFORE DELETE ON tb1
REFERENCING OLD AS old FOR EACH ROW
INSERT INTO tb1_arch VALUES (:old.OrderNum, :old.tb1Date,:old.CustomerName);
INSERT INTO tb2_arch VALUES (:old.OrderNum, :old.ItemNum, :old.Pieces);
DBMS_OUTPUT.PUT_LINE('Data archived.');
END
However, I'm still having the same error.
EDIT: removed FETCH, I'm still encountering the same problem
CREATE OR REPLACE TRIGGER trig
BEFORE DELETE ON tb1 FOR EACH ROW
INSERT INTO tb1_arch VALUES (:old.OrderNum, :old.tb1Date,:old.CustomerName);
INSERT INTO tb2_arch VALUES (:old.OrderNum, :old.ItemNum, :old.Pieces);
DBMS_OUTPUT.PUT_LINE('Data archived.');
END;
The table are as follows:
tb1:
create table tb1 (
OrderNum integer NOT NULL primary key,
tb1Date date NOT NULL,
CustomerName varchar2(50) NOT NULL,
--constraints
CONSTRAINT tb1_uc unique (CustomerName)
);
tb2
create table tb2 (
OrderNum integer NOT NULL,
ItemNum integer NOT NULL,
Pieces integer NOT NULL,
--constraints
CONSTRAINT tb2_fk foreign key (OrderNum) references tb1(OrderNum),
);

The keyword FETCH should be causing this, replace it by BEGIN, which is the right syntax,
CREATE OR REPLACE TRIGGER trig
BEFORE DELETE ON tb1 FOR EACH ROW
BEGIN
INSERT INTO tb1 VALUES (:old.OrderNum, :old.Date,:old.CustomerName);
INSERT INTO tb2_arch VALUES (:old.OrderNum, :old.ItemNum, :old.Pieces);
DBMS_OUTPUT.PUT_LINE('Data archived.');
END;
EDIT: The column name you refer using OLD might be invalid, which is the reason for this error.
Trigger will be applicable for only ONE Table's INSERT/UPDATE/DELETE event. Though, you can refer multiple tables in the body.
But OLD and NEW will be always referring the table you mentioned in DDL like DELETE ON tb1.
The below modified code will help you.
CREATE OR REPLACE TRIGGER trig
BEFORE DELETE ON tb1 FOR EACH ROW
DECLARE
v_ItemNum tb2.ItemNum%TYPE;
v_Pieces tb2.Pieces%TYPE;
BEGIN
INSERT INTO tb1_arch VALUES (:old.OrderNum, :old.Date,:old.CustomerName);
/* Select the values for this item from table 2 */
SELECT ItemNum,Pieces
INTO v_ItemNum,v_Pieces
FROM tb2
WHERE OrderNum = :old.OrderNum;
/* Insert the selected values in the table2's archive version */
INSERT INTO tb2_arch VALUES (:old.OrderNum, v_ItemNum, v_Pieces);
/* Delete the entry */
DELETE from tb2 WHERE OrderNum = :old.OrderNum;
DBMS_OUTPUT.PUT_LINE('Data archived.');
END;
Alternatively, you can have another trigger for delete on table2 and archive the data there.

Related

Accessing old and new values without :OLD and :NEW in a trigger

As discussed here, I'm unable to use :OLD and :NEW on columns with collation other than USING_NLS_COMP. I'm trying to find a way around this but haven't been successful so far.
This is the original trigger:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
AFTER UPDATE ON PERSONS
FOR EACH ROW
begin
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := :old.SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := :new.SalutationTitle;
end;
This is what I've tried:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
FOR UPDATE ON Persons
COMPOUND TRIGGER
TYPE Persons_Record IS RECORD (
SalutationTitle NVARCHAR2(30)
);
TYPE Persons_Table IS TABLE OF Persons_Record INDEX BY PLS_INTEGER;
gOLD Persons_Table;
gNEW Persons_Table;
BEFORE EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gOLD
FROM Persons
WHERE ID = :OLD.ID;
END BEFORE EACH ROW;
AFTER EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gNEW
FROM Persons
WHERE ID = :NEW.ID;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FOR i IN 1 .. gNEW.COUNT LOOP
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := gOLD(i).SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := gNEW(i).SalutationTitle;
END LOOP;
END AFTER STATEMENT;
END;
This results in error ORA-04091. I've also tried moving the select into the AFTER STATEMENT section which works, but there is no way to access the old values. If somebody has a solution for this it would be most appreciated.
EDIT:
I created a minimal reproducible example:
CREATE TABLE example_table (
id VARCHAR2(10),
name NVARCHAR2(100)
);
CREATE TABLE log_table (
id VARCHAR2(10),
new_name NVARCHAR2(100),
old_name NVARCHAR2(100)
);
CREATE OR REPLACE TRIGGER example_trigger
AFTER UPDATE ON example_table
FOR EACH ROW BEGIN
INSERT INTO log_table VALUES(:old.id, :new.name, :old.name);
END;
INSERT INTO example_table VALUES('01', 'Daniel');
-- this works as expected
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
CREATE TABLE example_table (
id VARCHAR2(10),
-- this is the problematic part
name NVARCHAR2(100) COLLATE XCZECH_PUNCTUATION_CI
);
INSERT INTO example_table VALUES('01', 'Daniel');
-- here nothing is inserted into log_example, if you try to
-- recompile the trigger you'll get error PLS-00049
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
DROP TABLE log_table;
DROP TRIGGER example_trigger;
In the discussion you reference a document concerning USING_NLS_COMP. That has nothing to do with the error you are getting. The error ORA-04091 is a reference to the table that fired the trigger (mutating). More to come on this. I am not saying you do not have USING_NLS_COMP issues, just that they are NOT causing the current error.
There are misconceptions shown in your trigger. Beginning with the name itself; you should avoid the prefix SYS. This prefix is used by Oracle for internal objects. While SYS prefix is not specifically prohibited at best it causes confusion. If this is actually created in the SYS schema then that in itself is a problem. Never use SYS schema for anything.
There is no reason to create a record type containing a single variable, then create a collection of that type, and finally define variables of the collection. Just create a collection to the variable directly, and define variables of the collection.
The bulk collect in the select statements is apparently misunderstood as used. I assume you want to collect all the new and old values in the collections. Bulk collect however will not do this. Each time bulk collect runs the collection used is cleared and repopulated. Result being the collection contains only the only the LAST population. Assuming id is unique the each collection would contain only 1 record. And now that brings us to the heart of the problem.
The error ORA-04091: <table name> is mutating, trigger/function may not see it results from attempting to SELECT from the table that fired the trigger; this is invalid. In this case the trigger fired due to a DML action on the persons table as a result you cannot select from persons in a row level trigger (stand alone or row level part of a compound trigger. But it is not needed. The pseudo rows :old and :new contain the complete image of the row. To get a value just reference the appropriate row and column name. Assign that to your collection.
Taking all into account we arrive at:
create or replace trigger personssalutation
for update
on persons
compound trigger
type persons_table is table of
persons.salutationtitle%type;
gold persons_table := persons_table();
gnew persons_table := persons_table();
before each row is
begin
gold.extend;
gold(gold.count) := :old.salutationtitle;
end before each row;
after each row is
begin
gnew.extend;
gold(gold.count) := :new.salutationtitle;
end after each row;
after statement is
begin
for i in 1 .. gnew.count loop
state_00.salutations_todelete(state_00.salutations_todelete.count + 1) := gold(i);
state_00.salutations_toinsert(state_00.salutations_toinsert.count + 1) := gnew(i);
end loop;
end after statement;
end personssalutation;
NOTE: Unfortunately you did not provide sample data, nor description of the functions in the AFTER STATEMENT section. Therefore the above is not tested.

Addition of values in two columns isn't working in PL/SQL ORACLE

So I am trying to add age with price and store the result on another table with the ID of the person who did this. I am able to set the business rule by making the trigger but when I check my second (END) table, there is nothing there.. Here is my code for the trigger:
CREATE OR REPLACE TRIGGER JIM
BEFORE INSERT ON END
FOR EACH ROW ENABLE
DECLARE
V_AGE JIM.AGE%TYPE;
V_PRICE JIM.PRICE%TYPE;
v_prices NUMBER(20);
BEGIN
SELECT AGE,PRICE INTO V_AGE,V_PRICE FROM JIM WHERE ID=:NEW.ID;
v_prices:=V_AGE+V_PRICE;
INSERT INTO END VALUES(:new.ID,v_prices);
END;
However, When I insert values onto the JIM table using the following code:
insert into jim values(4,'Sim',45,100);
nothing actually gets stored on the END table. i am sort of new to triggers and its so confusing. Please let me know what to do. thanls
Don't use a keyword end as a table name, this causes problem during
creation of trigger. I've presumed the table's name as t_end.
I think you are confused on which table to define trigger. It seems
you should define on table jim instead of t_end.
I've presumed you have a sequence named seq_end to populate the id
column of the table t_end
So , your trigger creation statement will be as follows :
create or replace trigger trg_ins_jim before insert on jim for each row
declare
v_id_end t_end.id%type;
v_prices t_end.prices%type;
begin
v_prices := :new.age + :new.price;
v_id_end := seq_end.nextval; insert into t_end values(v_id_end, v_prices);
/* if you have defined sequence for t_end as default value of id column, you may change the upper row as "insert into t_end(prices) values(v_prices);" and there would be no need for "v_id_end" */
end;
and when you issue insert into jim values(4,'Sim',45,100); command, you'll also have values inserted into t_end.
If there is only one table, then the SELECT and the INSERT are redundant.
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
:NEW.PRICES := :NEW.AGE + :NEW.PRICE;
END;
Possible duplicate

How to Insert affected rows in a new table

Following is a dummy table on which i am performing some updations. Here, i am only manipulating the "NAME" column. What i need is to insert the affected row, (say) i change the second for for "ID"-2, then i need that only this row should get inserted in a new table with new / updated value. I have tried doing so via a trigger but it vomits an error regarding table mutation.
ID NAME
================
1 Vaz
2 Dan
3 Steve
The table i want the data to get inserted has the same structure as the above mentioned table and same columns with same datatype.
Please suggest if this could be done in any other way or I am writing wrong code for the trigger. Here is the code i wrote for the purpose :
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
INSERT INTO temp3
(SELECT NAME
FROM temp2
WHERE (:new.NAME<>:old.NAME));
END;
temp2 is the manipulation tabel and temp3 is the new one.
No need for a SELECT. And you probably also want to put the ID value into the table temp3
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
INSERT INTO temp3 (id, name)
values (:new.id, :new.name);
END;
/
And as the trigger is defined as update of name you don't really need to check if the name changed, but if you want, you can do:
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
if ( (:new.name <> :old.name)
OR (:new.name is null and :old.name is not null)
OR (:new.name is not null and :old.name is null))
then
INSERT INTO temp3 (id, name)
values (:new.id, :new.name);
end if;
END;
/

Edit a row you just inserted in the db

I got an example that better explains the situation. I have a table A
CREATE TABLE A( ID NUMBER, VAL NVARCHAR2(255) )
and I create a trigger that does an update on the row it's just inserted
CREATE OR REPLACE TRIGGER XXX
AFTER INSERT
ON A
FOR EACH ROW
DECLARE
BEGIN
UPDATE A SET VAL = 'LOL' WHERE ID = :NEW.ID;
END;
When I perform an insert
INSERT INTO A VALUES(1, 'XX')
I get
ORA-04091: table name is mutating, trigger/function may not see it
Is there a workaround?
You don't need an update, just assign the new value in a BEFORE trigger.
CREATE OR REPLACE TRIGGER XXX
BEFORE INSERT --<< You need a BEFORE trigger for this to work.
ON A
FOR EACH ROW
BEGIN
:new.val := 'LOL';
END;

Oracle 'statement level' Trigger

I want to create a Statement level Trigger which means I want to insert only one record into table EMP_AUDIT when 1 or more rows are inserted into table EMP. For example: if I have 10 records inserted into EMP, then only 1 record should be inserted into EMP_AUDIT table.
There are no constraints on columns. (i.e. can be NULL)
I tried to use the following trigger but it gives me the Error(2,2): PL/SQL: SQL Statement ignored
Error(2,14): PL/SQL: ORA-00947: not enough values
CREATE OR REPLACE
TRIGGER TRIG_EMP AFTER INSERT ON EMP
BEGIN
INSERT INTO EMP_AUDIT
VALUES (TRANID,EMPNUM,SYSDATE);
END;
CREATE TABLE EMP
(TRANID NUMBER,
EMPNUM VARCHAR2(100),
EMPLOC VARCHAR2(100));
CREATE TABLE EMP_AUDIT
(EVENTID NUMBER,
EMPNUM VARCHAR2(100),
ENTRDATE DATE);
The statement-level trigger (which you have) cannot see the data that was inserted. After all, if there were 10 rows inserted, what values should the columns be for your audit table?
You need a row-level trigger for this to work, e.g.:
CREATE OR REPLACE
TRIGGER TRIG_EMP
AFTER INSERT ON EMP
FOR EACH ROW
BEGIN
INSERT INTO EMP_AUDIT
VALUES (:NEW.TRANID,:NEW.EMPNUM,:NEW.SYSDATE);
END;
Use this piece of code:
CREATE OR REPLACE TRIGGER
TRIG_EMP
AFTER INSERT ON EMP
FOR EACH ROW
BEGIN
INSERT INTO EMP_AUDIT
VALUES (:NEW.TRANID,:NEW.EMPNUM,:NEW.SYSDATE);
END;

Resources