CREATE OR REPLACE PROCEDURE check_salary (p_job IN varchar2, p_sal IN number) IS v_min_sal number; v_max_sal number; BEGIN SELECT min_salary, max_salary INTO v_min_sal, v_max_sal FROM hr.jobs WHERE job_id = upper(p_job); IF p_sal NOT BETWEEN v_min_sal AND v_max_sal THEN RAISE_APPLICATION_ERROR(-20000,'Invalid salary $'||p_sal||'. Salaries for job '||p_job|| ' must be between $'||v_min_sal||' and $'||v_max_sal); END IF; END check_salary; / |
/* 호출 */ execute check_salary('IT_PROG', 20000) -- ORA-20000: Invalid salary $20000. Salaries for job IT_PROG must be between $4000 and $10000 |
/* 테이블 내 트리거 체크 */ SELECT * FROM user_triggers WHERE table_name = 'EMPLOYEES'; |
/* 트리거에서 프로시저 호출 */ CREATE OR REPLACE TRIGGER check_salary_trg AFTER INSERT OR UPDATE OF salary ON hr.employees FOR EACH ROW BEGIN check_salary(:new.job_id, :new.salary); END; / || /* BEGIN~END -> CALL 문 */ CREATE OR REPLACE TRIGGER check_salary_trg AFTER INSERT OR UPDATE OF salary ON hr.employees FOR EACH ROW CALL check_salary(:new.job_id, :new.salary) -- 세미콜론(;) 제외 / |
/* 호출 */ UPDATE hr.employees SET salary = 6000 WHERE employee_id = 115; -- ORA-20000: Invalid salary $6000. Salaries for job PU_CLERK must be between $2500 and $5500 |
■ 독립 트랜잭션(Autonomous Transaction) (8i, 9iR2~)
- PRAGMA AUTONOMOUS_TRANSACTION 지시어를 선언부분에서 작성하면 독립 트랜잭션이 수행된다.
- 독립 트랜잭션은 시작 트랜잭션의 결과의 영향을 주지 않으면서 변경 사항에 대해서 COMMIT, ROLLBACK 할 수 있다.
DROP TABLE test PURGE;
CREATE TABLE test (name char(10));
CREATE OR REPLACE PROCEDURE p2 IS BEGIN INSERT INTO test(name) VALUES ('두번째'); -- 4 COMMIT; -- 5 END; / CREATE OR REPLACE PROCEDURE p1 IS BEGIN INSERT INTO test(name) VALUES ('첫번째'); -- 2 p2; -- 3 END; / |
/* 호출 */ EXECUTE p1 -- 1 SELECT * FROM hr.test; >> P1 호출 > P1_INSERT 실행 > P1_P2 호출 > P2_INSERT 실행 > P2_COMMIT > 1,2 둘다 영구저장 |
TRUNCATE TABLE test;
- DELETE : ROLLBACK 사용 가능. UNDO 공간 필요(휴지통)
- TRUNCATE : ROLLBACK 불가.
CREATE OR REPLACE PROCEDURE p2 IS PRAGMA AUTONOMOUS_TRANSACTION; -- 4 : 앞에꺼는 대기, 트랜잭션 다시 시작 BEGIN INSERT INTO test(name) VALUES ('두번째'); -- 5 COMMIT; -- 6 트랜잭션 종료 END; / CREATE OR REPLACE PROCEDURE p1 IS BEGIN INSERT INTO test(name) VALUES ('첫번째'); -- 2 트랜잭션 시작 p2; -- 3 END; / |
/* 호출 */ EXECUTE p1 -- 1 SELECT * FROM hr.test; >> P1 호출 > P1_INSERT 실행 > P1_P2 호출 > P2_PRAGMA AUTONOMOUS_TRANSACTION 적용 > P1_INSERT 대기 + P2_INSERT 실행 > P2_COMMIT ROLLBACK; SELECT * FROM hr.test; > 1은 대기상태에서 종료되어 2번만 영구저장 |
CREATE TABLE trigger_tab (id number,name varchar2(30),day timestamp default systimestamp); CREATE TABLE trigger_log (id number, name varchar2(30), log_day timestamp default systimestamp); |
CREATE OR REPLACE TRIGGER trig_log AFTER INSERT OR UPDATE OR DELETE ON trigger_tab FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO trigger_log (id,name,log_day) VALUES (:new.id,:new.name,:new.day); COMMIT; END; / INSERT INTO trigger_tab(id,name,day) VALUES (1,user,default); SELECT * FROM hr.trigger_tab; SELECT * FROM hr.trigger_log; ROLLBACK; SELECT * FROM hr.trigger_tab; SELECT * FROM hr.trigger_log; |
>> trigger_tab 테이블에 INSERT 작업 후 트리거 작동 >> PRAGMA AUTONOMOUS_TRANSACTION 적용 - trigger_log 테이블에 INSERT + COMMIT >> ROLLBACK하면 trigger_tab은 미저장. >> 영구저장을 하던말던 로그남기기 |
- SQL 엔진 : SQL문을 구문 분석하고실행하는 엔진. 경우에 따라 PL/SQL엔진에 데이터를 반환한다.
(명시적 커서를 사용할 때 결과집합을 변수에 로드하는 FETCH 시점)
- PL/SQL 엔진 : 프로시저문을 실행하지만 SQL문은 SQL엔진에 전달한다. -> 문맥전환
DECLARE CURSOR emp_cur IS SELECT * FROM hr.employees WHERE department_id = 20; -- 2명 v_rec emp_cur%rowtype; BEGIN OPEN emp_cur; -- SQL엔진 FETCH emp_cur INTO v_rec; -- PL/SQL엔진 DBMS_OUTPUT.PUT_LINE(v_rec.last_name); FETCH emp_cur INTO v_rec; DBMS_OUTPUT.PUT_LINE(v_rec.last_name); CLOSE emp_cur; END; / |
DECLARE CURSOR emp_cur IS SELECT * FROM hr.employees WHERE department_id = 20; -- 2명 v_rec emp_cur%rowtype; BEGIN OPEN emp_cur; LOOP FETCH emp_cur INTO v_rec; EXIT WHEN emp_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_rec.last_name); END LOOP; CLOSE emp_cur; END; / |
↓
# BULK COLLECT INTO : 결과 집합을 PL/SQL엔진에 전달하기 전에 SQL엔진이 이를 대량으로 바인드할 수 있도록 하는 절이다.
DECLARE TYPE tab_type IS TABLE OF hr.employees%rowtype; v_tab tab_type; BEGIN SELECT * BULK COLLECT INTO v_tab -- 배열변수 사용, 문맥전환 1번에 끝 FROM hr.employees WHERE department_id = 20; FOR i IN v_tab.FIRST..v_tab.LAST LOOP DBMS_OUTPUT.PUT_LINE(v_tab(i).last_name); END LOOP; END; / |
DECLARE CURSOR emp_cur IS SELECT * FROM hr.employees WHERE department_id = 20; TYPE tab_type IS TABLE OF emp_cur%rowtype; v_tab tab_type; BEGIN OPEN emp_cur; FETCH emp_cur BULK COLLECT INTO v_tab; CLOSE emp_cur; FOR i IN v_tab.FIRST..v_tab.LAST LOOP DBMS_OUTPUT.PUT_LINE(v_tab(i).last_name); END LOOP; END; / |
DROP TABLE hr.emp PURGE; CREATE TABLE hr.emp AS SELECT * FROM hr.employees; BEGIN --PL/SQL엔진 DELETE FROM hr.emp WHERE department_id = 10; -- SQL엔진 DELETE FROM hr.emp WHERE department_id = 20; -- SQL엔진 DELETE FROM hr.emp WHERE department_id = 30; -- SQL엔진 END; / 실행계획 상수값고정 -> 쉐어링 불가 ROLLBACK; |
↓ |
DECLARE TYPE nullist IS TABLE OF number; v_num nullist := nullist(10,20,30); BEGIN DELETE FROM hr.emp WHERE department_id = v_num(1); DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||' rows deleted.'); DELETE FROM hr.emp WHERE department_id = v_num(2); DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||' rows deleted.'); DELETE FROM hr.emp WHERE department_id = v_num(3); DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||' rows deleted.'); END; / > 변수처리 -> 실행계획 쉐어링 가능. 단, 값의 분포도에 따라 쉐어링 여부 상이함. ROLLBACK; |
↓ |
DECLARE TYPE nullist IS TABLE OF number; v_num nullist := nullist(10,20,30); BEGIN --PL/SQL엔진 FOR i IN v_num.FIRST..v_num.LAST LOOP --PL/SQL엔진 DELETE FROM hr.emp WHERE department_id = v_num(i); -- SQL엔진 DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||' rows deleted.'); END LOOP; END; / > 문맥전환 3번 발생 -> 반복문 내 SQL문이 잦을수록 성능 저하 ROLLBACK; |
↓ |
# FORALL 문 : PL/SQL 엔진이 대량으로 바인드해서 SQL 엔진에 전달하는 문. DECLARE TYPE nullist IS TABLE OF number; v_num nullist := nullist(10,20,30); BEGIN --PL/SQL엔진 FORALL i IN v_num.FIRST..v_num.LAST DELETE FROM hr.emp WHERE department_id = v_num(i); -- DML문만 가능 FOR i IN v_num.FIRST..v_num.LAST LOOP DBMS_OUTPUT.PUT_LINE(SQL%BULK_ROWCOUNT(i)||' rows deleted.'); END LOOP; END; / > 문맥전환 1번 ROLLBACK; |
DECLARE TYPE nullist IS TABLE OF number; v_num nullist := nullist(10,20,30,40,50); BEGIN FORALL i IN v_num.FIRST..v_num.LAST UPDATE hr.emp SET salary = salary * 1.1 WHERE department_id = v_num(i); FOR i IN v_num.FIRST..v_num.LAST LOOP DBMS_OUTPUT.PUT_LINE(SQL%BULK_ROWCOUNT(i)||' rows updated.'); END LOOP; END; / ROLLBACK; |
SQL%ROWCOUNT : 작업행 건수
SQL%BULK_ROWCOUNT(i) : FORALL문을 통해 영향을 받은 행의 건수
/* 테이블 생성*/
CREATE TABLE t(id int, name varchar2(128));
desc t
이름 널? 유형
---- -- -------------
ID NUMBER(38)
NAME VARCHAR2(128)
DECLARE TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; -- 10건 FOR i IN v_tab.FIRST..v_tab.LAST LOOP INSERT INTO t(id,name) VALUES (v_tab(i).id,v_tab(i).name); END LOOP; END; / >> 문맥전환 10건 발생 SELECT * FROM hr.t; rollback; |
↓ |
DECLARE TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; FORALL i IN v_tab.FIRST..v_tab.LAST INSERT INTO t VALUES v_tab(i); -- t 테이블 AND v_tab 배열 = 구조 동일 END; / >> 문맥전환 1건 발생 SELECT * FROM hr.t; rollback; |
INSERT INTO 테이블 VALUES 배열(i) : 테이블과 배열의 구조가 동일할 경우 사용가능.
DECLARE TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; v_tab.DELETE(2); v_tab.DELETE(4); v_tab.DELETE(6); --> 인덱스 번호에 갭 발생 FORALL i IN v_tab.FIRST..v_tab.LAST INSERT INTO t VALUES v_tab(i); END; / -- ORA-22160: element at index [2] does not exist >> FORALL 사용하여 작업하는데 중간에 갭 발생으로 인한 오류 |
↓ |
DECLARE /* 2차원 배열 선언 */ TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; dml_errors EXCEPTION; /* FORALL문 사용시 무조건 에러번호 -24381 만 사용 */ PRAGMA EXCEPTION_INIT(dml_errors,-24381); errors number; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; v_tab.DELETE(2); v_tab.DELETE(4); v_tab.DELETE(6); --> 인덱스 번호에 갭 발생 /* SAVE EXCEPTIONS : 오류는 세이브하고 다음 진행. FORALL문이 끝나면 EXCEPTION 실행 */ FORALL i IN v_tab.FIRST..v_tab.LAST SAVE EXCEPTIONS INSERT INTO t VALUES v_tab(i); EXCEPTION WHEN dml_errors THEN FOR i IN v_tab.first..v_tab.last LOOP /* SQL%BULK_ROWCOUNT(i) : i번째 항목 실행 시 영향을 받은 행의 건수 */ DBMS_OUTPUT.PUT_LINE('INSERT '||SQL%BULK_ROWCOUNT(i)||' row(s)'||' on iteration '||i); END LOOP; /* SQL%BULK_EXCEPTIONS.COUNT : 현재 발생한 오류 건수 */ errors := SQL%BULK_EXCEPTIONS.COUNT; DBMS_OUTPUT.PUT_LINE('Number of errors is '||errors); FOR i IN 1..errors LOOP /* SQL%BULK_EXCEPTIONS(i).ERROR_INDEX : 현재 발생한 오류의 인덱스 번호 */ DBMS_OUTPUT.PUT_LINE('Error index : '||SQL%BULK_EXCEPTIONS(i).ERROR_INDEX); /* SQL%BULK_EXCEPTIONS(i).ERROR_CODE : 현재 발생한 오류의 코드번호 */ DBMS_OUTPUT.PUT_LINE('실제 오류 정보 : '||SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); END LOOP; END; / ROLLBACK; |
- SAVE EXCEPTIONS : 오류는 세이브하고 다음 진행. FORALL문이 끝나면 EXCEPTION 실행
- SQL%BULK_EXCEPTIONS.COUNT : 현재 발생한 오류의 총 건수
- SQL%BULK_EXCEPTIONS(i).ERROR_INDEX : 현재 발생한 오류의 인덱스 번호
- SQL%BULK_EXCEPTIONS(i).ERROR_CODE : 현재 발생한 오류의 코드번호
DECLARE TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; TYPE t1_type IS TABLE OF number INDEX BY pls_integer; v1_tab t1_type; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; v1_tab(1) := 3; v1_tab(2) := 8; v1_tab(3) := 9; FOR i IN v1_tab.first..v1_tab.last LOOP DBMS_OUTPUT.PUT_LINE(v_tab(v1_tab(i)).id||' '||v_tab(v1_tab(i)).name); END LOOP; END; / |
↓ |
DECLARE TYPE t_type IS TABLE OF t%rowtype INDEX BY pls_integer; v_tab t_type; TYPE t1_type IS TABLE OF pls_integer INDEX BY pls_integer; v1_tab t1_type; BEGIN SELECT employee_id, last_name BULK COLLECT INTO v_tab FROM hr.employees WHERE rownum <= 10; v1_tab(1) := 3; v1_tab(2) := 8; v1_tab(3) := 9; --> 값을 인덱스 번호로 사용할꺼라 pls_integer 타입이여야 한다. FORALL i IN VALUES OF v1_tab -- 배열 안의 값 = i IN (3,8,9) = 방번호로 사용 INSERT INTO t VALUES v_tab(i); END; / SELECT * FROM hr.t; ROLLBACK; |
- FORALL i IN VALUES OF 배열 : 배열 안의 값을 i로 사용한다.
DECLARE v_id number := 100; v_sal_after number; BEGIN UPDATE hr.employees SET salary = salary * 1.1 WHERE employee_id = v_id; SELECT salary INTO v_sal_after FROM hr.employees WHERE employee_id = v_id; DBMS_OUTPUT.PUT_LINE(v_id||' '||v_sal_after); rollback; END; / >> UPDAT 작업 + 수정 후 확인을 위한 SELECT 작업 |
↓ |
/* 기존값 체크 */ select salary from hr.employees where employee_id = 100; DECLARE v_id number := 100; v_sal_after number; BEGIN UPDATE hr.employees SET salary = salary * 1.1 WHERE employee_id = v_id RETURNING salary INTO v_sal_after; -- 칼럼명 작성, 전체조회(*)는 불가 DBMS_OUTPUT.PUT_LINE(v_id||' '||v_sal_after); rollback; END; / >> UPDATE와 수정 후 조회를 한번에 해결 |
DECLARE v_id number := 100; TYPE record_type IS RECORD (name varchar2(30), sal number); v_rec record_type; BEGIN UPDATE hr.employees SET salary = salary * 1.1 WHERE employee_id = v_id RETURNING last_name, salary INTO v_rec; DBMS_OUTPUT.PUT_LINE(v_id||' '||v_rec.name||' '||v_rec.sal); rollback; END; / |
DECLARE v_id number := 20; TYPE record_type IS RECORD (name varchar2(30), sal number); TYPE tab_type IS TABLE OF record_type; v_tab tab_type; BEGIN UPDATE hr.employees SET salary = salary * 1.1 WHERE department_id = v_id -- 여러 작업행 발생 -> INTO 사용 불가 RETURNING last_name, salary BULK COLLECT INTO v_tab; FOR i IN v_tab.first..v_tab.last LOOP DBMS_OUTPUT.PUT_LINE(v_id||' '||v_tab(i).name||' '||v_tab(i).sal); END LOOP; rollback; END; / |
BEGIN
check_salary(:new.job_id, :new.salary);
END;
↓
CALL check_salary(:new.job_id, :new.salary) -- 세미콜론(;) 제외
PRAGMA AUTONOMOUS_TRANSACTION : 선언 시 독립 트랜잭션(Autonomous Transaction)이 수행된다.
DELETE : ROLLBACK 사용 가능. UNDO 공간 필요(휴지통)
TRUNCATE : ROLLBACK 불가.
DROP : Object 삭제
FORALL i IN FIRST..LAST
DML 문만 가능 ;
: PL/SQL 엔진이 대량으로 바인드해서 SQL 엔진에 전달하는 문.
FORALL i IN FIRST..LAST SAVE EXCEPTIONS
: 오류는 세이브하고 다음 진행. FORALL문이 끝나면 EXCEPTION 실행
PRAGMA EXCEPTION_INIT(에러명,-24381) : FORALL문 사용시 무조건 에러번호 -24381 만 사용
FORALL i IN VALUES OF 배열 : 배열 안의 값을 i로 사용
INSERT INTO 테이블 VALUES 배열(i) : 테이블과 배열의 구조가 동일할 경우 사용가능.
SELECT ~
INTO : 1개 행만 가능
BULK COLLECT INTO 배열변수
: 결과 집합을 PL/SQL엔진에 전달하기 전에 SQL엔진이 이를 대량으로 바인드할 수 있도록 하는 절이다.
UPDATE + SELECT, DELETE+SELECT, INSERT+SELECT
↓
DML작업 - 결과 1행
RETURNING 칼럼 INTO 변수;
: DML 작업 후 조회를 한번에 해결. 전체조회(*)는 불가
DML작업 - 결과 여러행
RETURNING 칼럼 BULK COLLECT INTO 배열;
값을 인덱스 번호로 사용 시 pls_integer 타입이여야 한다.
TYPE t1_type IS TABLE OF pls_integer INDEX BY pls_integer
SQL%ROWCOUNT : 작업행 건수
SQL%BULK_ROWCOUNT(i) :FORALL문을 통해 영향을 받은 행의 건수
SQL%BULK_EXCEPTIONS.COUNT : 현재 발생한 오류의 총 건수
SQL%BULK_EXCEPTIONS(i).ERROR_INDEX : 현재 발생한 오류의 인덱스 번호
SQL%BULK_EXCEPTIONS(i).ERROR_CODE : 현재 발생한 오류의 코드번호
'Oracle SQL' 카테고리의 다른 글
23 PL/SQL TRIGGER (0) | 2023.11.07 |
---|---|
22 PL/SQL PACKAGE, TRIGGER (1) | 2023.11.06 |
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 |