프로시저 -> 자주 사용되는 쿼리 저장 -> 유저가 필요하면 실행

트리거 -> 쿼리 저장 -> 자동으로 실행 (서버에 부하를 줄 수 있다.)

 

트리거 ? DML 이나 DDL 명령이 실행되면 자동으로 실행되도록 정의한 동작

트리거는 CREATE TRIGGER 권한이 있어야 만들 수 있다.

관리자(sys)에서 GRANT CREATE TRIGGER TO 계정; 을 통해 권한을 줄 수 있다.

 

문장 트리거 : DML 작업이 여러번 일어나도 트리거는 한번만 실행. 일반적으로 로그 등을 출력.

 

실습을 위한 테이블을 작성해봅시다.

CREATE TABLE insa (
    num VARCHAR2(30) PRIMARY KEY,
    name VARCHAR2(50) NOT NULL,
    dept VARCHAR2(50) NOT NULL,
    sal NUMBER NOT NULL
);

CREATE TABLE insa_time (
    memo VARCHAR2(1000),
    reg_date DATE DEFAULT SYSDATE
);

SELECT* FROM tab; -- 테이블 생성 확인

SELECT * FROM user_sys_privs; -- 권한 확인

 

-- 트리거에서 DML 문은 자동으로 COMMT/ROLLBACK되므로 COMMIT이나 ROLLBACK을 하지 않습니다.

CREATE OR REPLACE TRIGGER tri_insa
AFTER INSERT OR UPDATE OR DELETE ON insa
BEGIN
    IF INSERTING THEN
        INSERT INTO insa_time(memo) VALUES('추가 ...');
    ELSIF UPDATING THEN
        INSERT INTO insa_time(memo) VALUES('수정 ...');
    ELSIF DELETING THEN
        INSERT INTO insa_time(memo) VALUES('삭제 ...');
    END IF;
END;
/

 

-- 트리거 목록 확인

SELECT * FROM user_triggers;
SELECT * FROM user_source;

 

INSERT INTO insa VALUES ('11', '김자바', '개발부', 100);
INSERT INTO insa VALUES ('22', '너자바', '개발부', 100);
INSERT INTO insa VALUES ('33', '다자바', '개발부', 100);
COMMIT;

위 데이터를 추가하면 insa_time에 있는 memo컬럼에 데이터 추가의 시간과 추가... 라는 메모가 같이 적혀져있는 것을 확인할 수 있습니다.

SELECT memo, TO_CHAR(reg_date, 'YYYY-MM-DD HH24:MI:SS') reg_date FROM insa_time;

위 쿼리를 이용하면 insa_time에 작성된 memo의 날짜뿐만 아닌 시, 분, 초를 확인할 수 있습니다.

 

 

-- 지정된 시간에만 DML이 이루어 지도록 문장 트리거 만들기

CREATE OR REPLACE TRIGGER tri_insa
AFTER INSERT OR UPDATE OR DELETE ON insa
BEGIN
    IF TO_CHAR(SYSDATE, 'D') IN (1, 7) OR -- 일요일이거나 토요일이면
        ( TO_CHAR(SYSDATE, 'HH24') >= 20 AND TO_CHAR(SYSDATE, 'HH24') <= 8 ) THEN
            RAISE_APPLICATION_ERROR(-20001, '지금은 근무 시간이 아닙니다.');
    END IF;
END;
/

일요일이거나 토요일이면 예외처리를 통해 '지금은 근무 시간이 아닙니다.'를 출력하는 트리거입니다. 또 안에 적혀져 있는 시간을 통해 오후 8시 부터 오전 8시 까지를 예외처리를 했습니다.

 

-- 트리거 지우기

DROP TRIGGER tri_insa;

DROP TRIGGER 트리거명; 을 통해 삭제할 수 있습니다.

 

-- 행 트리거

DML 문장이 일어난 횟수만큼 트리거가 실행됩니다. 예를 들어 DELETE FROM insa;에서 30개 레코드가 삭제되면 트리거도 30번 실행됩니다.

 

