Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, SITARAM.

Asked: March 04, 2023 - 2:48 am UTC

Last updated: February 12, 2025 - 2:17 pm UTC

Version: 18c

Viewed 10K+ times! This question is

You Asked

Hello Tom,
What is the practical implementation of Instead of Triggers in the application? Can you provide some practical examples?

and Chris said...

You create instead of triggers on views. This enables you to insert/update rows in the underlying tables where this normally would raise an exception.

For example, this creates a view joining departments to employees. By default, you can't rows into the view.

Adding an instead of insert trigger intercepts DML on the view and redirects it to the underlying tables.

There should only be one row/department, so I've made it MERGE instead of inserting into this table. This then updates the department name with the new values.

If you only want to insert new departments and leave existing ones as-is, change this to an INSERT. And ensure DEPARTMENTS has appropriate primary/unique constraints so you can catch the ORA-00001 error:

create table departments ( 
  department_id int, department_name varchar2(30) 
);
create table employees ( 
  employee_id int, department_id int 
);

insert into departments values ( 1, 'New dept' );
insert into employees values ( 1, 1 );
commit;

create or replace view department_employees as 
  select * from departments d
  join   employees e
  using  ( department_id );
  
select * from department_employees;
/*
DEPARTMENT_ID DEPARTMENT_NAME                EMPLOYEE_ID
------------- ------------------------------ -----------
            1 New dept                                 1
*/

insert into department_employees ( department_id, department_name, employee_id ) 
values ( 2, 'Second dept', 2 );

--ORA-01779: cannot modify a column which maps to a non key-preserved table

create or replace trigger deem_instead_of_ins_t
instead of insert on department_employees
for each row
begin
  merge into departments 
  using dual 
  on   ( department_id = :new.department_id )
  when not matched then
    insert values ( :new.department_id, :new.department_name )
  when matched then
    update set department_name = :new.department_name;
    
  insert into employees values ( :new.employee_id, :new.department_id ); 
end;
/

insert into department_employees ( department_id, department_name, employee_id ) 
values ( 2, 'Second dept', 2 );
insert into department_employees ( department_id, department_name, employee_id ) 
values ( 1, 'Changed name', 3 );

select * from department_employees
order  by department_id;
/*
DEPARTMENT_ID DEPARTMENT_NAME                EMPLOYEE_ID
------------- ------------------------------ -----------
            1 Changed name                             1
            1 Changed name                             3
            2 Second dept                              2
*/


This method can be used when refactoring database schemas. For example, when splitting the columns from one table into two tables.

I've used this in the past to create a view which joins the tables to give the original view of the data. Then added an instead of insert trigger to redirect DML as needed. e.g.

create table new_table as
  select columns_to_remove from original_table;

rename original_table to original_table_t;

create view original_table as 
  select ot.id, nt.columns_to_remove
  from   original_table_t ot
  join   new_table nt
  where  ...;

create or replace trigger original_table_ins_t
instead of insert on original_table 
for each row
begin
  ...
end;


This allows you to change the schema with zero code changes! From the application's perspective, it's still accessing ORIGINAL_TABLE as it did when the view was a table.

I'd view this more as a stop-gap solution; ultimately the plan was to change the code to use the underlying tables, not the view. Switching to a view with instead of triggers was much faster than making the associated code changes.

Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Sitaram Sahoo, March 08, 2023 - 9:46 am UTC

Thanks for the response...
Is this something the user performs dml operations on view in th UI and the data are changed in the underlying tables accordingly?
Chris Saxon
March 08, 2023 - 1:41 pm UTC

Yes - you run DML on the view as if it was a normal table. Instead of triggers intercept this and convert it to DML on the underlying tables.

Sitaram Sahoo, March 08, 2023 - 1:57 pm UTC

Thanks a lot.

What about merge

A reader, January 28, 2025 - 6:49 pm UTC

Insert, update, delete work, but merge throws ORA-01446: cannot select ROWID from, or sample, a view DISTINCT, GROUP BY, etc.
Chris Saxon
January 29, 2025 - 2:44 pm UTC

When does MERGE throw that error? What exactly are you trying? Please provide a complete test case (create table + insert into + supporting objects + merge that errors).

Re:What about merge

Evgeny, February 01, 2025 - 6:23 pm UTC

Thank you so much for the prompt reply!

Here is an example:

create table test_tab1(pk number primary key, a number, b varchar2(100));

create table test_tab2(pk number primary key, a number, b varchar2(100));

-- tables should reside in different schemas with different level of access, but for simplicity, let's create them in the same schema

create or replace view v_test_tab
as
select pk, a, b from test_tab1
union all
select pk, a, b from test_tab2 where sys_context('userenv', 'os_user') = 'USERNAME'
;

create or replace trigger tiou_v_test_tab
instead of update on v_test_tab
for each row
declare
v_osuser varchar2(500) := sys_context('userenv', 'os_user');
begin
if v_osuser = 'USERNAME' then
-- Redirect the update to table2
update test_tab2
set a = :new.a,
b = :new.b
where pk = :old.pk;
else
update test_tab1
set a = :new.a,
b = :new.b
where pk = :old.pk;
end if;
end;
/

create or replace trigger tioi_v_test_tab
instead of insert on v_test_tab
for each row
declare
v_osuser varchar2(500) := sys_context('userenv', 'os_user');
begin
if v_osuser = 'USERNAME' then
-- Redirect the update to table2
insert into test_tab2(pk, a, b) values(:new.pk, :new.a, :new.b);
else
insert into test_tab1(pk, a, b) values(:new.pk, :new.a, :new.b);
end if;
end;
/

insert into v_test_tab(pk, a, b) values(1, 2, 3); -- works

update v_test_tab set a = 3, b = '2' where pk = 1; -- works

merge into v_test_tab t
using (select 1 as pk, 2 as a, '3' as b from dual) s
on (t.pk = s.pk)
when matched then update
set t.a = s.a
,t.b = s.b
when not matched then insert(pk, a, b) values(s.pk, s.a, s.b);

-- throws error: Error: ORA-01446: cannot select ROWID from, or sample, a view with DISTINCT, GROUP BY, etc.
Chris Saxon
February 03, 2025 - 5:48 pm UTC

Thanks for the example. There are a couple of problems:

There is a general restriction on merging into views with instead of triggers:

You cannot specify a target view on which an INSTEAD OF trigger has been defined.

That's not why you're getting this error though - it's because the view must be updatable. This is another requirement for merge:

In order to merge data into a view, the view must be updatable

Re:What about merge

Evgeny, February 03, 2025 - 8:16 pm UTC

Ok, thank you! That explains it!

Even though it may seem that, if I can update this view (literally, "update v_test_tab" - works) it should be considered "updateable", but, apparently, the usage of UNION ALL disqualifies this view as "updateable" :)
Chris Saxon
February 04, 2025 - 1:53 pm UTC

Try dropping the instead of triggers and see what happens when you update the view.

Trigger dropped automatically when Underlying table altered

Sivakumar, February 11, 2025 - 3:17 pm UTC

We have been using Instead of trigger in our enterprise banking application.

We noticed an below issue :

Whenever the underlying table altered the instead of trigger dropped silently and automatically.

Is there anyway or workaround to prevent this ?

We are using oracle 19.
Chris Saxon
February 12, 2025 - 2:17 pm UTC

Can you share a test case demonstrating this?

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library