package metaEx;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;

import db_util.DBConn;

public class MySqlplus {
	public static void main(String[] args) {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String sql, s;
		int n;

		gogo: 
		while (true) {
			try {
				System.out.print("SQL> ");
				sql = "";
				n = 1;
				do {
					s = br.readLine();
					if (s == null || s.equalsIgnoreCase("exit")) {
						DBConn.close();
						System.exit(0);
					}

					s = s.trim();
					sql += s + " ";

					if (sql.trim().length() == 0) {
						continue gogo;
					}
					if (s.lastIndexOf(";") == -1) {
						System.out.print((++n) + " ");
					}
				} while (s.lastIndexOf(";") == -1);

				sql = sql.trim();
				sql = sql.substring(0, sql.lastIndexOf(";"));
				if (sql.length() == 0) {
					continue;
				}
				execute(sql);

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public static void execute(String sql) {
		Connection conn = DBConn.getConnection();
		Statement stmt = null;
		ResultSet rs = null;
		ResultSetMetaData rsmd = null;

		try {
			stmt = conn.createStatement();
			if (sql.toUpperCase().startsWith("SELECT")) {
				rs = stmt.executeQuery(sql);
				rsmd = rs.getMetaData();

				int cols = rsmd.getColumnCount();
				for (int i = 1; i <= cols; i++) {
					System.out.print(rsmd.getColumnName(i) + "\t");
				}
				System.out.println();

				for (int i = 1; i <= cols; i++) {
					System.out.print("----------");
				}
				System.out.println();

				while (rs.next()) {
					for (int i = 1; i <= cols; i++) {
						System.out.print(rs.getString(i) + "\t");
					}
					System.out.println();
				}
			} else {
				// INSERT, UPDATE, DELETE, CREATE, ALTER, DROP 등
				int result = stmt.executeUpdate(sql);
				if (sql.toUpperCase().startsWith("INSERT")) {
					System.out.println(result + " 행이 추가 되었습니다.");
				} else if (sql.toUpperCase().startsWith("UPDATE")) {
					System.out.println(result + " 행이 수정 되었습니다.");
				} else if (sql.toUpperCase().startsWith("DELETE")) {
					System.out.println(result + " 행이 삭제 되었슨비다.");
				} else {
					System.out.println("쿼리가 실행 되었습니다.");
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception e2) {
				}
			}

			if (stmt != null) {
				try {
					stmt.close();
				} catch (Exception e2) {
				}
			}
		}

	}
}

 

메타데이터 ?

- 부가적인 정보로, 컬럼명, 컬럼 타입, 컬럼폭, 등을 의미하여 ResultSet에서 얻어온다.

package metaEx;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

import db_util.DBConn;

public class Ex001_MetaData {

	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ResultSetMetaData rsmd = null;

		try {
			conn = DBConn.getConnection();

			String sql = "SELECT * FROM score";

			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			rsmd = rs.getMetaData();

			// 컬럼수
			int cols = rsmd.getColumnCount();
			System.out.println("전체 컬럼 수 : " + cols);

			for (int i = 1; i <= cols; i++) {
				System.out.print("컬럼명 : " + rsmd.getColumnName(i) + "\t");
				System.out.print("컬럼타입명 : " + rsmd.getColumnTypeName(i) + "\t");
				System.out.println("컬럼폭 : " + rsmd.getPrecision(i));
			}
			System.out.println();

			// 데이터
			// 테이블의 숫자, 날짜 형등은 문자열로 가져올 수 있다.
			while (rs.next()) {
				for (int i = 1; i <= cols; i++) {
					System.out.print(rs.getString(i) + "\t");
				}
				System.out.println();
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception e2) {
				}
			}
			if (pstmt != null) {
				try {
					pstmt.close();
				} catch (Exception e2) {
				}
			}
		}

	}

}


package metaEx;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;

import db_util.DBConn;

public class Ex002_Matadata {

	public static void main(String[] args) {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		Connection conn = DBConn.getConnection();
		Statement stmt = null;
		ResultSet rs = null;
		ResultSetMetaData rsmd = null;

		String sql;

		try {
			System.out.print("쿼리 ? ");
			sql = br.readLine();
			sql = sql.trim();

			if (!sql.toUpperCase().startsWith("SELECT")) {
				System.out.println("SELECT 문만 가능합니다.");
				return;
			}

			stmt = conn.createStatement();
			rs = stmt.executeQuery(sql);
			rsmd = rs.getMetaData();

			int cols = rsmd.getColumnCount();
			for (int i = 1; i <= cols; i++) {
				System.out.print(rsmd.getColumnName(i) + "\t");
			}
			
			System.out.println();
			
			while (rs.next()) {
				for (int i = 1; i <= cols; i++) {
					System.out.print(rs.getString(i) + "\t");
				}
				System.out.println();
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception e2) {
				}
			}
			if (stmt != null) {
				try {
					stmt.close();
				} catch (Exception e2) {
				}
			}
		}

	}

}

 

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import db_util.DBConn;

