이번에는 Java를 Orcale에 연동한 JDBC를 이용해서 Semi Project를 진행했다.

0901~0910까지 진행했으며 10일에 발표를 했다.

 

ERD

 

회원가입, 회원주문

더보기
회원가입 예시
회원주문

현재 주문과 동시에 포인트를 넣어주고 있음...(소스에서)

 나중에 배송이 완료된 이후(지금은 이것까지는 안했긴 함...) 배송일을 입력 한 후에 포인트를 넣던가 방식을 바꿔야 될 것 같다.

회원주문리스트

 

후기작성

>> 후기작성의 경우 도착일이 생긴 후에 작성하게끔 하려면 어떻게 해야될까 ?

후기작성할 때 빵코드를 입력받지 않고 후기작성하려면 ? >> 주문번호를 받아서 먼저 주문번호에 해당하는 결과를 받은 후에 별점 내용을 거기에 넣어서 해줘야될 듯 ..

 

내 정보

아직 상품준비중인데 포인트가 쌓였음. 이부분도 나중에는 생각해봐야할 문제

재고입고

더보기

아까 민트초코마카롱을 5개를 주문했는데 10개가 사라져있었다. 소스를 뜯어보니, 

재고를 변경하는 곳에 문제가 있었다.

빵코드를 입력받아 재고는 빵코드로 합쳐서 보여지는데, 

재고 업데이트를 빵코드를 받아서 하다보니까 빵코드에 해당하는 재고는 입고번호에 따라 계속 넣어지는데, (즉 빵코드에 해당하는 자료가 입고받을 때마다 넣어지므로 여러개) 재고를 2번을 입고 받았었기 때문에 해당 빵코드의 qty에서 -5가 다 들어가기 때문에 2번의 입고 정보에 다 -5가 되어서 -10이 되었던 것이다.

 

납품업체 리스트

납품업체 리스트 확인.

납품업체 하나에 (부모테이블) 입고(자식테이블) 이 여러개가 존재할 수 있다. 참조키로 납품업체 코드를 해놨다. 그래서 납품업체 삭제는 거의 사용하지 않을 듯. 입고를 한번이라도 했으면 삭제해도 자식 레코드는 재고를 위해서 사용해야되서.

 

입고1
입고2

 

입고 후 재고확인

입고날짜를 넣어줄때는 매개변수가 없는 메소드를 통해 현재 날짜를 넣어주고,

데이터베이스에서 가져올 때는 매개변수가 있는 메소드를 통해 데이터베이스에서 가져온 날짜로 set할 수 있도록 overloading 을 통해 구현했다. 

입고번호도 overloading을 통해서 입고에서 넣어줄 때는 오늘날짜+랜덤번호 를 통해 넣었다.

데이터베이스에서 가져와서 데이터를 출력할 때는 가져온 값을 매개변수로 해서 넣어준다.

 

유통기한 조회, 폐기등록

더보기
폐기 전 재고
폐기
폐기 후 재고

폐기가 고구마케익만 되었다. 소스를 보고 해당 재고를 다 없애도록 고쳐야 할듯...

폐기 후 재고

하나씩 만 폐기되도록 되어있음. 한번 더 실행하니까 이번엔 뉴욕치즈케익이 사라졌음.

싹 다 사라지도록 변경해야될 것 같다.

배송일자 등록

더보기
배송등록 전 주문리스트

주문총금액부분은 없애야함.

배송등록

해당 주문번호에 배송일자를 추가한다.

배송등록 후 주문리스트
새로운 주문 확인

총 주문리스트 말고 새로운 주문만 확인하는 리스트도 있다.

배송등록 후 주문 확인 리스트

배송등록 후에는 주문 확인 리스트가 뜨지 않도록 설정했다.

비회원주문, 비회원주문확인

더보기
비회원 구매
비회원 구매
비회원구매목록확인

 

후기확인

 

판매현황

더보기
회원 판매현황

총팔린금액에 개당 금액이 들어가있다. 고쳐야함!

비회원 판매현황

여기도 총금액에 개당금액이 들어가있다.

 

 

수정이 필요한 부분은 짬짬히 따로 수정해가며 완성도를 높여보자!!

10일 남짓 시간동안 요구사항분석, 시나리오작성, 추상화, 코딩, 함께해준 팀원들에게 모두 감사합니다:)

 

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;

+ Recent posts