Home>Question Details



-- Thanks for the question regarding "updatable views", version 10.1.0

Submitted on 7-Apr-2008 14:31 Central time zone
Last updated 9-Apr-2008 14:38

You Asked

Hi Tom,

I'm facing a problem with updateable views and instead of triggers.

I've to deal with a "big" table, more than 300 columns and hundred thousands
of records.

I would like to replace this table with a view and splitting the "big"
table in several easier to mantaine tables.

The first tests figured out an big performance boost, but I'm facing problems
with updates.

Replacing the values of a column, taking some minutes on the original
table, but breaks after more than half an hour with end of rollback space.

Is there a possibility to easy figure out which columns are to be changed
to issue a more precise update statement? Or do I have to build a case construct
to check 300 old values versus new values, or do I understand something wrong?

My idea is to have a view acting as a replacement for the "big" table
and the user/application should not face any difference of behavior
except the speed (hopefully).

The problem is that I can not change the application. So my view must be
as transparent as possible for the application. Furthermore a direct change
via sqlplus must be possible, in this case I can not predict the column(s)
going to be changed.

My intention is to get the information similar to "The columns c27, c96, c198
are to be changed", so that I can issue inside the trigger:

update table t1 set (c27, c96) = (....) where ....;
update table t2 set (c198) = (....) where ....;

instead of the following updates

update table t1 set (c1, .... , c27, ...., c96, ...., c129 ) = (....) where ....;
update table t2 set (c130, .... , c198, ..., c256 ) = (....) where ....;
update table t3 set (c257, ...., c305 ) = (....) where ....;

Following an Example with smaller tables:


--Former table which should be replaced by a view:
--
--create table t_total (
--    TableIndex    number    (10),
--    ColumnName1    char (20),
--    ColumnName2    char (20),
--    ColumnName3    char (20),
--    ColumnName4    char (20),
--    ColumnName5    char (20),
--    ColumnName6    char (20),
--    ColumnName7    char (20),
--    ColumnName8    char (20)
--);


-- new basic tables:

create table t1 (
    TableIndex number (10), ColumnName1 char (20), ColumnName2 char (20), ColumnName3 
char (20)
);
create unique index t1_TableIndex on t1 (TableIndex);

create table t2 (
    TableIndex number (10), ColumnName4 char (20), ColumnName5 char (20), ColumnName6 
char (20)
);
create unique index t2_TableIndex on t2 (TableIndex);

create table t3 (
    TableIndex number (10), ColumnName7 char (20), ColumnName8 char (20)
);
create unique index t3_TableIndex on t3 (TableIndex);

-- view:

create or replace view t_total as
select
    
t1.TableIndex,t1.ColumnName1,t1.ColumnName2,t1.ColumnName3,t2.ColumnName4,t2.ColumnName5,t
2.ColumnName6,t3.ColumnName7,t3.ColumnName8
from 
    t1, t2, t3
where
    t1.TableIndex = t2.TableIndex and t1.TableIndex = t3.TableIndex;

-- triggers:

-- I use 2 triggers, one for insert, one for update, so I don't need to distinguish
-- inside the triggers if I'm doing an insert or update.
-- Disadvantage: having two triggers to maintain.

-- insert:

CREATE OR REPLACE TRIGGER "t_total_TRIGGER_insert" 
INSTEAD OF
   INSERT 
ON t_total
   REFERENCING OLD AS old_value NEW AS new_value FOR EACH ROW
BEGIN
       INSERT INTO t1 (
    TableIndex,ColumnName1,ColumnName2,ColumnName3
    ) 
    VALUES (
    
:new_value.TableIndex,:new_value.ColumnName1,:new_value.ColumnName2,:new_value.ColumnName3

    );
       INSERT INTO t2 (
    TableIndex,ColumnName4,ColumnName5,ColumnName6
    ) 
    VALUES (
    
:new_value.TableIndex,:new_value.ColumnName4,:new_value.ColumnName5,:new_value.ColumnName6

    );
       INSERT INTO t3 (
    TableIndex,ColumnName7,ColumnName8
    ) 
    VALUES (
    :new_value.TableIndex,:new_value.ColumnName7,:new_value.ColumnName8
    );
END;
/

-- update

CREATE OR REPLACE TRIGGER "t_total_TRIGGER_update" 
INSTEAD OF
   UPDATE