/*
 - 결과 집합 유형
 	TYPE_FORWORD_ONLY : 기본. 순방향(next)만 가능
 	TYPE_SCROLL_SENSITIVE : 순방향, 역방향 가능, 수정 결과 바로 반영
 	TYPE_SCROLL_INSENSITIVE : 순방향, 역방향 가능, 수정 결과 바로 반영 안됨
 	
 - 동시성 유형
 	CONCUR_READ_ONLY : 기본. 읽기만 가능
 	CONCUR_UPDATABLE : 수정도 가능
 	
 */
public class Ex_Scroll {

	public static void main(String[] args) {
		Connection conn = DBConn.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			String sql = " SELECT hak, name, birth, kor, eng, mat FROM score ";
			
			// pstmt = conn.prepareStatement(sql); // 기본으로 순방향(next)만 가능
			
			// 순방향 및 역방향 가능
			pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, 
					ResultSet.CONCUR_READ_ONLY); 
			
			rs = pstmt.executeQuery();
			
			char ch;
			while(true) {
				do {
					System.out.print("1.처음 2.이전 3.다음 4.마지막 5.종료 => ");
					ch = (char)System.in.read();
					System.in.skip(2); // 엔터 버림
				} while (ch<'1'||ch>'5');
				
				if(ch=='5') {
					break;
				}
				
				switch(ch) {
				case '1':
					if(rs.first())
						System.out.println("처음->"+rs.getString(1)+":"+rs.getString(2));
					break;
				case '2':
					if(rs.previous())
						System.out.println("이전->"+rs.getString(1)+":"+rs.getString(2));
					break;
				case '3':
					if(rs.next())
						System.out.println("다음->"+rs.getString(1)+":"+rs.getString(2));
					break;
				case '4':
					if(rs.last())
						System.out.println("마지막->"+rs.getString(1)+":"+rs.getString(2));
					break;
				}
				
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) {
				try {
					rs.close();
				} catch (Exception e2) {
				}
			}
			
			if(pstmt != null) {
				try {
					pstmt.close();
				} catch (Exception e2) {
				}
			}
		}

	}

}

부하를 많이 준다.

 

 

트랜잭션 ? 

- 하나의 논리적인 작업 단위

- 자바에서 INSERT, UPDATE, DELETE 등의 작업을 실행하면 기본적으로 자동 COMMIT

 

실습용 테이블

더보기
CREATE TABLE test1 (
    id VARCHAR2(30) PRIMARY KEY,
    name VARCHAR2(30) NOT NULL
);

CREATE TABLE test2 (
    id VARCHAR2(30) PRIMARY KEY,
    birth VARCHAR2(30) NOT NULL,
    FOREIGN KEY(id) REFERENCES test1(id)
);

CREATE TABLE test3 (
    id VARCHAR2(30) PRIMARY KEY,
    tel VARCHAR2(30) NOT NULL,
    FOREIGN KEY(id) REFERENCES test1(id)
);

SELECT * FROM test1;
SELECT * FROM test2;
SELECT * FROM test3;
package transEx;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import db_util.DBConn;

public class Ex_Transaction {