새로운 실습을 위해 테이블을 생성합니다.

 	CREATE TABLE score1 (
          hak VARCHAR2(20) NOT NULL
          ,name VARCHAR2(30) NOT NULL
          ,kor NUMBER(3) NOT NULL
          ,eng NUMBER(3) NOT NULL
          ,mat NUMBER(3) NOT NULL
          ,CONSTRAINT pk_score1_hak PRIMARY KEY(hak)
      );

      CREATE TABLE score2 (
          hak VARCHAR2(20) NOT NULL
          ,kor NUMBER(2,1) NOT NULL
          ,eng NUMBER(2,1) NOT NULL
          ,mat NUMBER(2,1) NOT NULL
          ,CONSTRAINT pk_score2_id PRIMARY KEY(hak)
          ,CONSTRAINT fk_score2_id FOREIGN KEY(hak)
              REFERENCES score1(hak)
      );

      CREATE OR REPLACE FUNCTION fnGrade
      (
          pScore NUMBER
      )
      RETURN NUMBER
      IS
         n NUMBER(2,1);
      BEGIN
         IF pScore>=95 THEN n:=4.5;
         ELSIF pScore>=90 THEN n:=4.0;
         ELSIF pScore>=85 THEN n:=3.5;
         ELSIF pScore>=80 THEN n:=3.0;
         ELSIF pScore>=75 THEN n:=2.5;
         ELSIF pScore>=70 THEN n:=2.0;
         ELSIF pScore>=65 THEN n:=1.5;
         ELSIF pScore>=60 THEN n:=1.0;
         ELSE n:=0.0;
         END IF;
         RETURN n;
      END;
 	     /

 

-- 행트리거 : INSERT

CREATE OR REPLACE TRIGGER triScoreInsert
AFTER INSERT ON score1
FOR EACH ROW -- 행 트리거
DECLARE
-- 변수선언 영역
BEGIN
    -- :NEW -> score1에서 INSERT 한 행. 행트리거만 사용 가능
    INSERT INTO score2(hak, kor, eng, mat) VALUES (:NEW.hak,
        fnGrade(:NEW.kor), fnGrade(:NEW.eng), fnGrade(:NEW.mat) );
END;
/

score1 테이블에 hak, name, kor, eng, mat을 입력하면 자동으로 score2테이블 hak에는 입력받은 값을 입력해주고 kor, eng, mat에는 평점을 계산하여 넣어주는 트리거가 작성되었습니다.

INSERT INTO score1(hak, name, kor, eng, mat) VALUES ('1', 'a', 95, 100, 80);
INSERT INTO score1(hak, name, kor, eng, mat) VALUES ('2', 'b', 70, 90, 100);
COMMIT;

SELECT * FROM score1;
SELECT * FROM score2;

 

-- 행트리거 : UPDATE

CREATE OR REPLACE TRIGGER triScoreUpdate
AFTER UPDATE ON score1
FOR EACH ROW -- 행 트리거
DECLARE
-- 변수선언 영역
BEGIN
    -- :NEW -> score1에 새로 UPDATE할 레코드
    -- :OLD -> score1에서 UPDATE 하기 전 레코드
    UPDATE score2 SET kor = fnGrade(:NEW.kor), eng = fnGrade(:NEW.eng), mat = fnGrade(:NEW.mat) 
    WHERE hak = :OLD.hak ;
END;
/

score1이 UPDATE로 데이터가 바뀌면 score2에 score1에서 값을 연산해서 넣어줍니다. 

OLD.hak 은 바뀌기 전의 hak 을 말합니다. 

 

UPDATE score1 SET kor = 100, eng = 100, mat = 100 WHERE hak = '1';
COMMIT;
SELECT * FROM score1;
SELECT * FROM score2;

 

-- 행 트리거 : DELETE

CREATE OR REPLACE TRIGGER triScoreDELETE
BEFORE DELETE ON score1
FOR EACH ROW -- 행 트리거
DECLARE
-- 변수선언 영역
BEGIN
    -- :OLD -> score1에서 DELETE할 레코드
    DELETE FROM score2 WHERE hak = :OLD.hak ;
END;
/

여기서는 score1의 데이터가 삭제되기전에 score2에 입력한  hak에 해당되는 자료를 먼저 지워줍니다. score2 테이블에서 hak은 참조키 이므로, 부모테이블에서 해당 hak을 지우고 싶으면 자식을 먼저 지워야 되기때문에 자동으로 자식을 먼저 지워지게 한 것입니다.

DELETE FROM score1 WHERE hak = '1';
COMMIT;
SELECT * FROM score1;
SELECT * FROM score2;

-- 패키지 선언

