ORACLE trigger instead of insert on view with a nested table - oracle

I have to write a trigger. It compiles everything, but if I want to insert something in my view, i get an error message. Maybe you can help me.
SET DEFINE off;
CREATE OR REPLACE TRIGGER LieferantOV_trig
INSTEAD OF INSERT
ON LIEFERANT_OV
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO Lieferant (LiefNr, Name, Adresse)
VALUES(:new.LiefNr, :new.Name, ntTAdresse());
INSERT INTO TABLE (SELECT Adresse FROM Lieferant ) VALUES
(TAdresse(:new.Straße, :new.PLZ, :new.Ort));
END IF;
END;
INSERT INTO Lieferant_OV
VALUES(752443, 'Laepple Teublitz', 'Maxstr. 12', '93158', 'Teublitz');
For the nested Table
CREATE OR REPLACE TYPE TAdresse AS OBJECT(
Straße VARCHAR2(50),
PLZ VARCHAR2(5),
Ort VARCHAR2(50)
);
CREATE TABLE Lieferant(
LiefNr number(6) PRIMARY KEY,
Name varchar2(20) NOT NULL
);
1.
CREATE OR REPLACE TYPE ntTAdresse AS TABLE OF TAdresse;
2.
ALTER TABLE Lieferant ADD Adresse ntTAdresse NESTED TABLE Adresse STORE AS TAdresseNT;
CREATE OR REPLACE VIEW Lieferant_OV (LiefNr, Name, Straße, PLZ, ORT)
AS SELECT k.LiefNr, k.Name, l.Straße, l.PLZ, l.Ort
FROM Lieferant k, table(k.Adresse) l;

