Trigger on UPDATE OF a particular column Oracle - oracle

CREATE TABLE regions (
region_id number NOT NULL,
region_name varchar2(25) NULL,
CONSTRAINT reg_id_pk PRIMARY KEY (region_id)
);
CREATE TABLE tbl_history
(
operation character varying(120) NOT NULL ,
table_name character varying(120) NOT NULL ,
column_name character varying(120) NOT NULL,
old_value character varying(120),
new_value character varying(120)
);
CREATE OR REPLACE trigger regions_audit
BEFORE INSERT OR DELETE OR UPDATE ON regions
FOR EACH ROW
ENABLE
DECLARE
BEGIN
for c in (SELECT table_name, column_name from user_tab_columns ) loop
if updating(c.column_name ) then
INSERT INTO tbl_history (operation,table_name, column_name, old_value, new_value)
VALUES('Update',c.table_name,c.column_name,:old.column_name,:new.column_name);
end if;
end loop;
END;
/
Trigger on UPDATE OF a Dynamic column?
result is:
operation table_name column_name old_value new_value_
UPDATE regions region_name Asia Europe
UPDATE regions region_id 5 4

That's in vain, you can't create "generic" trigger which would work on any table. In Oracle, triggers are anchored to their tables, so code which shows how to do it might be
SQL> create or replace trigger regions_audit
2 before insert or delete or update on regions
3 for each row
4 enable
5 declare
6 begin
7 if updating then
8 insert into tbl_history
9 (operation, table_name, column_name, old_value, new_value)
10 values
11 ('Update', 'regions', 'region_name', :old.region_name,:new.region_name);
12 end if;
13 end;
14 /
Trigger created.
SQL>
In other words: table and column name are static; table name is regions as that's the table that trigger is based on. Column name is region_name as you really shouldn't be modifying primary key column's value. Old and new values are then also related to region_name.
As of other operations (deleting, inserting), you'd do similarly.

Related

Fire trigger based on two entries in table

I would like to know if it is possible to check a table for two conditions in a trigger.
More precisely, I want to decide what to enter based on entries from two columns.
create or replace TRIGGER AUDIT_TABLE1
-- starts on every update or insert
AFTER INSERT OR UPDATE ON table1
FOR EACH ROW
DECLARE
v_user varchar2(30);
v_userid USERS.UUID%TYPE;
BEGIN
v_user := SYS_CONTEXT('APEX$SESSION','APP_USER');
select UUID into v_userid from USERS where lower(username)=lower(v_user);
IF :NEW.COLUMN4 ='REQUESTED' AND :NEW.COLUMN5='TEXT123' THEN
INSERT INTO AUDIT_TABLE1
(COLUMN1,COLUMN2,COLUMN3)
VALUES
(:NEW.COLUMN1, :NEW.COLUMN2, 'TEXT123');
ELSIF :NEW.COLUMN4 ='REQUESTED' AND :NEW.COLUMN5='TEXT3265' THEN
INSERT INTO AUDIT_TABLE1
(COLUMN1,COLUMN2,COLUMN3)
VALUES
(:NEW.COLUMN1, :NEW.COLUMN2, 'TEXT52354');
END IF;
END;
I'll get no error when compiling this trigger but I as well get no entry in the AUDIT_TABLE1 table.
The principle is OK, which means that you either didn't post what you really have (which might be the case, regarding column names), or you didn't insert what you should.
Sample tables:
SQL> CREATE TABLE table1
2 (
3 column4 VARCHAR2 (20),
4 column5 VARCHAR2 (20)
5 );
Table created.
SQL> CREATE TABLE audit_table
2 (
3 column4 VARCHAR2 (20),
4 column5 VARCHAR2 (20),
5 column6 VARCHAR2 (20)
6 );
Table created.
SQL>
Trigger:
SQL> CREATE OR REPLACE TRIGGER audit_table1
2 -- starts on every update or insert
3 AFTER INSERT OR UPDATE
4 ON table1
5 FOR EACH ROW
6 DECLARE
7 v_user VARCHAR2 (30);
8 BEGIN
9 v_user := SYS_CONTEXT ('APEX$SESSION', 'APP_USER');
10
11 IF :new.column4 = 'REQUESTED'
12 AND :new.column5 = 'TEXT123'
13 THEN
14 INSERT INTO audit_table (column4, column5, column6)
15 VALUES (:new.column4, :new.column5, 'TEXT123');
16 ELSIF :new.column4 = 'REQUESTED'
17 AND :new.column5 = 'TEXT3265'
18 THEN
19 INSERT INTO audit_table (column4, column5, column6)
20 VALUES (:new.column4, :new.column5, 'TEXT52354');
21 END IF;
22 END;
23 /
Trigger created.
Testing:
These values won't insert anything because none of IF conditions is met:
SQL> INSERT INTO table1
2 VALUES ('x', 'y');
1 row created.
SQL> SELECT * FROM audit_table;
no rows selected
SQL>
These values will insert a row into the audit table:
SQL> INSERT INTO table1
2 VALUES ('REQUESTED', 'TEXT123');
1 row created.
SQL> SELECT * FROM audit_table;
COLUMN4 COLUMN5 COLUMN6
-------------------- -------------------- --------------------
REQUESTED TEXT123 TEXT123
SQL>