	public static void main(String[] args) {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		Connection conn = DBConn.getConnection();
		PreparedStatement pstmt = null;
		
		String sql;
		String id, name, birth, tel;
		
		try {
			System.out.print("아이디 ? ");
			id = br.readLine();
			
			System.out.print("이름 ? ");
			name = br.readLine();
			
			System.out.print("생년월일 ? ");
			birth = br.readLine();
			
			// 전화번호를 입력하지 않고 그냥 엔터를 누를경우와 전화번호를 입력했을 때와 비교
			System.out.print("전화번호 ? ");
			tel = br.readLine();
			
			// 트랜잭션이 필요한 부분은 반드시 하나의 try 블록에서 모두 코드를 작성해야 한다.
			
			// 자동으로 COMMIT 되지 않도록 설정
			conn.setAutoCommit(false);
			
			sql = "INSERT INTO test1(id, name) VALUES (?, ?)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, name);
			pstmt.executeUpdate();
			pstmt.close();
			
			sql = "INSERT INTO test2(id, birth) VALUES (?, ?)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, birth);
			pstmt.executeUpdate();
			pstmt.close();
			
			sql = "INSERT INTO test3(id, tel) VALUES (?, ?)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, tel);
			pstmt.executeUpdate();
			
			// 커밋
			conn.commit();
			
			System.out.println("데이터 추가 성공...");
		} catch (SQLException e) {
			try {
				// 세 개의 테이블 중 하나의 테이블이라도 추가하는 도중에 에러가 발생하면 모두 롤백
				conn.rollback();
			} catch (Exception e2) {
			}
			System.out.println(e.toString());
			System.out.println("데이터 추가 실패...");
		
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (pstmt != null) {
				try {
					pstmt.close();
				} catch (Exception e2) {
				}
			}
			
			try {
				conn.setAutoCommit(true);
			} catch (Exception e2) {
			}
			
			DBConn.close(); // 프로그램 종료 시
			
		}

	}

}

 

conn.setAutoCommit(false)를 통해 트랜잭션이 다 이뤄지고 나서 3개의 테이블에 모두 COMMIT되지 않는 경우에는 데이터가 들어가지 않도록 한다. 또 트랜잭션이 필요한 부분은 반드시 하나의 try 블록에서 모두 코드를 작성해야한다. 예외가 발생했을때는 ROLLBACK을 통해 데이터가 들어가지 않도록 해야한다.

-- 예외처리

DECLARE
    vName VARCHAR2(30);
    vSal NUMBER;
BEGIN
    SELECT name, sal INTO vName, vSal FROM emp WHERE empNo = '1001';
    -- SELECT name, sal INTO vName, vSal FROM emp WHERE empNo = '9001'; -- NO_DATE_FOUND
    -- SELECT name, sal INTO vName, vSal FROM emp; -- TOO_MANY_ROWS
    DBMS_OUTPUT.PUT_LINE(vName || ' : ' || vSal);
    
    EXCEPTION
        WHEN NO_DATA_FOUND THEN 
            DBMS_OUTPUT.PUT_LINE('등록된 자료가 없습니다.');
        WHEN TOO_MANY_ROWS THEN 
            DBMS_OUTPUT.PUT_LINE('두 개 이상의 레코드가 존재합니다.');
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('기타 에러가 발생 했습니다.');
END;
/

 

-- 사용자 정의 예외

DECLARE
    vName VARCHAR2(30);
    vSal NUMBER;
    
    sal_check EXCEPTION; -- 사용자 정의 예외 선언
BEGIN
    SELECT name, sal INTO vName, vSal FROM emp WHERE empNo = '1001';
    IF vSal >= 3000000 THEN
        RAISE sal_check; -- 예외를 발생시킴
    END IF;
    DBMS_OUTPUT.PUT_LINE(vName || ' : ' || vSal);
    
    EXCEPTION
        WHEN NO_DATA_FOUND THEN 
            DBMS_OUTPUT.PUT_LINE('등록된 자료가 없습니다.');
        WHEN TOO_MANY_ROWS THEN 
            DBMS_OUTPUT.PUT_LINE('두 개 이상의 레코드가 존재합니다.');
        WHEN sal_check THEN
            DBMS_OUTPUT.PUT_LINE('급여가 300 이상입니다.');
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('기타 에러가 발생 했습니다.');
END;
/

 

또, 여태까지 썼던

2021.08.20 - [쌍용강북교육센터/8월] - 0819_Oracle[PL/SQL] : PROCEDURE, FUNCTION 예제

 

IF 조건

RAISE_APPLICATION_ERROR(-20001, '여기에 메세지 입력');

END IF;

방법도 있었다.

 

이때 안에있는 번호는 20000 부터 20999까지 가능하다.

커서 CURSOR ?

 

- 하나의 레코드가 아닌 여러 레코드로 구성된 작업영역에서 SQL문을 실행하고 그 과정에 생긴 정보를 저장하기 위해서 CURSOR를 사용한다.

- 오라클 서버에 의해 실행되는 모든 SQL문은 연관된 각각의 커서를 소유하고 있다.

 

주기억장치에 저장된 공간이 커서이고, 그 커서에서 하나씩 꺼내오는 것이 FETCH이다.

 

-- 명시적 커서

DECLARE
    vname emp.name%TYPE;
    vsal emp.sal%TYPE;
    
    -- 1. 커서 선언
    CURSOR cur_emp IS SELECT name, sal FROM emp;
