Change detection capture in Oracle DB tables - oracle

I wanted to if it is possible to detect changes in Oracle DB table for each column and its value and capture the change is a separate temporary table ?

Yes; people usually do that using triggers.
Here's a simple example:
SQL> create table dept_log
2 (deptno number,
3 dname varchar2(20),
4 loc varchar2(20),
5 when date
6 );
Table created.
SQL>
SQL> create or replace trigger trg_bu_dept
2 before update on dept
3 for each row
4 begin
5 if :new.dname <> :old.dname or
6 :new.loc <> :old.loc
7 then
8 insert into dept_log (deptno, dname, loc, when)
9 values (:new.deptno, :old.dname, :old.loc, sysdate);
10 end if;
11 end;
12 /
Trigger created.
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> update dept set loc = 'LONDON' where deptno = 40;
1 row updated.
SQL> select * From dept_log;
DEPTNO DNAME LOC WHEN
---------- -------------------- -------------------- -------------------
40 OPERATIONS BOSTON 11.04.2018 22:00:23
SQL>

Related

How to store and retrieve data from temp table

I am new to PL-SQL. Please can someone help me on wrapping the below.
I need to:
Copy contents of table A and store in global temp table
Do an update on Table A & other tasks
Revert contents of Table A from global temp table
Delete temp table
ANy help is much appreciated.
Here you are:
Sample tables:
SQL> create table test as select * from dept;
Table created.
SQL> create global temporary table gtt_test as
2 select * from test where 1 = 2;
Table created.
SQL> select * from test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
1 Dept 1 NY
2 Dept 2 London
6 rows selected.
SQL> select * from gtt_test;
no rows selected
Anonymous PL/SQL block which does what you asked:
SQL> begin
2 -- copy contents ...
3 insert into gtt_test select * from test;
4
5 -- update A
6 update test set loc = 'blabla';
7 delete from test where deptno = 20;
8
9 -- revert
10 delete from test;
11 insert into test select * from gtt_test;
12
13 -- delete temp
14 delete from gtt_test;
15 end;
16 /
PL/SQL procedure successfully completed.
At the end, no changes whatsoever:
SQL> select * from test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
1 Dept 1 NY
2 Dept 2 London
6 rows selected.
SQL> select * from gtt_test;
no rows selected
SQL>
[EDIT]
Query you posted as a comment isn't correct; dynamic SQL must be enclosed into single quotes, it must not be terminated by a semi-colon while all other statements must. Fixed, it should be
BEGIN
EXECUTE IMMEDIATE 'create table camel_route_temp as '
|| ' select * from acquire.CAMEL_ROUTE_PROCESS';
DBMS_OUTPUT.put_line ('Temp table created');
--update A
UPDATE acquire.camel_route_process
SET acquire = 'DISABLED',
assure = 'DISABLED',
validation = 'DISABLED',
report = 'DISABLED',
notification = 'DISABLED';
COMMIT;
DBMS_OUTPUT.put_line ('Aquire components disabled');
END;
/

Create a PL/SQL block to insert a new record into the Department table. Fetch the maximum department id from the Department table and add 10 to it;

Create a PL/SQL block to insert a new record into the Department table. Fetch the maximum department id from the Department table and add 10 to it
I can't see images, but - here's how I understood your homework:
Current table contents:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Procedure:
SQL> create or replace procedure p_ins_dept (par_dname in varchar2, par_loc in varchar2) is
2 l_deptno number;
3 begin
4 select max(deptno)
5 into l_deptno
6 from dept;
7
8 insert into dept (deptno, dname, loc)
9 values (nvl(l_deptno, 0) + 10, par_dname, par_loc);
10 end;
11 /
Procedure created.
Testing:
SQL> exec p_ins_dept ('Test', 'Zagreb');
PL/SQL procedure successfully completed.
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------------- --------------------
50 Test Zagreb --> here it is
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Note that such an approach is likely to fail in a multi-user environment where
two (or more) users simultaneously call the procedure,
fetch MAX department number value
add 10 to it
insert a row
which fails for the 2nd (and other) user because primary key is violated because of the 1st user - who successfully inserted the row
But OK; for a simple homework question, I guess you don't care much about such things.

Procedure with exceptions