PL/SQL: How to insert all records from table type to another table without for loop

I have probably trivial problem but I can't nail the logic quite right.
I have following types:
create or replace TYPE test_rec FORCE
AS OBJECT (ref_id NUMBER (20)
,ref_type VARCHAR2 (4));
create or replace TYPE test_ref_tbl FORCE
AS TABLE OF test_rec;
and actual table
CREATE TABLE my_tbl
( id number(10) NOT NULL,
ref_id varchar2(20) NOT NULL,
ref_type varchar2(4),
CONSTRAINT my_pk PRIMARY KEY (id)
);
Now, in one procedure I get variable test_ref_tbl with data and I have to insert everything to my_tbl, also id should be generated from sequence.
I managed to do this quite easily with for loop
FOR i IN 1 .. test_ref_tbl.COUNT LOOP
INSERT INTO my_tbl(id
,ref_id
,ref_type)
VALUES (my_test_sequence.NEXTVAL
,test_ref_tbl(i).ref_id
,test_ref_tbl(i).ref_type
);
END LOOP;
and everything works fine, but I got alot of flack for inserting data in for loop, I'm not plsql developer so maybe my colleagues are making my job harder just for the hell of it.
But to get back on topic, is there a way to do this without for loop?
Thanks
Yes, there is. Here's an example:
Creating test case first:
SQL> CREATE OR REPLACE TYPE test_rec FORCE AS OBJECT
2 (
3 ref_id NUMBER (20),
4 ref_type VARCHAR2 (4)
5 );
6 /
Type created.
SQL> CREATE OR REPLACE TYPE test_ref_tbl FORCE AS TABLE OF test_rec;
2 /
Type created.
SQL> CREATE TABLE my_tbl
2 (
3 id NUMBER (10) NOT NULL,
4 ref_id VARCHAR2 (20) NOT NULL,
5 ref_type VARCHAR2 (4),
6 CONSTRAINT my_pk PRIMARY KEY (id)
7 );
Table created.
SQL> CREATE SEQUENCE my_test_sequence;
Sequence created.
As data source, I'm using Scott's DEPT table.
SQL> DECLARE
2 l_tab test_ref_tbl;
3 BEGIN
4 SELECT test_rec (deptno, SUBSTR (dname, 1, 4))
5 BULK COLLECT INTO l_tab
6 FROM dept;
7
8 -- this is what you're looking for
9 INSERT INTO my_tbl (id, ref_id, ref_type)
10 SELECT my_test_sequence.NEXTVAL, t.*
11 FROM TABLE (l_tab) t;
12 END;
13 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM my_tbl;
ID REF_ID REF_
---------- -------------------- ----
1 10 ACCO
2 20 RESE
3 30 SALE
4 40 OPER
SQL>