CREATE OR REPLACE PACKAGE pEmp IS
    FUNCTION fnTax( p IN NUMBER ) RETURN NUMBER;
    PROCEDURE empList(pName VARCHAR2);
    PROCEDURE empList;   -- 중복 정의 (패키지 안에서는 가능)
END pEmp;
/

앞으로 만들 FUNCTION과 PROCEDURE을 선언할 수 있습니다.

 

-- 패키지 몸체 구현

CREATE OR REPLACE PACKAGE BODY pEmp IS
    -- 함수 구현
    FUNCTION fnTax( p IN NUMBER ) 
    RETURN NUMBER
    IS
        t NUMBER := 0;
    BEGIN
        IF p >= 3000000 THEN
            t := TRUNC(p * 0.03, -1);
        ELSIF p >= 2000000 THEN
            t := TRUNC(p * 0.02, -1);
        ELSE
            t := 0;
        END IF;
        
        RETURN t;
    END;
    
    PROCEDURE empList( pName VARCHAR2 )
    IS
        vName VARCHAR2(30);
        vSal NUMBER;
        CURSOR cur_emp IS
            SELECT name, sal FROM emp WHERE INSTR( name, pName) = 1;
        BEGIN
            OPEN cur_emp;
            LOOP
                FETCH cur_emp INTO vName, vSal;
                EXIT WHEN cur_emp%NOTFOUND;
                DBMS_OUTPUT.PUT_LINE(vName|| ' : ' || vSal);
            END LOOP;
            CLOSE cur_emp;
        END;
        
        PROCEDURE empList
        IS
        BEGIN
            FOR rec IN ( SELECT name, sal+bonus pay, fnTax(sal+bonus) tax FROM emp ) LOOP
                DBMS_OUTPUT.PUT_LINE( rec.name || ' : ' || rec.pay || ' : ' || rec.tax);
            END LOOP;
        END;
END pEmp;
/

 

-- 패키지 확인

SELECT * FROM user_objects;
SELECT * FROM user_objects WHERE object_type = 'PACKAGE';
SELECT * FROM user_procedures;

 

-- 실행

EXEC pEmp.empList;
EXEC pEmp.empList('김');

-- 패키지에 있는 함수를 SELECT 문에서도 사용할 수 있다.

SELECT name, sal, bonus, sal+bonus, pEmp.fnTax( sal+bonus) tax FROM emp;

 

-- 패키지 삭제

DROP PACKAGE 패키지명;
DROP PACKAGE BODY 패키지명;

선언부를 삭제하게 되면 몸체도 자동으로 삭제가 된다.

문제

-- score1 테이블 작성
hak     문자(20)  기본키
name   문자(30)  NOT  NULL
kor      숫자(3)     NOT  NULL
eng      숫자(3)    NOT  NULL
mat      숫자(3)    NOT  NULL

 

풀이 >>

더보기
CREATE TABLE score1 (
    hak VARCHAR2(30),
    name VARCHAR2(30) NOT NULL,
    kor NUMBER(3) NOT NULL,
    eng NUMBER(3) NOT NULL,
    mat NUMBER(3) NOT NULL,
    CONSTRAINT pk_score1_hak 
        PRIMARY KEY(hak)
);

-- score2 테이블 작성
hak     문자(20)  기본키, score1 테이블의 참조키
kor      숫자(2,1)     NOT  NULL
eng      숫자(2,1)    NOT  NULL
mat      숫자(2,1)    NOT  NULL
     

풀이 >>

더보기
CREATE TABLE score2 (
    hak VARCHAR2(20),
    kor NUMBER(2, 1) NOT NULL,
    eng NUMBER(2, 1) NOT NULL,
    mat NUMBER(2, 1) NOT NULL,
    CONSTRAINT pk_score2_hak PRIMARY KEY (hak),
    CONSTRAINT fk_score2_hak FOREIGN KEY (hak) 
        REFERENCES score1 (hak)
);


-- 평점을 구하는 함수 작성
-- 함수명 : fnGrade(s)
95~100:4.5    90~94:4.0
85~89:3.5     80~84:3.0
75~79:2.5     70~74:2.0
65~69:1.5     60~64:1.0
60미만 0
       

풀이 >> 

더보기
CREATE OR REPLACE FUNCTION fnGrade
(
	n NUMBER
)
RETURN NUMBER
IS
	s NUMBER(2, 1) := 0;