I have try to create procedure for updating row in table 'Departments'. I have to check if department name is unique. If is not unique it has to be stored in new table 'ERROR_DEPART'. I have tried to do that with exception but I fail to execute code.
CREATE PROCEDURE UPD_DEPARTMENT IS
v_depid department.department_id%TYPE;
v_depn department.department_name%TYPE;
v_lid department.location_id%TYPE;
v_phn employees.phone_number%TYPE;
BEGIN
select distinct departments.department_name, locations.location_id, employees.phone_number
into v_depn, v_lid, v_phn
from departments
inner join locations
on departments.location_id = locations.location_id
inner join employees
on departments.manager_id = employees.employee_id
END;
I'd suggest a unique index on department name column. Doing so, Oracle won't let you update any department name so that it would make a duplicate.
Here's an example.
First, test case, based on Scott's DEPT table:
SQL> create table depart as select * From dept;
Table created.
SQL> create unique index ui1_dept on depart (dname);
Index created.
SQL> create table error_depart as select * From depart where 1 = 2;
Table created.
SQL>
The procedure - in the exception handler section - inserts a row into the ERROR_DEPART table if uniqueness is violated.
SQL> create or replace procedure p_upd_dept (par_deptno in number, par_dname in varchar2)
2 is
3 begin
4 update depart d set
5 d.dname = par_dname
6 where d.deptno = par_deptno;
7
8 exception
9 when dup_val_on_index then
10 insert into error_Depart(deptno, dname) values (par_Deptno, par_dname);
11 end;
12 /
Procedure created.
SQL>
Testing: First, what we have now?
SQL> select * From depart;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> begin
2 p_upd_dept(10, 'SALES'); --> a duplicate
3 p_upd_dept(20, 'new dept'); --> not a duplicate
4 end;
5 /
PL/SQL procedure successfully completed.
SQL> select * from depart;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 new dept DALLAS --> not a duplicate - updated
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> select * From error_depart;
DEPTNO DNAME LOC
---------- -------------- -------------
10 SALES --> duplicate
SQL>

Print a rollback query of a update query using a query