The syntax should be like this:
CREATE OR REPLACE TRIGGER LieferantOV_trig
INSTEAD OF INSERT
ON LIEFERANT_OV
FOR EACH ROW
BEGIN
INSERT INTO Lieferant (LiefNr, Name, Adresse)
VALUES(:new.LiefNr, :new.Name, ntTAdresse(TAdresse(:new.Straße,:new.PLZ,:new.ORT));
END;
You can skip IF INSERTING THEN because your trigger fires only on INSERT
Note, by this each record have at maximum only one address, thus a nested table does not make much sense.
In order to add an address to existing Lieferant you can do this one:
CREATE OR REPLACE TRIGGER LieferantOV_trig
INSTEAD OF INSERT
ON LIEFERANT_OV
FOR EACH ROW
DECLARE
lieferantCount INTEGER;
BEGIN
select count(*)
into lieferantCount
from Lieferant
where LiefNr = :new.LiefNr
and Name = :new.Name;
if lieferantCount = 0 then
INSERT INTO Lieferant (LiefNr, Name, Adresse)
VALUES(:new.LiefNr, :new.Name, ntTAdresse(TAdresse(:new.Straße,:new.PLZ,:new.ORT)));
else
UPDATE Lieferant
SET Adresse = Adresse MULTISET UNION ntTAdresse(TAdresse(:new.Straße,:new.PLZ,:new.ORT))
WHERE LiefNr = :new.LiefNr
and Name = :new.Name;
end if;
END;

I think you want do this if you want to keep one row per insertion.
CREATE OR REPLACE TRIGGER LieferantOV_trig
INSTEAD OF INSERT
ON LIEFERANT_OV
FOR EACH ROW
BEGIN
INSERT INTO Lieferant (LiefNr, Name, Adresse)
VALUES(:new.LiefNr, :new.Name, ntTAdresse(TAdresse(:new.Straße, :new.PLZ, :new.Ort)));
END;
/
This works if the Adresse column contain only one Adress. But that is not purpose of a nested table. So you probably want to check if there is an existing row in the table with given LiefNr. If yes, only insert into the nested table.

Related

Need logs for the data entered in oracle

I have entered the data in one of the table present in oracle database
Table name is TABLE1 and it has 3 columns i.e. (ROLL_NO, NAME, AGE)
CREATE TABLE TABLE1
(
ROLL_NO NUMBER NOT NULL,
NAME VARCHAR2(10),
AGE NUMBER
);
INSERT INTO TABLE1 VALUES(1, 'BOB', 20);
INSERT INTO TABLE1 VALUES(2, 'TOM', 21);
INSERT INTO TABLE1 VALUES(3, 'SAM', 22);
I just want the logs of the data entered in oracle. How can I get the logs for these data entered
You can create an extra table in order to retain those DML logs such as
CREATE TABLE log_table1
(
id NUMBER,
dml_type VARCHAR2(1),
roll_no NUMBER,
name VARCHAR2(10),
age NUMBER,
client_info VARCHAR2(64),
osuser VARCHAR2(128),
module VARCHAR2(64),
machine VARCHAR2(64),
time DATE
);
/
and a database trigger to populate that table along with a sequence to generate identity value for each record such as
CREATE SEQUENCE seq_table1;
/
and then
CREATE OR REPLACE TRIGGER trg_log_table1
AFTER INSERT OR UPDATE OR DELETE ON table1
FOR EACH ROW
DECLARE
v_dml_type VARCHAR2(1):='I';
v_client_info v$session.client_info%TYPE;
v_osuser v$session.osuser%TYPE;
v_module v$session.module%TYPE;
v_machine v$session.machine%TYPE;
BEGIN
SELECT client_info, osuser, module, machine
INTO v_client_info, v_osuser, v_module, v_machine
FROM v$session
WHERE sid = sys_context('USERENV','SID');
IF inserting OR updating THEN
IF updating THEN v_dml_type := 'U'; END IF;
INSERT INTO log_table1
VALUES(seq_table1.nextval,v_dml_type,:new.roll_no,:new.name,:new.age,v_client_info, v_osuser, v_module, v_machine,SYSDATE);
ELSIF deleting THEN
v_dml_type := 'D';
INSERT INTO log_table1
VALUES(seq_table1.nextval,v_dml_type,:old.roll_no,:old.name,:old.age,v_client_info, v_osuser, v_module, v_machine,SYSDATE);
END IF;
END;
/
If you get null values for client_info and module for some rows, probably need to call those procedures
dbms_application_info.set_module(i_module,i_action);
dbms_application_info.set_client_info(i_client);
from the application from which you reach the database in order to populate those columns.

Oracle Trigger to insert /update to another table

Basically, i wanted to create a oracle trigger, which will track the Insert/Updates in a table and i wanted to insert these changed records alone into a new table on a daily basis.This new table data will be using for my daily data refresh (more like a CDC approach)
I was using below code. My expectation was when my CUSTOMER table got INSERT/UPDATED,that record should be available in CUSTOMER_D table.But i am missing something in below code
CREATE OR REPLACE TRIGGER CUST_TRIG AFTER INSERT OR UPDATE ON CUSTOMERS
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW WHEN (OLD.ID <> NEW.ID)
begin
IF INSERTING THEN
begin
INSERT INTO CUSTOMERS_D
(ID, NAME, AGE, ADDRESS, SALARY) values
(:new.ID, :new.NAME, :new.AGE, :new.ADDRESS, :new.SALARY);
-- assuming, there is an unique key on id
exception
when dup_val_on_index then
null;
end;
ELSIF UPDATING THEN
IF :NEW.ID = :OLD.ID THEN
UPDATE CUSTOMERS_D DWT
SET DWT.ID = :NEW.ID,
DWT.NAME = :NEW.NAME,
DWT.AGE = :NEW.AGE,
DWT.ADDRESS = :NEW.ADDRESS,
DWT.SALARY = :NEW.SALARY;
END IF;
MERGE INTO CUSTOMERS_D D
USING DUAL
ON (D.ID = :NEW.ID)
WHEN MATCHED THEN
UPDATE SET D.NAME = :NEW.NAME,
D.AGE = :NEW.AGE,
D.ADDRESS = :NEW.ADDRESS,
D.SALARY = :NEW.SALARY
WHEN NOT MATCHED THEN
INSERT
(D.ID, D.NAME, D.AGE, D.ADDRESS, D.SALARY) VALUES
(:NEW.ID, :NEW.NAME, :NEW.AGE, :NEW.ADDRESS, :NEW.SALARY);
END IF;
end test;
Your trigger in its present form has too many unnecessary constructs which you should get rid of. All you need ( as you confirmed in the comments ) is a single insert statement into customers_d whenever a record gets added or inserted from customers table.
I would recommend you to add another column indicating the time of transaction -MODIFIED_TIME
CREATE OR REPLACE TRIGGER CUST_TRIG
AFTER INSERT OR UPDATE ON CUSTOMERS
FOR EACH ROW
begin
INSERT INTO CUSTOMERS_D
(ID, NAME, AGE, ADDRESS, SALARY,modified_time )
values (:new.ID,
:new.NAME,
:new.AGE,
:new.ADDRESS,
:new.SALARY,
systimestamp);
end ;
/
DEMO
At first glance I can see the one issue in this trigger when new record is inserted the OLD.ID will be NULL so WHEN ((OLD.ID <> NEW.ID) will be FALSE and the trigger wont be invoked on INSERT into CUST_TRIG. Just add following condition:
FOR EACH ROW WHEN ((OLD.ID <> NEW.ID) OR (OLD.ID IS NULL))

audit trigger in oracle12c is compiling with error

refer my ER
CREATE OR REPLACE TRIGGER EVA
AFTER INSERT ON C_EVALUATION
FOR EACH ROW
DECLARE
v_cid number(25);
v_isbn number(25);
v_cname VARCHAR2(50);
v_tittle VARCHAR2(150);
v_date date;
v_location VARCHAR2(50);
v_eva VARCHAR2(250);
BEGIN
v_cid:=:OLD.C_ID;
v_isbn:=:OLD.B_ISBN;
v_eva:=:OLD.e_desc;
SELECT C_NAME INTO v_cname FROM C_CUSTOMER WHERE C_ID = v_cid;
select l_date INTO v_date FROM C_LEND where c_id = v_cid;
select B_TITTLE INTO v_tittle FROM C_BOOK WHERE B_ISBN = v_isbn;
SELECT TOWN INTO v_location FROM COPY WHERE B_ISBN = v_isbn;
IF :NEW.R_ID IS NULL
THEN
INSERT INTO e_audit (
c_name,
b_tittle,
h_date,
location,
evaluation
) VALUES (
v_cname,
v_tittle,
v_date,
v_location,
v_eva
);
END IF;
END;
/
in the book table evaluation is given, but the evaluation,rate should be given by the customer, if the customer gives the rate as null value the below trigger should work. But we are getting an error saying as statement ignored, table or view does not exist. I checked it twice or more than that but all the table name and ID are perfect. please give us the solution to sort out the error
Something else looks wrong here. This is an INSERT trigger, but you are referring to :OLD.e_desc. This is wrong. An INSERT trigger should only refer to :NEW. A DELETE trigger should only refer to :OLD, whilst an UPDATE trigger can refer to both :NEW and :OLD. :OLD gives the value of the record before the change, but for INSERT there was no such record. I think what you really want is to use :NEW.e_desc, :NEW.c_id and :NEW.b_isbn. But I am guessing a little!
EDIT
Your diagram does not show all the fields? But what I think you need is:
SELECT TOWN into v_location FROM copy inner join lend
ON lend.copyid = copy.copyid WHERE b_isbn = v_isbn
AND lend.c_id = v_cid
What I am assuming here is that lend has a field copyid linking to copy.copyid and that it has a field linking to customer id. I am also assuming that copy has a field linking to b_isbn in book. According to your diagram, this must all be true, it is just that I do not know the field names.

PLS-00049: bad bind variable Triggers

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.

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;
/

Resources