Need to populate table with Foreign and Primary key

I need to create three tables from all_tab_col system table such that schema details are in one schema_detail table, table details are in table_detail table and column details are in col_table. These three tables are to be populated simultaneously through a stored procedure, with PK(generated using SEQUENCE) in schema_detail is FK in table_detail table and PK(generated using SEQUENCE) in table_detail is FK in col_detail table.
SQL is a set based language, so I would be tempted to solve your task with three set bases steps.
Some mock up tables (just add columns for the details you are interested in):
CREATE TABLE schema_detail (
schema_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
schema_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT schema_detail_pk PRIMARY KEY (schema_id)
);
CREATE TABLE table_detail (
schema_id NUMBER,
table_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
table_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT table_detail_pk PRIMARY KEY (table_id),
CONSTRAINT table_detail_fk FOREIGN KEY (schema_id)
REFERENCES schema_detail(schema_id)
ON DELETE CASCADE
);
CREATE INDEX table_detail_schema_idx ON table_detail(schema_id);
CREATE INDEX table_detail_name_idx ON table_detail(table_name);
CREATE TABLE col_detail (
table_id NUMBER,
col_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
col_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT col_detail_pk PRIMARY KEY (col_id),
CONSTRAINT col_detail_fk FOREIGN KEY (table_id)
REFERENCES table_detail(table_id)
ON DELETE CASCADE
);
CREATE INDEX col_detail ON col_detail(table_id);
I'd fill the table schema_detail first. PK is generated automatically:
INSERT INTO schema_detail(schema_name)
SELECT DISTINCT c.owner FROM all_tab_columns c ORDER BY owner;
SCHEMA_ID SCHEMA_NAME
1 APPQOSSYS
2 AUDSYS
3 CTXSYS
...
Next, I'd fill the tables. The schema_id needs to be looked up the the schema_detail table. Again, we let the PKs be generated automatically:
INSERT INTO table_detail(schema_id, table_name)
SELECT DISTINCT s.schema_id, c.table_name
FROM all_tab_columns c
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY table_name;
SCHEMA_ID TABLE_ID TABLE_NAME
1 8403 WLM_CLASSIFIER_PLAN
1 8404 WLM_FEATURE_USAGE
1 8405 WLM_METRICS_STREAM
...
And last, I'd fill the columns:
INSERT INTO col_detail(table_id, col_name)
SELECT DISTINCT t.table_id, c.column_name
FROM all_tab_columns c
JOIN table_detail t ON c.table_name = t.table_name
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY s.schema_id, t.table_id, c.column_name;
Does this solve your question or do you need a PL/SQL procedure?
In case you insist on a PL/SQL stored procedure, I would code it along the lines of:
CREATE OR REPLACE PROCEDURE myproc IS
schema_id schema_detail.schema_id%type;
table_id table_detail.table_id%type;
FUNCTION lookup_insert_schema (p_schema_name VARCHAR2)
RETURN NUMBER
IS
sid schema_detail.schema_id%type;
BEGIN
BEGIN
SELECT schema_id INTO sid
FROM schema_detail
WHERE schema_name = p_schema_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO schema_detail (schema_name)
VALUES (p_schema_name)
RETURNING schema_id INTO sid;
END;
RETURN sid;
END lookup_insert_schema;
-- lookup p_table_name in table table_detail, if not found, insert it
FUNCTION lookup_insert_table (p_schema_id NUMBER, p_table_name VARCHAR)
RETURN NUMBER
IS
tid table_detail.table_id%type;
BEGIN
BEGIN
SELECT table_id INTO tid
FROM table_detail
WHERE schema_id = p_schema_id
AND table_name = p_table_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO table_detail (schema_id, table_name)
VALUES (p_schema_id, p_table_name)
RETURNING table_id INTO tid;
END;
RETURN tid;
END lookup_insert_table;
BEGIN
FOR r IN (SELECT * FROM all_tab_columns)
LOOP
schema_id := lookup_insert_schema(r.owner);
table_id := lookup_insert_table(schema_id, r.table_name);
INSERT INTO col_detail (table_id, col_name)
VALUES (table_id, r.column_name);
END LOOP;
END myproc;
/

