CREATE OR REPLACE TRIGGER EMPCNT
AFTER DELETE OR INSERT OR UPDATE
ON SCOTT.EMP
/*
아래 코드가 있을 경우 다음과 같은 에러가 발생한다.
ORA-04091: 테이블 SCOTT.EMP가 변화하고 있어서 트리거/함수가 이를 볼 수 없습니다.
따라서 자신의 테이블에 트리거를 생성하는 경우 개별 로우가 영향받을 때마다 실행하라는 [FOR EACH ROW] 구문은 사용할 수 없다???
*/
--FOR EACH ROW
DECLARE
BEGIN
FOR IREC IN(
SELECT DEPTNO,COUNT(*) EMPS
FROM EMP
GROUP BY DEPTNO
HAVING COUNT(*)<3 OR COUNT(*)>8
)
LOOP
RAISE_APPLICATION_ERROR(-20000,'DEPARTMENT '||IREC.DEPTNO||' HAS '||IREC.EMPS||' EMPLOYEES');
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END EMPCNT;
/
SELECT
DEPTNO
,COUNT(*)
,CASE
WHEN COUNT(*) NOT BETWEEN 3 AND 8 THEN '<<<'
ELSE NULL
END FLAG
FROM EMP
GROUP BY DEPTNO
---------------------
DEPTNO CNT FLAG
---------------------
10 3
20 5
30 6
(세션1)
DELETE FROM EMP WHERE EMPNO IN(7369,7566) --2명삭제
(세선2)
DELETE FROM EMP WHERE EMPNO IN(7788); --1명삭제
COMMIT; --세션1에서 두명을 삭제했으므로 더 이상 삭제가 되지 않아야 하나 삭제가 됨
(세션1)
COMMIT; --세션2에서 1명을 삭제했으므로 2명 삭제는 안되어야 하나 삭제가 됨
1 DEPT 테이블에 EMP_COUNT 칼럼 추가
ALTER TABLE DEPT ADD EMP_COUNT NUMBER CONSTRAINT MUSTBE_BETWEEN_38
CHECK (EMP_COUNT BETWEEN 3 AND 8 OR EMP_COUNT=0)
DEFERRABLE INITIALLY DEFERRED --commit 단위로 제약조건을 보류한다
2 EMP_COUNT 칼럼에 해당 부서의 인원수 업데이트
UPDATE DEPT SET EMP_COUNT=(
SELECT COUNT(*)
FROM EMP
WHERE EMP.DEPTNO=DEPT.DEPTNO)
3 EMP_COUNT 칼럼의 NOT NULL로 변경
ALTER TABLE DEPT MODIFY EMP_COUNT NOT NULL
4 트리거 생성
CREATE OR REPLACE TRIGGER DEPTCNT
AFTER DELETE OR INSERT OR UPDATE
ON SCOTT.EMP
/*
아래 코드가 없을 경우 에러
ORA-04082: 테이블 레벨 트리거에서 NEW 혹은 OLD 참조는 허용되지 않습니다
*/
FOR EACH ROW
DECLARE
BEGIN
/*EMP테이블에서 DEPTNO 이외의 칼럼이 변경된 경우 리턴*/
IF (UPDATING AND :OLD.DEPTNO = :NEW.DEPTNO) THEN
RETURN;
END IF;
/*입력과 부서코드 수정이라면 카운트 증가*/
IF (INSERTING OR UPDATING) THEN
UPDATE DEPT SET EMP_COUNT=EMP_COUNT+1
WHERE DEPTNO = :NEW.DEPTNO;
END IF;
/*삭제와 부서코드 수정이라면 카운트 감소*/
IF (UPDATING OR DELETING) THEN
UPDATE DEPT SET EMP_COUNT=EMP_COUNT-1
WHERE DEPTNO = :OLD.DEPTNO;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END DEPTCNT;
/
5 실행
(세션1)
DELETE FROM EMP WHERE EMPNO IN(7369,7566)
(세션2)
DELETE FROM EMP WHERE EMPNO IN(7788)
세션1에서 COMMIT을 하지 않으면 세션2는 대기상태가 된다.
세션1에서 COMMIT을 완료하면 세션2에서 곧바로 에러가 발생한다.
따라서 원하는 결과를 얻을 수 있지만 로우 하나 하나 변경시마다 빈번한 직렬화로 인해 DB의 동시성과 확정성이 떨어진다
1. 테이블 생성
CREATE TABLE TEST AS
SELECT
TO_CHAR(TO_DATE('19950101','YYYYMMDD')+ROWNUM,'YYYYMMDD') STR_DATE
,TO_DATE('19950101','YYYYMMDD')+ROWNUM DATE_DATE
FROM ALL_OBJECTS
2. 인덱스 생성
CREATE INDEX T_STRDATE_IDX ON TEST(STR_DATE)
CREATE INDEX T_DATEDATE_IDX ON TEST(DATE_DATE)
3. 통계정보 생성
ANALYZE TABLE TEST COMPUTE STATISTICS
FOR TABLE
FOR ALL INDEXES
FOR ALL INDEXED COLUMNS
4. STR_DATE 인덱스 사용
SQL> SET AUTOTRACE ON EXPLAIN
SQL> SELECT * FROM TEST
2 WHERE STR_DATE BETWEEN '20001231' AND '20010101';
STR_DATE DATE_DAT
-------- --------
20001231 00/12/31
20010101 01/01/01
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=313 Bytes=4695)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (Cost=3 Card=313 Bytes=4695)
2 1 INDEX (RANGE SCAN) OF 'T_STRDATE_IDX' (NON-UNIQUE) (Cost=2 Card=313)
5. DATE_DATE 인덱스 사용
SQL> SELECT * FROM TEST
2 WHERE DATE_DATE BETWEEN TO_DATE('20001231','YYYYMMDD')
3 AND TO_DATE('20010101','YYYYMMDD');
STR_DATE DATE_DAT
-------- --------
20001231 00/12/31
20010101 01/01/01
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=15)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (Cost=3 Card=1 Bytes=15)
2 1 INDEX (RANGE SCAN) OF 'T_DATEDATE_IDX' (NON-UNIQUE) (Cost=2 Card=1)
6. 해설
1) DATE_DATE 인덱스를 사용하는 경우 이 칼럼이 날짜형이므로 20001231과 20010101 사이에는 오직 하루만 존재한다는 걸
옵티마이져가 알고 있으므로 딱 두건의 인덱스만 찾는다.
2) 그러나 STR_DATE의 경우 문자형이므로 20001231과 20010101 사이에는 20001232~20010100 라는 상당히 많은 값이
존재할 수 있으므로 이 사이값을 찾아 인덱스를 뒤져야 한다. 경우에 따라서는 인덱스 스캔이 아닌 테이블
풀 스캔이 일어나기도 한다.
3) 따라서 이로인한 성능차이가 발생하게 되며 그 차이는 검색조건의 범위가 넓으면 넓을 수록 더욱 격차가 벌어지게 된다.
- 강좌 URL : http://www.gurubee.net/lecture/3556
- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.