여러 개의 Table이 Join된 View를 Update할 수 있다는 것을 잘 알고 있을 것이다. Oracle은 이것을 Updatable Join View라고 부른다.
View(단, Subquery까지 포함)를 Update할 수 있는 방법은 내가 알기로는 세 가지 정도가 있다.
- Updatable Join View
- Merge Statement(정확하게 말하면 View는 아니지만 그런 효과가 있다는 의미)
- Instead of Trigger
역시 하나의 문제에 대해 여러 가지 솔루션이 있을 수 있다는 것을 아는 것이 중요하다는 것의 예가 되겠다.
간단한 예를 통해 각 방법들을 살펴 보면...
우선 다음과 같이 Parent/Child 관계를 가지는 Table을 만든다.
drop table t1 purge;
drop table t2 purge;
create table t1(c1 int, c2 int);
create table t2(c1 int, c2 int);
insert into t1
select level, level
from dual
connect by level <= 10
;
insert into t2
select mod(level, 10)+1, level
from dual
connect by level <= 100
;
commit;
Data만으로 보면 Parent(T1)/Child(T2) 관계이지만 Oracle이 이 관계를 파악할 수 있는 방법은 없다.
다음과 같이 두 개의 View를 만든다. View의 "Column"에 유의하자.
create or replace view v1
as
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
;
이 View는 Updatable한가? user_updatable_columns 뷰를 보면 알 수 있다.
select
column_name, updatable, insertable, deletable
from
user_updatable_columns
where
table_name = 'V1'
;
COLUMN_NAME UPDATABLE INSERTABL DELETABLE
-------------------- --------- --------- ---------
C1 NO NO NO
C2 NO NO NO
전혀 Updatable하지 않다. Oracle은 Parent/Child 관계를 알 수 없기 때문이다.
View의 특정 Column이 Updatable하려면 Key-Preserved해야 하는데(Oracle의 용어이다), Key-Preserved한지를 Oracle이 판단할 수 없기 때문이다.
같은 이유로 다음과 같은 View도 전혀 Updatable하지 않다.
create or replace view v2
as
select
t2.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
;
select
column_name, updatable, insertable, deletable
from
user_updatable_columns
where
table_name = 'V2'
;
COLUMN_NAME UPDATABLE INSERTABL DELETABLE
-------------------- --------- --------- ---------
C1 NO NO NO
C2 NO NO NO
Updatable하지 않기 때문에 다음과 같이 Subquery(View)에 대한 Update는 실패한다.
update (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
ERROR at line 10:
ORA-01779: cannot modify a column which maps to a non key-preserved table
update (
select
t2.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
ERROR at line 10:
ORA-01779: cannot modify a column which maps to a non key-preserved table
이 문제를 해결하는 한가지 안좋은 방법은 /*+ BYPASS_UJVC */ Hint를 사용하는 것이다. 말 그대로 Updatable Join View Constraint(제약 조건)을 Bypass한다는 것이다.
update /*+ bypass_ujvc */ (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
20 rows updated.
궁극적인 해결책은 Oracle에게 Parent/Child 관계를 알려 주는 것이다.
더 정확하게 이야기하면 t1:t2 = 1:N이며 t1.c1이 Unique하기 때문에 t2와 관련된 Column들은 Updatable하다는 것을 알려 주어야 한다.
다음과 같이 t1.c1에 primary key를 추가함으로써 이를 달성할 수 있다.
alter table t1 add constraint t1_pk primary key (c1);
다음과 같이 일부 혹은 전체 Column들이 Updateable하다는 것을 확인할 수 있다.
alter view v1 compile;
alter view v2 compile;
select
column_name, updatable, insertable, deletable
from
user_updatable_columns
where
table_name = 'V1'
;
COLUMN_NAME UPDATABLE INSERTABL DELETABLE
-------------------- --------- --------- ---------
C1 NO NO NO
C2 YES YES YES
select
column_name, updatable, insertable, deletable
from
user_updatable_columns
where
table_name = 'V2'
;
COLUMN_NAME UPDATABLE INSERTABL DELETABLE
-------------------- --------- --------- ---------
C1 YES YES YES
C2 YES YES YES
따라서 다음과 같이 Join View를 Update할 수 있다.
더 정확하게 말하면 user_updatable_columns 뷰에서 'YES'로 나온 Column에 대해서는 update/insert/delete가 가능하다.
update (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
20 rows updated.
다음과 같은 Insert 구문은 어떨까? 성공할까?
insert into (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
)
values(1, 11)
;
Column t1.c1은 Updatable하지 않기 때문에 다음과 같이 에러가 난다.
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
반면 다음과 같은 Insert는 성공한다.
Key-Preserved인 Table에 대해서만 Insert가 이루어지기 때문이다.
insert into (
select
t2.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
)
values(1, 11)
;
1 row created.
다음과 같이 Delete도 가능하다.
delete from (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
;
20 rows deleted.
View에 대한 Update는 어떤 실행 계획을 보일까? 아래에 그 결과가 있다.
explain plan for
update (
select
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
매우 논리적인 실행 계획을 보인다.
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 4 | 156 | 4 (0)| 00:00:01 |
| 1 | UPDATE | T2 | | | | |
| 2 | NESTED LOOPS | | 4 | 156 | 4 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| T2 | 20 | 520 | 4 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN| T1_PK | 1 | 13 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("T2"."C1">=1 AND "T2"."C1"<=2)
4 - access("T1"."C1"="T2"."C1")
filter("T1"."C1">=1 AND "T1"."C1"<=2)
논의를 더 확장해보자.
이런 Query에서 Parallel Execution은 가능한가?
안될 이유가 없다.
explain plan for
update (
select /*+ parallel(t2) parallel(t1) no_index(t2) no_index(t1) */
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
실행 계획은 다음과 같다.
-------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |
-------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 4 | 156 |
| 1 | UPDATE | T2 | | |
| 2 | PX COORDINATOR | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10001 | 4 | 156 |
|* 4 | HASH JOIN | | 4 | 156 |
| 5 | PX RECEIVE | | 2 | 26 |
| 6 | PX SEND BROADCAST | :TQ10000 | 2 | 26 |
| 7 | PX BLOCK ITERATOR | | 2 | 26 |
|* 8 | TABLE ACCESS FULL| T1 | 2 | 26 |
| 9 | PX BLOCK ITERATOR | | 20 | 520 |
|* 10 | TABLE ACCESS FULL | T2 | 20 | 520 |
-------------------------------------------------------------
View 부분(Select ...)은 명백하게 병렬 실행이 된다.
하지만 Update 부분은 병렬 실행이 되지 않는 것을 알 수 있다.
Parallel DML은 다음과 같이 명시적으로 활성화를 해야만 실행이 된다.
alter session enable parallel dml;
explain plan for
update (
select /*+ parallel(t2) parallel(t1) no_index(t2) no_index(t1) */
t1.c1, t2.c2
from
t1, t2
where
t1.c1 = t2.c1
and t1.c1 between 1 and 2
) x
set x.c2 = x.c2 + 1000
;
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |
---------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 4 | 156 |
| 1 | PX COORDINATOR | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 4 | 156 |
| 3 | UPDATE | T2 | | |
| 4 | PX RECEIVE | | 4 | 156 |
| 5 | PX SEND HASH (BLOCK ADDRESS)| :TQ10001 | 4 | 156 |
|* 6 | HASH JOIN | | 4 | 156 |
| 7 | PX RECEIVE | | 2 | 26 |
| 8 | PX SEND BROADCAST | :TQ10000 | 2 | 26 |
| 9 | PX BLOCK ITERATOR | | 2 | 26 |
|* 10 | TABLE ACCESS FULL | T1 | 2 | 26 |
| 11 | PX BLOCK ITERATOR | | 20 | 520 |
|* 12 | TABLE ACCESS FULL | T2 | 20 | 520 |
---------------------------------------------------------------------
Merge 구문이 등장함으로써 Updatable Join View의 기능을 어느 정도 대신할 수 있게 되었다.
아래와 같이 Insert가 없는 Merge 구문을 사용하면 똑같은 효과를 얻을 수 있다.
(더 직관적이기도 하다)
merge into t2
using (select c1, c2 from t1
where c1 between 1 and 2) x
on (x.c1 = t2.c1)
when matched
then update set t2.c2 = t2.c2 + 1000
;
20 rows merged.
Updatable Join View나 Merge를 이용해서 하나의 Query로 View에 대해 DML이 불가능하다면?
개별 Table에 대한 여러 번의 DML로 나누어서 수행할 수 밖에 없는가?
만일 권한 관리 때문에 View에 대한 권한밖에 줄 수 없다면?
이런 상황을 해결할 수 있는 마지막 수단이 Instead of Trigger이다.
가령 다음과 같은 View v1에 대한 Insert 문은 (당연하게도) 모두 실패한다.
(Column t1.c1은 Updatable하지 않기 때문이다)
insert into v1
values(1, 10)
;
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
insert into v1
select values(100, 100);
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
/*+ bypass_ujvc */ Hint를 사용하면 해결될까?
insert /*+ bypass_ujvc */ into v1 values(1, 10)
*
ERROR at line 1:
ORA-01776: cannot modify more than one base table through a join view
View v1은 Table t1, t2의 Column을 모두 참조하고 있기 때문에 두 개의 Table에 대해 Insert하는 것이 불가능하다.
이 문제를 해결할 수 있는 한가지 방법은 View에 대한 Trigger(Instead of Trigger)를 만드는 것이다.
create or replace trigger trg1
instead of insert on v1
for each row
declare
begin
insert into t1 values(:new.c1, 100);
insert into t2 values(:new.c1, 100);
end;
/
다음과 같이 Insert가 모두 성공한다.
insert into v1
values(1, 10)
;
ERROR at line 1:
ORA-00001: unique constraint (UKJA.T1_PK) violated
ORA-06512: at "UKJA.TRG1", line 4
ORA-04088: error during execution of trigger 'UKJA.TRG1'
insert into v1
values(100, 100);
1 row created.
역시 가장 중요한 것은 하나의 문제에 대해 여러 가지 솔루션이 있을 수 있다는 가정을 잊지 않는 것이다.
출처 : http://ukja.tistory.com/187