How to find currval in oracle 12c for identity columns

I create a table in oracle 12 with a column as identity. The problem is that I want to find the current value of identity column. How I can find this, please someone help me to solve this problem...
You could make use of the data dictionary views *_TAB_IDENTITY_COLS
Here is a working example.
create TABLE t ( ID INTEGER GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR2(10));
Table created.
INSERT INTO t(NAME) VALUES ( 'TESTER' );
1 row(s) inserted.
select SEQUENCE_NAME FROM user_tab_identity_cols WHERE TABLE_NAME ='T' ;
SEQUENCE_NAME
-----------
ISEQ$$_1727054
Now you could get the currval from this sequence.
select ISEQ$$_1727054.CURRVAL FROM DUAL;
CURRVAL
-------
1
LIVESQL DEMO - Free OTN account required.
Why do you want to know? If to insert a child row you can use the returning clause of the insert statement like this:
insert into master (...) values (...)
returning master_id into l_master_id;
insert into child (master_id, ...) values (l_master_id, ...);
As I've written in this blog post, this query produces all the identities' backing sequences' currval values of your schema:
with
function current_value(p_table_name varchar2) return number is
v_current number;
begin
for rec in (
select data_default
from user_tab_cols
where table_name = p_table_name
and data_default is not null
and identity_column = 'YES'
)
loop
execute immediate replace(
'select ' || rec.data_default || ' from dual',
'.nextval',
'.currval'
) into v_current;
return v_current;
end loop;
return null;
end;
select *
from (
select table_name, current_value(table_name) current_value
from user_tables
)
where current_value is not null
order by table_name;
/
The output could be something like this:
TABLE_NAME CURRENT_VALUE
--------------------------
T1 3
T2 1

How to create one table with same datatype of all columns from another table?

I have one table and I need to create one dummy table with same column names and data but with different datatypes for some of the columns.
For example: Table-1 has two columns C1 (varchar2) and C2(date).
I need to create a dummy table called Table-2 with columns C1 (varchar2) and C2(varchar2).
Please suggest the way to do it in oracle.
The best way to do this is to duplicate the table with create as select, without the data, for example -
create table Table-2 as select * from Table-1 where 1=0;
And then alter the datatypes of the required columns manually like so -
alter table Table-2 modify (C2 varchar2);
After the column was altered you can push the data from Table-1 into Table-2, using proper conversions. in your example -
insert into Table-2 select C1, to_char(C2,'dd-mm-yyyy') from Table-1;
Assuming that all the columns of the starting table can be converted ( implicit conversion) in VARCHAR2, you can do something like the following.
Say you have this table:
SQL> create table table1 (
2 date_field date,
3 varchar_field varchar2(1000),
4 number_field number
5 );
Table created.
SQL> insert into table1 values (sysdate, 'some text', 999);
1 row created.
SQL> commit;
Commit complete.
You can build a dynamis SQL that creates another table and copies the data from one table to another, using implicit type conversion:
SQL> declare
2 stm varchar2(32767);
3 begin
4 select 'create table table2( ' ||
5 listagg(column_name, ' varchar2(4000), ') within group (order by column_name) ||
6 ' varchar2(4000) )'
7 into stm
8 from user_tab_columns
9 where table_name = 'TABLE1';
10 --
11 execute immediate stm;
12 --
13 select 'insert into table2( ' ||
14 listagg(column_name, ', ') within group (order by column_name) ||
15 ' ) select ' ||
16 listagg(column_name, ', ') within group (order by column_name) ||
17 ' from table1'
18 into stm
19 from user_tab_columns
20 where table_name = 'TABLE1';
21 execute immediate stm;
22 end;
23 /
PL/SQL procedure successfully completed.
SQL> select * from table2;
DATE_FIELD NUMBER_FIELD VARCHAR_FIELD
--------------- --------------- ---------------
27-APR-16 some text 999
SQL>

Resources