[문제] 사원번호를 입력 받아서 사원의 last_name을 출력해주세요. |
/* 호출 */ execute emp_find.find(100) execute DBMS_OUTPUT.PUT_LINE(emp_find.find(100)) SELECT employee_id, emp_find.find(employee_id) FROM employees; |
CREATE OR REPLACE PACKAGE emp_find IS PROCEDURE find(p_id IN number); FUNCTION find(p_id IN number) RETURN varchar2; END; / CREATE OR REPLACE PACKAGE BODY emp_find IS /* PROCEDURE */ PROCEDURE find(p_id IN number) IS v_name varchar2(30); BEGIN SELECT last_name INTO v_name FROM hr.employees WHERE employee_id = p_id; DBMS_OUTPUT.PUT_LINE(v_name); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END; /* FUNCTION */ FUNCTION find(p_id IN number) RETURN varchar2 IS v_name varchar2(30); BEGIN SELECT last_name INTO v_name FROM hr.employees WHERE employee_id = p_id; RETURN v_name; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END; END; / |
# 에러 확인
SHOW ERROR
SELECT * FROM user_errors;
# 객체
SELECT * FROM user_objects
# 패키지 안에 생성된 명시적 커서 지속상태
CREATE OR REPLACE PACKAGE pack_cursor IS PROCEDURE open; PROCEDURE next(p_num IN number); PROCEDURE close; END pack_cursor; / CREATE OR REPLACE PACKAGE BODY pack_cursor IS /* private cursor : package hody에서는 어디서든지 사용할 수 있다. */ CURSOR c1 IS SELECT employee_id, last_name FROM hr.employees ORDER BY 1 desc; PROCEDURE open IS BEGIN IF NOT c1%ISOPEN THEN -- 오픈되어있지 않으면 TRUE OPEN c1; DBMS_OUTPUT.PUT_LINE('c1 CURSOR OPEN'); END IF; END; PROCEDURE next(p_num IN number) IS v_id number; v_name varchar2(30); BEGIN LOOP EXIT WHEN c1%rowcount >= p_num; FETCH c1 INTO v_id, v_name; DBMS_OUTPUT.PUT_LINE('ID : '||v_id||' NAME : '||v_name); END LOOP; -- FETCH 계속 열려있다 -> 닫기 전까지 데이터 누적 END; PROCEDURE close IS BEGIN IF c1%ISOPEN THEN CLOSE c1; DBMS_OUTPUT.PUT_LINE('c1 CURSOR CLOSE'); END IF; END; END pack_cursor; / |
/* 호출 */ execute pack_cursor.open -- 로드. 1번행 기억 execute pack_cursor.next(3) -- 1번행 ~ 3번행까지 출력 execute pack_cursor.next(6) -- cursor.next 프로시저 안에 CLOSE가 없기 때문에 연속 진행 6번행까지 출력 execute pack_cursor.close |
/* 출력 */ c1 CURSOR OPEN ID : 206 NAME : Gietz ID : 205 NAME : Higgins ID : 204 NAME : Baer ID : 203 NAME : Mavris ID : 202 NAME : Fay ID : 201 NAME : Hartstein c1 CURSOR CLOSE |
# 패키지안에서 생성된 global, private 변수는 session이 종료할 때까지 지속적으로 변경한 값을 사용한다.
CREATE OR REPLACE PACKAGE comm_pkg IS PROCEDURE reset_comm(p_comm IN number); END; / CREATE OR REPLACE PACKAGE BODY comm_pkg IS g_comm number := 0.1; -- private 변수 /* private function */ FUNCTION validation_comm(v_comm IN number) RETURN BOOLEAN -- TRUE, FALSE, NULL IS v_max_comm number; -- local 변수 BEGIN SELECT max(commission_pct) INTO v_max_comm FROM hr.employees; IF v_comm > v_max_comm THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END validation_comm; /* global procedure */ PROCEDURE reset_comm(p_comm IN number) IS BEGIN IF validation_comm(p_comm) THEN DBMS_OUTPUT.PUT_LINE('OLD : '||g_comm); g_comm := p_comm; DBMS_OUTPUT.PUT_LINE('NEW : '||g_comm); ELSE RAISE_APPLICATION_ERROR(-20000,'기존 최고값을 넘을 수 없습니다.'); END IF; END reset_comm; END comm_pkg; / |
/* 호출 */ execute comm_pkg.reset_comm(0.2); |
/* 연속호출 결과 */ OLD : .1 NEW : .2 OLD : .2 NEW : .2 OLD : .2 NEW : .2 -- comm_pkg.reset_comm 프로시저 내 0.2 -> p_comm -> g_comm 되어 0.2로 저장되어있다. ... |
# PRAGMA SERIALLY_REUSABLE : 패키지내에서 생성된 생성자들의 변경한 값은 호출 동안에만 변경한 값을 사용하고 호출이 끝나면 원래 값으로 되돌아가는 기능
CREATE OR REPLACE PACKAGE comm_pkg IS PRAGMA SERIALLY_REUSABLE; PROCEDURE reset_comm(p_comm IN number); END; / CREATE OR REPLACE PACKAGE BODY comm_pkg IS PRAGMA SERIALLY_REUSABLE; -- 지시어 g_comm number := 0.1; -- private 변수 /* private function */ FUNCTION validation_comm(v_comm IN number) RETURN BOOLEAN -- TRUE, FALSE, NULL IS v_max_comm number; -- local 변수 BEGIN SELECT max(commission_pct) INTO v_max_comm FROM hr.employees; IF v_comm > v_max_comm THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END validation_comm; /* global procedure */ PROCEDURE reset_comm(p_comm IN number) IS BEGIN IF validation_comm(p_comm) THEN DBMS_OUTPUT.PUT_LINE('OLD : '||g_comm); g_comm := p_comm; DBMS_OUTPUT.PUT_LINE('NEW : '||g_comm); ELSE RAISE_APPLICATION_ERROR(-20000,'기존 최고값을 넘을 수 없습니다.'); END IF; END reset_comm; END comm_pkg; / |
/* 호출 */ execute comm_pkg.reset_comm(0.2); |
/* 연속호출 결과 */ OLD : .1 NEW : .2 OLD : .1 NEW : .2 OLD : .1 NEW : .2 -- PRAGMA SERIALLY_REUSABLE 적용 ... |
<< 상수 표준화 >>
CREATE OR REPLACE PACKAGE global_consts IS c_mile_2_kilo CONSTANT number := 1.6093; c_kilo_2_mile CONSTANT number := 0.6214; c_yard_2_meter CONSTANT number := 0.9144; c_meter_2_yard CONSTANT number := 1.0936; END; / |
BEGIN DBMS_OUTPUT.PUT_LINE('20mile = '||20 * global_consts.c_mile_2_kilo||'km'); END; / BEGIN DBMS_OUTPUT.PUT_LINE('20km = '||20 * global_consts.c_kilo_2_mile||'mile'); END; / BEGIN DBMS_OUTPUT.PUT_LINE('20yard = '||20 * global_consts.c_yard_2_meter||'m'); END; / BEGIN DBMS_OUTPUT.PUT_LINE('20m = '||20 * global_consts.c_meter_2_yard||'yd'); END; / CREATE OR REPLACE FUNCTION meter_to_yard(p_m IN number) RETURN number IS BEGIN RETURN(p_m * global_consts.c_meter_2_yard); END; / SELECT meter_to_yard(100) FROM dual; |
# 상수는 한번 입력한 값만 사용해야 한다. execute global_consts.c_mile_2_kilo := 2.6093 -- 수정 시 오류 -- PLS-00363: expression 'GLOBAL_CONSTS.C_MILE_2_KILO' cannot be used as an assignment target |
desc departments 이름 널? 유형 --------------- -------- ------------ DEPARTMENT_ID NOT NULL NUMBER(4) DEPARTMENT_NAME NOT NULL VARCHAR2(30) MANAGER_ID NUMBER(6) LOCATION_ID NUMBER(4) INSERT INTO hr.departments(department_id, department_name) VALUES(280,'DA'); SELECT * FROM hr.departments; rollback; INSERT INTO hr.departments(department_id, department_name) VALUES(280,null); -- ORA-01400: cannot insert NULL into |
↓
DECLARE e_insert_null EXCEPTION; PRAGMA EXCEPTION_INIT(e_insert_null,-01400); BEGIN INSERT INTO hr.departments(department_id, department_name) VALUES(280,null); EXCEPTION WHEN e_insert_null THEN DBMS_OUTPUT.PUT_LINE('INSERT OPERATION FAILED'); DBMS_OUTPUT.PUT_LINE(SQLERRM); END; / >> 오류가 있어도 EXCEPTION 핸들링으로 정상 종료 됨. >> 사용자마다 오류명칭 다양 |
↓
<<EXCEPTION 이름 표준화>>
CREATE OR REPLACE PACKAGE err_pkg IS e_insert_null EXCEPTION; PRAGMA EXCEPTION_INIT(e_insert_null,-01400); END; / |
BEGIN INSERT INTO hr.departments(department_id, department_name) VALUES(280,null); EXCEPTION WHEN err_pkg.e_insert_null THEN DBMS_OUTPUT.PUT_LINE('INSERT OPERATION FAILED'); DBMS_OUTPUT.PUT_LINE(SQLERRM); END; / |
■ Trigger 트리거
- 트리거는 데이터베이에 저장되고 지정된 이벤트에 대한 응답으로 실행되는 PL/SQL블록이다.
- 오라클 데이터베이스는 지정된 조건이 발생할 때 트리거를 자동으로 실행한다.
- 트리거는 테이블, 뷰, 스키마(소유자), 데이터베이스(모든 유저)에 정의할 수 있다.
- CREATE TRIGGER 시스템 권한 필요하다.
# 트리거 유형
- 특정 테이블, 뷰에 INSERT, UPDATE, DELETE 문
- CREATE, ALTER, DROP 문
- 데이터베이스 시작 또는 종료
- 특정 오류 메세지 또는 임의의 오류 메세지
- 유저 로그인, 로그오프
/* CREATE TRIGGER 권한 확인 */ SELECT * FROM session_privs; |
DROP TABLE hr.dept PURGE;
CREATE TABLE hr.dept AS SELECT * FROM hr.departments;
<< DML TRIGGER>>
/* BEFORE 문장 트리거 */ /* 작업 수행 전 트리거 실행. 제약조건체크 등 */ CREATE OR REPLACE TRIGGER dept_before BEFORE INSERT ON hr.dept -- INSERT 작업 전 트리거 BEGIN DBMS_OUTPUT.PUT_LINE('INSERT하기 전에 문장트리거 수행'); END; / |
/* AFTER 문장 트리거 */ /* 작업의 영향이 있든 없든 무조건 맨 뒤에 수행 */ CREATE OR REPLACE TRIGGER dept_after AFTER INSERT ON hr.dept BEGIN DBMS_OUTPUT.PUT_LINE('INSERT한 후에 문장트리거 수행'); END; / |
/* BEFORE 행 트리거 */ /* 누가 행을 수정하려는지 작업 전 수행 */ CREATE OR REPLACE TRIGGER dept_row_before BEFORE INSERT ON hr.dept FOR EACH ROW -- 행트리거 BEGIN DBMS_OUTPUT.PUT_LINE('INSERT하기 전에 행트리거 수행'); END; / |
/* AFTER 행 트리거 */ /* 누가 행을 수정하고 갔는지 영향을 받은 행이 있는 경우 수행 */ CREATE OR REPLACE TRIGGER dept_row_after AFTER INSERT ON hr.dept FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('INSERT한 후에 행트리거 수행'); END; / |
/* 생성된 트리거 확인 */ SELECT * FROM user_triggers; INSERT INTO hr.dept(department_id, department_name, manager_id, location_id) VALUES(300, 'Data Architect', 100, 1500); >> 결과 INSERT하기 전에 문장트리거 수행 INSERT하기 전에 행트리거 수행 INSERT한 후에 행트리거 수행 INSERT한 후에 문장트리거 수행 |
# FOR EACH ROW : 행 트리거, 영향을 받은 행이 있는 경우 수행되는 트리거
DROP TABLE hr.copy_emp PURGE;
CREATE TABLE hr.copy_emp
AS
SELECT employee_id, last_name, salary, department_id
FROM hr.employees;
CREATE OR REPLACE TRIGGER test_trig BEFORE /* DELETE OR INSERT -> 행단위 작업 // UPDATE (OF salary) -> 행, 칼럼단위 작업 */ DELETE OR INSERT OR UPDATE OF salary ON hr.copy_emp /* 행트리거 */ FOR EACH ROW /* WHEN() : 행트리거에서만 사용하는 조건절, 옵션 */ WHEN(new.department_id = 20 OR old.department_id = 10) /* 선언부에서는 new, old 앞에 기호가 없다. new : INSERT,UPDATE, old : DELETE,UPDATE */ DECLARE salary_diff number; BEGIN /* 작업마다 다르게 트리거 설정 시 조건문 사용 */ /* DML 작업에 대한 조건 설정 시 명칭 오류때문에 조건부 술어 DELETING, INSERTING, UPDATING 사용*/ IF DELETING THEN DBMS_OUTPUT.PUT_LINE('OLD SALARY : '||:old.salary); /* :old.이전데이터 , :new.수정후데이터 */ ELSIF INSERTING THEN DBMS_OUTPUT.PUT_LINE('NEW SALARY : '||:new.salary); ELSE /* UPDATING */ salary_diff := :new.salary - :old.salary; -- 급여 변동 차액 DBMS_OUTPUT.PUT_LINE('ID : '||:new.employee_id||' OLD SALARY : '||:old.salary|| ' NEW SALARY : '||:new.salary||' Difference of Salary : '||salary_diff); END IF; END; / |
UPDATE hr.copy_emp SET salary = salary * 1.1 WHERE department_id = 20; >> ID : 201 OLD SALARY : 14300 NEW SALARY : 15730 Difference of Salary : 1430 ID : 202 OLD SALARY : 6600 NEW SALARY : 7260 Difference of Salary : 660 UPDATE hr.copy_emp SET salary = salary * 1.1 WHERE department_id = 10; >> ID : 200 OLD SALARY : 5324 NEW SALARY : 5856.4 Difference of Salary : 532.4 UPDATE hr.copy_emp SET salary = salary * 1.1 WHERE department_id = 30; -- 6개 행 이(가) 업데이트되었지만 트리거 없음 |
INSERT INTO hr.copy_emp(employee_id, last_name, salary, department_id) VALUES(300, 'Oracle', 1000, 20); >> NEW SALARY : 1000 -- new.department_id = 20 조건 적용 INSERT INTO hr.copy_emp(employee_id, last_name, salary, department_id) VALUES(400, 'HADOOP', 2000, 10); >> 트리거 없음 -- old.department_id = 10 조건으로 DELETE 또는 UPDATE에서 작동 |
DELETE FROM hr.copy_emp WHERE department_id = 10; >> OLD SALARY : 5324 -- old.department_id = 10 조건 적용 DELETE FROM hr.copy_emp WHERE department_id = 20; >> 트리거 없음 -- new.department_id = 20 조건으로 INSERT 또는 UPDATE에서 작동 |
DELETE FROM hr.copy_emp WHERE employee_id = 200; >> OLD SALARY : 5324 -- 200번 사원이 10번 부서 사원이기 때문에 old.department_id = 10 조건으로 트리거 작동 DELETE FROM hr.copy_emp WHERE employee_id = 100; >> 트리거 없음 -- 조건에 맞지 않아 트리거 작동안함 |
# PACKAGE
- 관련있는 것끼리 모아서 관리
- 오버로딩 : 하나의 명칭으로 여러 작업 수행
- 글로벌변수 : SPEC 영역에 선언
- 상수표준화 : CONSTANT 사용. 고정값.
- EXCEPTION 표준화 : 패키지 SPEC영역에서 EXCEPTION 선언
PRAGMA EXCEPTION_INIT(오류변수명,-오류번호);
# PRAGMA SERIALLY_REUSABLE
패키지내에서 생성된 생성자들의 변경한 값은 호출 동안에만 변경한 값을 사용하고 호출이 끝나면 원래 값으로 되돌아가는 기능
# Explicit Cursor 명시적 커서
CURSOR 커서명 IS -- 선언
OPEN 커서명; -- OPEN : 메모리 할당
FETCH 커서명 INTO 칼럼; -- FETCH : 첫번째 데이터 변수에 로
CLOSE 커서명; -- CLOSE : 종료
# TRIGGER
CREATE OR REPLACE TRIGGER dept_before 트리거 생성
BEFORE 작업 전 or AFTER 작업 후
DELETE OR INSERT OR UPDATE OF salary ON hr.copy_emp
- DELETE, INSERT : 행단위 작업
- UPDATE (OF 칼럼명) : 행, 칼럼단위 작업
FOR EACH ROW : 행단위 작업 대상 트리거 발생
WHEN(행조건, 옵션)
DECLARE
BEGIN
- DML 명칭으로 조건 검색 시 DELETING, INSERTING, UPDATING 사용
END;
- AFTER : 작업의 영향이 없어도 마지막에 실행
- AFTER ~ FOR EACH ROW : 영향을 받은 행이 있을 경우 실행
선언부 | DECLARE ~ END | |
new. | :new. | INSERT, UPDATE |
old. | :old. | DELETE, UPDATE |
'Oracle SQL' 카테고리의 다른 글
24 PL/SQL Autonomous Transaction (0) | 2023.11.08 |
---|---|
23 PL/SQL TRIGGER (0) | 2023.11.07 |
21 PL/SQL PACKAGE (1) | 2023.11.03 |
20 PL/SQL PROCEDURE, FUNCTION (0) | 2023.11.02 |
19 PL/SQL PROCEDURE (0) | 2023.11.01 |