I need to print the rollback query of a update query.
My original query is
UPDATE EMPLOYEE SET NAME = 'SAMAN' WHERE ID=4;
The corresponding update for above query is
UPDATE EMPLOYEE SET NAME=(SELECT NAME FROM EMPLOYEE WHERE ID=4);
I need to print a query to rollback if above original query went wrong. I need it to print it via using a query also.
I'm using oracle 11g database.
You can do it by running a select query like below,
SELECT 'UPDATE EMPLOYEE SET NAME = '''||name||''' WHERE id = '||id||';' FROM employee;
and of course, run the select query before your update query.
As far as I understood the question, it is some kind of a log table you're looking for. I have no idea what you mean by "printing the rollback query"; never heard of anything like that.
So, let me demonstrate what I have on my mind. Review it, apply if it makes sense in your case. The code is commented, I hope you'll understand it
Prepare the scene:
SQL> -- Log table
SQL> create table emp_log
2 (id number constraint pk_el primary key,
3 empno number constraint fk_el_emp references emp (empno) not null,
4 datum date not null,
5 ename varchar2(20)
6 );
Table created.
SQL> -- A sequence which will be used to populate the ID column in the EMP_LOG table
SQL> create sequence seqa;
Sequence created.
SQL> -- Trigger; if new ENAME is different from the last one, log the change (i.e.
SQL> -- store the old ENAME)
SQL> create or replace trigger trg_bu_emp
2 before update on emp
3 for each row
4 begin
5 if :new.ename <> :old.ename then
6 insert into emp_log (id, empno, datum, ename)
7 values (seqa.nextval, :new.empno, sysdate, :old.ename);
8 end if;
9 end;
10 /
Trigger created.
OK, let's see how it works:
SQL> -- Some employees in department 10
SQL> select empno, ename from emp where deptno = 10;
EMPNO ENAME
---------- ----------
7782 CLARK
7839 KING
7934 MILLER
SQL> -- Update KING's name to a better one (just kidding)
SQL> update emp set ename = 'LITTLEFOOT' where empno = 7839;
1 row updated.
SQL> -- What's in the log?
SQL> select * From emp_log order by id desc;
ID EMPNO DATUM ENAME
---------- ---------- ------------------- --------------------
5 7839 30.03.2018 20:22:15 KING
SQL> -- I don't like the new name after all; return the previous one
SQL> update emp e set
2 e.ename = (select l.ename from emp_log l
3 where l.empno = e.empno
4 and l.id = (select max(l1.id) from emp_log l1
5 where l1.empno = l.empno
6 )
7 )
8 where e.empno = 7839;
1 row updated.
SQL> -- What we've done?
SQL> select empno, ename from emp where deptno = 10;
EMPNO ENAME
---------- ----------
7782 CLARK
7839 KING
7934 MILLER
SQL> -- What's in the log now?
SQL> select * From emp_log order by id desc;
ID EMPNO DATUM ENAME
---------- ---------- ------------------- --------------------
6 7839 30.03.2018 20:22:33 LITTLEFOOT
5 7839 30.03.2018 20:22:15 KING
SQL>

Oracle get id of updated record in trigger update

I have the following trigger for update action.
CREATE OR REPLACE TRIGGER oferta_trigger
BEFORE UPDATE ON oferty
FOR EACH ROW
DECLARE
id_oferta number (10);
id_komis number (10);
id_test_komis number (10);
suma decimal(10,2) :=0;
CURSOR c1 IS
SELECT cena_aktualna, id_test_komis
FROM oferty
WHERE status = 'A';
BEGIN
id_oferta := :NEW.idk;
dbms_output.put_line(id_oferta);
SELECT komis_id INTO id_komis FROM oferty WHERE idk = id_oferta;
FOR i in c1 LOOP
IF i.id_test_komis = id_komis THEN
suma := suma + i.cena_aktualna;
END IF;
END LOOP;
UPDATE komisy SET wartosc_samochodow = suma WHERE idk = id_komis;
END;
During update operation.
UPDATE oferty SET status ='Z' WHERE idk =1;
I get the following error:
SQL Error: ORA-04091: table OFERTY is mutating, trigger/function may
not see it.
How to solve the problem. I think this is problem of getting id.
Here's an example based on Scott's schema. I altered the DEPT table, adding a new column (SUM_SAL) which is supposed to contain sum of all salaries in that department.
First, the good, old mutating table way.
SQL> create or replace trigger trg_sumsal
2 after update on emp
3 for each row
4 declare
5 l_sum number;
6 begin
7 select sum(sal) into l_sum
8 from emp
9 where empno = :new.empno;
10
11 update dept set sum_sal = l_sum
12 where deptno = :new.deptno;
13 end;
14 /
Trigger created.
SQL> update emp set sal = 5000 where ename = 'KING';
update emp set sal = 5000 where ename = 'KING'
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_SUMSAL", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_SUMSAL'
SQL>
As we already knew, that won't work.
Now, the compound trigger:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 l_deptno emp.deptno%type;
6
7 after each row is
8 begin
9 l_deptno := :new.deptno;
10 end after each row;
11
12 after statement is
13 l_sum number;
14 begin
15 select sum(sal) into l_Sum
16 from emp
17 where deptno = l_deptno;
18
19 update dept set sum_sal = l_sum
20 where deptno = l_deptno;
21 end after statement;
22 end;
23 /
Trigger created.
SQL> update emp set sal = 10000 where ename = 'KING';
1 row updated.
SQL> select * From dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Nice; that works!
[EDIT, after reading William's comment]
If several departments are affected within a single UPDATE statement, the above code won't work properly. Slightly adjusted, it looks like this & fixes that issue:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 type t_tab is table of number;
6 l_tab t_tab := t_tab();
7
8 after each row is
9 begin
10 l_tab.extend;
11 l_tab(l_tab.last) := :new.deptno;
12 end after each row;
13
14 after statement is
15 l_sum number;
16 begin
17 for i in l_tab.first .. l_tab.last loop
18 select sum(sal) into l_Sum
19 from emp
20 where deptno = l_tab(i);
21
22 update dept set sum_sal = l_sum
23 where deptno = l_tab(i);
24 end loop;
25 end after statement;
26 end;
27 /
Trigger created.
Testing:
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> update emp set sal = 10000 where ename in ('SMITH', 'KING');
2 rows updated.
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS 15975
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>

Resources