항목별 서브쿼리 사용 비효율 |
---|
{code:sql} UPDATE 고객 c SET 최종거래일시 = (SELECT MAX(거래일시) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)) , 최근거래횟수 = (SELECT COUNT(*) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)) , 최근거래금액 = (SELECT SUM(거래금액) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)) WHERE EXISTS (SELECT 1 FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)) ; {code} |
여러 항목을 한번의 서브쿼리로 개선 => set 절과 Where 절 2번의 서브쿼리 사용 비효율 |
---|
{code:sql} UPDATE 고객 c SET (최종거래일시, 최근거래횟수, 최근거래금액) = (SELECT MAX(거래일시) , COUNT(*) , SUM(거래금액) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1) ) WHERE EXISTS (SELECT 1 FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)) ; {code} |
해시 새미 조인 방식으로 유도, Unnest hash_sj => 여전히 거래 2번 읽음 |
---|
{code:sql} UPDATE 고객 c SET (최종거래일시, 최근거래횟수, 최근거래금액) = (SELECT MAX(거래일시) , COUNT(*) , SUM(거래금액) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1) ) WHERE EXISTS (SELECT /*+ UNNEST HASH_SJ */ 1 FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1) ) ; {code} |
Where 절을 생략하여 거래 한번만 읽기 => 전체 고객이 업데이트 되는 비효율 |
---|
{code:sql} UPDATE 고객 c SET (최종거래일시, 최근거래횟수, 최근거래금액) = (SELECT NVL(MAX(거래일시), c.최종거래일시) , DECODE(COUNT(), 0, c.최근거래횟수, COUNT()) , NVL(SUM(거래금액), c.최근거래금액) FROM 거래 WHERE 고객번호 = c.고객번호 AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1) ) ; {code} |
조인 뷰 업데이트 => 거래 두번 읽는 비효율 없음 |
---|
{code:sql} |
UPDATE /*+ BYPASS_UJVC */
(
SELECT c.최종거래일시, c.최근거래횟수, c.최근거래금액
, t.거래일시, t.거래횟수, t.거래금액
FROM (SELECT 고객번호
, MAX(거래일시) 거래일시
, COUNT(*) 거래횟수
, SUM(거래금액) 거래금액
FROM 거래
GROUP BY 고객번호
AND 거래일시 >= ADD_MONTHS(TRUNC(sysdate), -1)
) t
, 고객 c
WHERE t.고객번호 = c.고객번호
)
SET 최종거래일시 = 거래일시
, 최종거래횟수 = 거래횟수
, 최종거래금액 = 거래금액
;
|
|| 조인 뷰 수정 테스트 ||
|{code:sql}
SQL> CREATE TABLE emp1 AS SELECT * FROM emp;
테이블이 생성되었습니다.
SQL> CREATE TABLE dept1 AS SELECT * FROM dept;
테이블이 생성되었습니다.
SQL> CREATE OR REPLACE VIEW view_emp_dept
2 AS
3 SELECT e.ROWID emp_rid
4 , e.*
5 , d.ROWID dept_rid
6 , d.dname, d.loc
7 FROM emp1 e
8 , dept1 d
9 WHERE e.deptno = d.deptno
10 ;
뷰가 생성되었습니다.
SQL> -- 1 쪽 집합(dept) Update --
SQL> UPDATE view_emp_dept SET loc = 'SEOUL' WHERE job = 'CLOCK';
UPDATE view_emp_dept SET loc = 'SEOUL' WHERE job = 'CLOCK'
*
1행에 오류:
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
SQL> -- M 쪽 집합(emp) Update --
SQL> UPDATE view_emp_dept SET sal = sal * 1.1 WHERE deptno = 30;
UPDATE view_emp_dept SET sal = sal * 1.1 WHERE deptno = 30
*
1행에 오류:
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
SQL> CREATE UNIQUE INDEX pk_dept1 ON dept1(deptno);
인덱스가 생성되었습니다.
SQL> -- M 쪽 집합(emp) Update --
SQL> UPDATE view_emp_dept SET sal = sal * 1.1 WHERE deptno = 30;
6 행이 갱신되었습니다.
SQL> CREATE UNIQUE INDEX pk_emp1 ON emp1(empno);
인덱스가 생성되었습니다.
SQL> -- 1 쪽 집합(dept) Update --
SQL> UPDATE view_emp_dept SET loc = 'SEOUL' WHERE job = 'CLOCK';
UPDATE view_emp_dept SET loc = 'SEOUL' WHERE job = 'CLOCK'
*
1행에 오류:
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
SQL>
|
ROWID 확인 : 조인뷰의 ROWID 와 M쪽 테이블(EMP)의 ROWID 가 같음 |
---|
{code:sql} SQL> SELECT ROWID rid 2 , emp_rid 3 , dept_rid 4 , deptno 5 , empno 6 FROM view_emp_dept 7 ; |
RID EMP_RID DEPT_RID DEPTNO EMPNO
14 개의 행이 선택되었습니다.
SQL>
|
|| 키보존 안 된 뷰의 ROWID 조회 ||
|{code:sql}
SQL> DROP INDEX pk_dept1;
인덱스가 삭제되었습니다.
SQL> SELECT ROWID rid
2 , emp_rid
3 , dept_rid
4 , deptno
5 , empno
6 FROM view_emp_dept
7 ;
FROM view_emp_dept
*
6행에 오류:
ORA-01445: 키 보존 테이블이 없는 조인 뷰에서 ROWID를 선택할 수 없음
SQL> ALTER TABLE dept1 ADD CONSTRAINT pk_dept1 PRIMARY KEY(deptno);
테이블이 변경되었습니다.
SQL>
|
(1:M) 조인 뷰에서 M 쪽 테이블의 컬럼만 수정 가능 |
---|
{code:sql} SQL> INSERT INTO view_emp_dept(empno, ename, deptno, dname) 2 VALUES (9999, 'MANON94', 40, 'GURUBEE') 3 ; INSERT INTO view_emp_dept(empno, ename, deptno, dname) * 1행에 오류: ORA-01776: 조인 뷰에 의하여 하나 이상의 기본 테이블을 수정할 수 없습니다. |
SQL> SELECT *
2 FROM user_updatable_columns
3 WHERE table_name = 'VIEW_EMP_DEPT'
4 ;
OWNER TABLE_NAME COLUMN_NAME UPD INS DEL
12 개의 행이 선택되었습니다.
SQL> INSERT INTO view_emp_dept(empno, ename, deptno)
2 VALUES (9999, 'MANON94', 40)
3 ;
1 개의 행이 만들어졌습니다.
SQL>
|
h4. 수정가능 조인 뷰 제약 회피
|| Group By 결과가 Unique 함에도 불구하고 에러 발생 ||
|{code:sql}
SQL> ALTER TABLE dept1 ADD avg_sal NUMBER(7,2);
테이블이 변경되었습니다.
SQL> UPDATE
2 (
3 SELECT d.avg_sal, e.sal
4 FROM dept1 d
5 , (SELECT deptno, AVG(sal) sal FROM emp GROUP BY deptno) e
6 WHERE d.deptno = e.deptno
7 )
8 SET avg_sal = sal
9 ;
SET avg_sal = sal
*
8행에 오류:
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
SQL>
|
BYPASS_UJVC 힌트 사용 => 11G 부터는 해당 힌트 사용 못함 |
---|
{code:sql} SQL> UPDATE /*+ BYPASS_UJVC */ 2 ( 3 SELECT d.avg_sal, e.sal 4 FROM dept1 d 5 , (SELECT deptno, AVG(sal) sal FROM emp GROUP BY deptno) e 6 WHERE d.deptno = e.deptno 7 ) 8 SET avg_sal = sal 9 ; SET avg_sal = sal * 8행에 오류: ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다 |
SQL>
|
h2. (2) Merge 문 활용
|| Merge ||
|{code:sql}
SQL> MERGE INTO dept1 d
2 USING (SELECT deptno, AVG(sal) sal FROM emp GROUP BY deptno) e
3 ON (d.deptno = e.deptno)
4 WHEN MATCHED THEN
5 UPDATE SET d.avg_sal = e.sal
6 ;
3 행이 병합되었습니다.
SQL>
|
Merge 활용 1 : 있으면 Update / 없으면 Insert |
---|
{code:sql} DECLARE v_deptno NUMBER := 50; v_dname VARCHAR2(10) := 'GURUBEE'; v_cnt NUMBER; BEGIN SELECT COUNT(*) INTO v_cnt FROM dept1 WHERE deptno = v_deptno; IF v_cnt = 0 THEN INSERT INTO dept1(deptno, dname) VALUES(v_deptno, v_dname); ELSE UPDATE dept1 SET dname = v_dname WHERE deptno = v_deptno; END IF; END; / |
MERGE INTO dept1
USING dual
ON (deptno = 50)
WHEN MATCHED THEN
UPDATE SET dname = 'GURUBEE'
WHEN NOT MATCHED THEN
INSERT (deptno, dname) VALUES(50, 'GURUBEE')
;
|
|| Merge 활용 2 : 일련번호 Update 개선 ||
|{code:sql}
SQL> UPDATE emp1 e
2 SET comm = (SELECT rn
3 FROM (SELECT empno
4 , ROW_NUMBER() OVER(ORDER BY ename) rn
5 FROM emp
6 )
7 WHERE empno = e.empno
8 )
9 ;
15 행이 갱신되었습니다.
Execution Plan
----------------------------------------------------------
Plan hash value: 3002760866
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 15 | 390 | 79 (38)| 00:00:01 |
| 1 | UPDATE | EMP1 | | | | |
| 2 | TABLE ACCESS FULL | EMP1 | 15 | 390 | 4 (0)| 00:00:01 |
|* 3 | VIEW | | 14 | 364 | 4 (25)| 00:00:01 |
| 4 | WINDOW SORT | | 14 | 140 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| EMP | 14 | 140 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("EMPNO"=:B1)
Note
-----
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
4 recursive calls
30 db block gets
120 consistent gets
0 physical reads
6692 redo size
689 bytes sent via SQL*Net to client
827 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
16 sorts (memory)
0 sorts (disk)
15 rows processed
SQL> MERGE INTO emp1 a
2 USING (SELECT empno
3 , ROW_NUMBER() OVER(ORDER BY ename) rn
4 FROM emp
5 ) b
6 ON (a.empno = b.empno)
7 WHEN MATCHED THEN
8 UPDATE SET a.comm = b.rn
9 ;
14 행이 병합되었습니다.
Execution Plan
----------------------------------------------------------
Plan hash value: 2424884718
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 14 | 364 | 7 (29)| 00:00:01 |
| 1 | MERGE | EMP1 | | | | |
| 2 | VIEW | | | | | |
| 3 | MERGE JOIN | | 14 | 1750 | 7 (29)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMP1 | 15 | 1485 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | PK_EMP1 | 15 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 14 | 364 | 5 (40)| 00:00:01 |
| 7 | VIEW | | 14 | 364 | 4 (25)| 00:00:01 |
| 8 | WINDOW SORT | | 14 | 140 | 4 (25)| 00:00:01 |
| 9 | TABLE ACCESS FULL | EMP | 14 | 140 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("A"."EMPNO"="B"."EMPNO")
filter("A"."EMPNO"="B"."EMPNO")
Note
-----
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
7 recursive calls
14 db block gets
27 consistent gets
0 physical reads
3404 redo size
690 bytes sent via SQL*Net to client
752 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
14 rows processed
SQL>
|
Merge 구문 사용법 : http://docs.oracle.com/database/121/SQLRF/statements_9016.htm#SQLRF01606
Insert First |
---|
{code:sql} – 1. 부서별로 여러번 나누어 처리 -- INSERT INTO dept_10 SELECT * FROM dept WHERE deptno = 10; INSERT INTO dept_20 SELECT * FROM dept WHERE deptno = 20; INSERT INTO dept_30 SELECT * FROM dept WHERE deptno = 30; – 2. 한방에 처리 -- INSERT FIRST WHEN deptno = 10 THEN INTO dept_10 VALUES(deptno, dname, loc) WHEN deptno = 20 THEN INTO dept_20 VALUES(deptno, dname, loc) WHEN deptno = 30 THEN INTO dept_30 VALUES(deptno, dname, loc) SELECT * FROM dept ; {code} |