BEGIN
	IF n>= 95 THEN s := 4.5;
	ELSIF n>=90 THEN s := 4.0;
	ELSIF n>=85 THEN s := 3.5;
	ELSIF n>=80 THEN s := 3.0;
	ELSIF n>=75 THEN s := 2.5;
	ELSIF n>=70 THEN s := 2.0;
	ELSIF n>=65 THEN s := 1.5;
	ELSIF n>=60 THEN s := 1.0;
	ELSE s := 0;
    END IF;
	
	RETURN s;
END;
/


-- score1 테이블과 score2 테이블에 데이터를 추가하는 프로시저 만들기
프로시저명 : pScoreInsert
실행예 : EXEC pScoreInsert('1111', '가가가', 80, 60, 75);
   
score1 테이블 => '1111', '가가가', 80, 60, 75  정보 추가
score2 테이블 => '1111',            3.0, 1.0, 2.5 정보 추가(국, 영, 수 점수가 평점으로 계산되어 추가)
   
단, 국어, 영어, 수학 점수는 0~100 사이가 아니면 예외 발생하고 종료
 

풀이 >>

더보기
CREATE OR REPLACE PROCEDURE pScoreInsert
(
	pHak VARCHAR2,
	pName VARCHAR2,
	pKor NUMBER,
	pEng NUMBER,
	pMat NUMBER
)
IS
BEGIN
	IF pKor < 0 OR pKor > 100 THEN 
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	ELSIF pEng < 0 OR pEng > 100 THEN
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	ELSIF pMat <0 OR pMat > 100 THEN
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	END IF;
	
	INSERT INTO score1(hak, name, kor, eng, mat) 
		VALUES(pHak, pName, pKor, pEng, pMat);
	INSERT INTO score2(hak, kor, eng, mat)
		VALUES(pHak, fnGrade(pKor), fnGrade(pEng), fnGrade(pMat) );
	COMMIT;
END;
/


-- score1 테이블과 score2 테이블에 데이터를 수정하는 프로시저 만들기
프로시저명 : pScoreUpdate
실행예 : EXEC pScoreUpdate('1111', '가가가', 90, 60, 75);
   
score1 테이블 => 학번이 '1111' 인 자료를  '가가가', 90, 60, 75  으로 정보 수정
score2 테이블 => 학번이 '1111' 인 자료를           4.0, 1.0, 2.5 으로 정보 수정(국, 영, 수 점수가 평점으로 계산되어 수정)
   
단, 국어, 영어, 수학 점수는 0~100 사이가 아니면 예외 발생하고 종료
   

풀이 >>

CREATE OR REPLACE PROCEDURE pScoreUpdate(
	pHak score1.hak%TYPE,
	pName score1.name%TYPE,
	pKor score1.kor%TYPE,
	pEng score1.eng%TYPE,
	pMat score1.mat%TYPE
)
IS
BEGIN
	IF pKor < 0 OR pKor > 100 THEN 
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	ELSIF pEng < 0 OR pEng > 100 THEN
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	ELSIF pMat <0 OR pMat > 100 THEN
		RAISE_APPLICATION_ERROR(-20001, '점수는 0~100 사이 값만 입력 가능합니다.');
	END IF;
	
	UPDATE score1 SET name = pName, kor = pKor, eng = pEng, mat = pMat 
		WHERE hak = pHak;
	UPDATE score2 SET kor = fnGrade(pKor), eng = fnGrade(pEng), mat = fnGrade(pMat)
		WHERE hak = pHak;
	COMMIT;
END;
/

 

-- score1 테이블과 score2 테이블에 데이터를 삭제하는 프로시저 만들기
프로시저명 : pScoreDelete
실행예 : EXEC pScoreDelete('1111');
score1 과 score2 테이블 정보 삭제

 

풀이 >>

CREATE PROCEDURE pScoreDelete(
	pHak score1.hak%TYPE
)
IS
BEGIN
	DELETE FROM score2 WHERE hak = pHak;
	DELETE FROM score1 WHERE hak = pHak;
	COMMIT;
END;
/

 

※ 프로시저 실행시에는 앞에 꼭 EXEC 를 붙여야 한다. 

EXEC pScoreInsert('1111', '홍길동', 80, 90, 90);
EXEC pScoreUpdate('1111', '홍길동', 100, 90, 90);
EXEC pScoreDelete('1111');

 

+ Recent posts