ON t_total
   REFERENCING OLD AS old_value NEW AS new_value FOR EACH ROW
BEGIN
       UPDATE t1 SET (
    TableIndex,ColumnName1,ColumnName2,ColumnName3
    ) 
    = (
    select
    
:new_value.TableIndex,:new_value.ColumnName1,:new_value.ColumnName2,:new_value.ColumnName3

    from dual
    )
    where t1.TableIndex = :new_value.TableIndex;

       UPDATE t2 SET (
    TableIndex,ColumnName4,ColumnName5,ColumnName6
    ) 
    = (
    select
    
:new_value.TableIndex,:new_value.ColumnName4,:new_value.ColumnName5,:new_value.ColumnName6

    from dual
    )
    where t2.TableIndex = :new_value.TableIndex;
    
       UPDATE t3 SET (
    TableIndex,ColumnName7,ColumnName8
    ) 
    = (
    select
    :new_value.TableIndex,:new_value.ColumnName7,:new_value.ColumnName8
    from dual
    )
    where t3.TableIndex = :new_value.TableIndex;
END;
/

-- Check that the columns are updatable

select 
TABLE_NAME, INSERTABLE, UPDATABLE, DELETABLE 
from 
    dba_updatable_columns 
where 
    TABLE_NAME in ('T1', 'T2', 'T3', 'T_TOTAL');

-- Using the view a user can issue:

update t_total set(
ColumnName2,ColumnName5
)
= (
select 'Testdata a','Testdata b' from dual
) 
where
TableIndex = 815;

-- resulting in the change of 2 basic tables t1 and t2 or

update t_total set ColumnName7=    'Testdata c';

-- resulting in the change of the 3rd basic table.



The triggers are touching columns in the update statements
which are not changing, the last update only changes the 3rd basic
table t3 results in updates to t1 and t2 and in touching ColumnName8
of t3 although only ColumnName7 was changed.

My goal is that the update trigger for the last update results only
in:

update t3 set ColumnName7 = 'Testdata c';


Hope my example is not too big and that my question got clearer.

Many thanks

Stefan

and we said...

that would involve lots of dynamic sql and lots of code.

I don't like this "design" at all - it is not going to scale no matter what you do. You have access to all of the :old and :new values - a simple:

if ( :new.c <> :old.c or coalesce(:new.c,:old.c) is null )
then
   we are different
end if

would tell you if a given column has changed - but then you have to dynamically construct an update statement (and given you have hundreds of columns - you'll dynamically generate THOUSANDS or MILLIONS of different updates - flooding the shared pool - killing performance - definitely not scaling up at all). You'd have to use dbms_sql - since you don't know the number of bind variables at compile time. You would have to parse each and every statement each and every time (no caching of sql like static sql would do).

This would be a horrible idea. You will use static sql and pay that penalty (that of updating all columns) because the alternative - flooding the shared pool with thousands of unique updates that have to be parsed each and ever time) is infinitely worse.



statements like this:

The problem is that I can not change the application. So my view must be
</>
are never true. That is a choice you have made. It is a decision you have choosen.

I don't know how this could run out of rollback - if you are updating a row at a time (small transactions). If you are doing mass updates, then you have simply undersized your rollback space..


This'll be my epitaph ...

The problem is that I can not change the application.

Either that or

just bind, just join

one of the two will kill me some day....
Reviews    
2 stars   April 8, 2008 - 11am Central time zone
Reviewer: John Lavoie 
You should just use an updateable view and not bother with an instead-of trigger.


Followup   April 9, 2008 - 2pm Central time zone:

well, that would move the goal posts wouldn't it. one must presume the view they need is the view they need - and hence an updatable one is not feasible since that would not provide the data they need.

sort of like saying "querying dual is faster than querying a million row table so query dual instead"

it is true that querying dual would likely be faster, but not relevant since it doesn't provide the necessary data.



All information and materials provided here are provided "as-is"; Oracle disclaims all express and implied warranties, including, the implied warranties of merchantability or fitness for a particular use. Oracle shall not be liable for any damages, including, direct, indirect, incidental, special or consequential damages for loss of profits, revenue, data or data use, incurred by you or any third party in connection with the use of this information or these materials.

About Oracle | Legal Notices and Terms of Use | Privacy Statement