BEGIN
    -- 2. 커서 OPEN
    OPEN cur_emp;
    
    LOOP
        -- 3. FETCH - 하나 레코드를 가져옴
        FETCH cur_emp INTO vname, vsal;
        EXIT WHEN cur_emp%NOTFOUND; -- 이거 없으면 에러. 
        -- 가져올 자료가 없으면 LOOP를 빠져 나가라.
        
        DBMS_OUTPUT.PUT_LINE(vname || ' : ' || vsal);
    END LOOP;
    -- 커서 CLOSE
    CLOSE cur_emp;
END;
/

 

-- 암시적 커서

DECLARE
    vempNo emp.empNo%TYPE;
    vName emp.name%TYPE;
    vSal emp.sal%TYPE;
    vCount NUMBER;
BEGIN
    vempNo := '1001';
    SELECT name, sal INTO vname, vsal FROM emp WHERE empNo = vempNo;
        -- 레코드가 없거나 2개 이상 존재하면 오류
    vCount := SQL%ROWCOUNT; -- 해당 SQL 문에 영향을 받은 행의 수 
    
    DBMS_OUTPUT.PUT_LINE(vCount || ' : ' || '개 존재');
END;
/

 

-- CURSOR FOR LOOP : 자동 OPNE, 자동 FETCH, 자동 CLOSE

DECLARE 
    CURSOR cur_emp IS SELECT name, sal FROM emp;
BEGIN
    FOR rec IN cur_emp LOOP 
        DBMS_OUTPUT.PUT_LINE(rec.name || ' : ' || rec.sal);
    END LOOP;
END;
/

 

커서 변수 ( CURSOR VARIABLE) 

- 명시적 커서와 암시적 커서는 정적이므로, 커서가 만들어지는 시점에서 쿼리문이 정의된다. 따라서 런타임까지는 커서에 이용할 쿼리를 정의할 수 없기 때문에 REF CURSOR 와 커서변수를 제공하여, 런타임 시에 커서의 쿼리를 정의하고 결과 셋을 처리할 수 있도록 하고 있다.

- 커서 타입의 변수는 프로시저 등의 매개 변수로 사용할 수 있다.

- REF CURSOR 커서 타입 

  강한 커서 타입 : 쿼리의 결과 셋을 구성할 리턴 타입을 %ROWTYPE 속성으로 정의한다.

  약한 커서 타입 : 리턴 타입 정의를 포함하지 않고, 어떤 결과 셋(set)을 얻는 경우에도 이용할 수 있다.

- REF CURSOR type 정의

  형식

   TYPE type_name IS REF CURSOR [ RETRUN return_type ]

  커서 타입 정의 예

   TYPE refcur IS REF CURSOR ; -- 약한 커서 타입

   TYPE refcur_emp IS REF CURSOR RETURN emp%ROWTYPE ; -- 강한 커서 타입

  커서 변수 사용

  1) 커서 변수 선언 : 커서변수 커서타입명;

  2) 커서 변수 사용 : OPEN 커서변수 FOR SELECT 문 ;

 

-- SYS_REFCURSOR 예

CREATE OR REPLACE PROCEDURE pEmpSelectList
(
    pResult OUT SYS_REFCURSOR 
)
IS
BEGIN
    OPEN pResult FOR
        SELECT name, sal, bonus FROM emp;
END;
/

 

-- 확인용 프로시저

CREATE OR REPLACE PROCEDURE pEmpSelectResult
IS
    vName emp.name%TYPE;
    vSal emp.sal%TYPE;
    vBonus emp.bonus%TYPE;
    vResult SYS_REFCURSOR;
BEGIN
    pEmpSelectList( vResult );
    LOOP 
        FETCH vResult INTO vName, vSal, vBonus;
        EXIT WHEN vResult%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(vName || ' : ' || vSal || ' : ' || vBonus);
    END LOOP;
END;
/

 

-- 결과 확인

EXEC pEmpSelectResult;

 

'쌍용강북교육센터 > 8월' 카테고리의 다른 글

0823_Java : InputStream 클래스  (0) 2021.08.23
0820_Oracle[PL/SQL] : 예외처리  (0) 2021.08.23
0820_Oracle[PL/SQL] : TRIGGER 트리거  (0) 2021.08.23
0820_Oracle[PL/SQL] : PACKAGE 패키지  (0) 2021.08.23
0820_Oracle : INDEX  (0) 2021.08.23

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

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

 

트리거 ? 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 패키지명;

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

+ Recent posts