PL/SQL에서 사용할 수 있는 함수와 없는 함수 : decode를 제외하고 단일행함수는 모두 사용 가능, 그룹함수는 사용할 수 없음
PL/SQL 내에서 select .. into, insert, update, delete문 사용법
if문과 loop문 3가지
이중 루프문과 레이블 사용법
continue와 exit 사용법
조합변수중에 레코드 사용법
명시적 커서 사용법
💡
PL/SQL 변수의 종류 3가지 ?
스칼라 변수 : 단일값 만 저장
조합변수 : 여러개값을 저장 (레코드 변수, 컬렉션 )
바인드 변수 : 호스트 변수
sql은 똑같은데, 검색하는 where절의 값만 다를 경우에 값을 바인딩하기 위해 사용하는 변수 (바인딩 해오는 것)
select ename, sal, deptno
from emp
where deptno = :deptno;
=====================================
:(콜론)을 앞에 붙여 사용하여 바인드변수임을 알림
아래와 같이 값을 입력하면,
입력값에 해당하는 행들을 볼 수 있
커서 사용 이유 : 여러개의 행을 화면에 출력 하고 싶을 때 사용
1장. 테이블 형태의 변수를 생성하는 방법
💡
연관배열과 vs varray
공통점 :키와 값으로 이루어진 세로로된 형태
연관배열 : 값이 몇개가 들어와도 상관 없음
varray : 담아낼 데이터의 행의 개수를 처음부터 지정
052. 사용자 정의 레코드 변수 사용법 (오라클 교재 핵심 요약 ★ )
📖
💡
PL/SQL 변수의 종류 3가지 ?
스칼라 변수 : 단일값 만 저장
조합변수 : 여러개값을 저장 (레코드 변수, 컬렉션 )
바인드 변수 : 호스트 변수
💡 조합 변수의 종류 2가지 —(오라클 정규교재 핵심 요약! 이것만 잘 알기)
레코드 (값을 여러개 바인딩 할 수 있는 변수. 값을 가로로 담아냄):
사용자 정의 레코드 변수
%rowtype : 특정 테이블의 모든 유형의 datatype을 따르겠다
컬렉션 (세로로 데이터를 담아내는 변수 ) :
연관 배열
varray
중첩 테이블
▣ 사용자 정의 레코드 변수 사용법
예제 )
사원번호를 입력해서 프로시져를 수행하면 해당 사원의 이름과 월급과 직업이 출력되게 하시오 !
createor replace procedure pro1
( p_empno emp.empno%type )
is
type t_rec is record --레코드 타입 이름
( ename emp.ename%type,
sal emp.sal%type, -- 방 3개 만드는 것
job emp.job%type );
v_myrec t_rec ; --레코드 변수 이름 생성 및 타입 지정beginselect ename, sal, job into v_myrec --select into절을 사용해 값from emp --레코드변수에 값 바인딩where empno = p_empno;
dbms_output.put_line(chr(9));
dbms_output.put_line ( v_myrec.ename || chr(9) ||
v_myrec.sal || chr(9) ||
v_myrec.job ) ;
end;
/SQL>exec pro1(7788);
SCOTT 3000 ANALYST
--값이 출력이 안된다면, set serverout on 해주===========================================================
레코드변수이기 때문에 begin절에서 여러개의 컬럼을 하나의 레코드변수
v_myrec에 담을 수 있는 것
문제풀며 익히기
Q1.
이름을 입력해서 프로시져를 실행하면 해당 사원의 이름과 월급과 직업이
출력되게하는 프로시져를 생성하세요.
실행문 : exec pro_ename(’SCOTT’);
출력예시 : SCOTT 3000 ANALYST
createor replace procedure pro_ename
( p_ename emp.ename%type )
is
type t_rec is record
( ename emp.ename%type,
sal emp.sal%type,
job emp.job%type );
v_myrec t_rec ;
beginselect ename, sal, job into v_myrec
from emp
where ename = p_ename;
dbms_output.put_line(chr(9));
dbms_output.put_line ( v_myrec.ename || chr(9) ||
v_myrec.sal || chr(9) ||
v_myrec.job ) ;
end;
/
053. 조합변수 레코드 %rowtype 사용법
📖
조합변수 레코드 %rowtype 사용법
💡 조합 변수의 종류 2가지 —(오라클 정규교재 핵심 요약! 이것만 잘 알기)
레코드 (값을 여러개 바인딩 할 수 있는 변수. 값을 가로로 담아냄):
사용자 정의 레코드 변수
%rowtype : 특정 테이블의 모든 유형의 datatype을 따르겠다
컬렉션 (세로로 데이터를 담아내는 변수 ) :
연관 배열
varray
중첩 테이블
예제) — 비효율적으로 한 버전
사원번호를 입력하면 해당 사원의 모든 컬럼의 데이터가 출력되는 프로시져를 생성하세요.
createor replace procedure pro2
( p_empno emp.empno%type )
is
type t_rec is record -- 레코드 타입 생성
( empno emp.empno%type, --8개의 방을 가진 레코드 타입
ename emp.ename%type,
job emp.job%type,
mgr emp.mgr%type,
hiredate emp.hiredate%type,
sal emp.sal%type,
comm emp.comm%type,
deptno emp.deptno%type );
v_myrec t_rec; --레코드 변수 생성 및 타입 지정beginselect*into v_myrec
from emp
where empno = p_empno;
dbms_output.put_line(chr(9));
dbms_output.put_line ( v_myrec.empno ||chr(9)||--chr(9)공백 또는 탭
v_myrec.ename ||chr(9)||
v_myrec.job ||chr(9)||
v_myrec.mgr ||chr(9)||
v_myrec.hiredate ||chr(9)||
v_myrec.sal ||chr(9)||
v_myrec.comm ||chr(9)||
v_myrec.deptno );
end;
/SQL>exec pro2(7788);
========================================7788인 사원의 모든 컬럼의 데이터를 한번에 담아올 수 있음
한 행씩 담는게 아니라 한번에 담음
즉 asteroid를 사용할 수 있음
문제풀며 익히기
Q1.
비효율적으로 작성한 예제 1번 코드를 %rowtype을 사용하여 효율적이고 간단하게 만드시오.
createor replace procedure pro2
( p_empno emp.empno%type )
is
v_myrec emp%rowtype; --이 한줄로 끝남. emp테이블의 모든 rowtype을 따르겠다-- 따라서 위와 똑같이 8개의 방이 만들어짐-- 각 방의 이름은 컬럼 이름beginselect*into v_myrec
from emp
where empno = p_empno;
dbms_output.put_line(chr(9));
dbms_output.put_line ( v_myrec.empno ||chr(9)||
v_myrec.ename ||chr(9)||
v_myrec.job ||chr(9)||
v_myrec.mgr ||chr(9)||
v_myrec.hiredate ||chr(9)||
v_myrec.sal ||chr(9)||
v_myrec.comm ||chr(9)||
v_myrec.deptno );
end;
/
Q2.
부서번호를 입력하고 프로시져를 수행하면 dept 테이블의 해당 부서번호의
모든 컬럼을 출력하는 프로시져를 생성하시오 !
실행문 : SQL> exec pro_dept(10);
출력예시
10 ACCOUNTING NEW YORK
createor replace procedure pro_dept
( p_deptno dept.deptno%type )
is
v_myrec dept%rowtype;
beginselect*into v_myrec
from dept
where deptno = p_deptno;
dbms_output.put_line(chr(9));
dbms_output.put_line ( v_myrec.deptno ||chr(9)||
v_myrec.dname ||chr(9)||
v_myrec.loc );
end;
/
054. 조합변수 컬렉션 연관 배열 사용법(튜닝시 유용 ★)
📖
조합변수 컬렉션 연관 배열 사용법
💡 조합 변수의 종류 2가지 —(오라클 정규교재 핵심 요약! 이것만 잘 알기)
레코드 (값을 여러개 바인딩 할 수 있는 변수. 값을 가로로 담아냄):
사용자 정의 레코드 변수
%rowtype : 특정 테이블의 모든 유형의 datatype을 따르겠다
컬렉션 (세로로 데이터를 담아내는 변수 ) :
연관 배열 : 키와 값으로 구성된 컬렉션입니다.
key
value
1
7788
2
1566
…
…
varray
중첩 테이블
연관배열을 사용한 예제)
CREATEOR REPLACE procedure pro1
--입력 매개변수는 따로 작성하지 않음is
TYPE emp_arr_typ --반드시 작성해줘야하는 문법들ISTABLEOF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
emp_arr emp_arr_typ; -- 연관배열 이름(생성) 및 타입 지정BEGINselect ename BULK COLLECTINTO emp_arr
from emp;
FOR j IN1..emp_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j));
END LOOP;
END;
/SQL>exec pro1;
KING
BLAKE
CLARK
JONES
MARTIN
ALLEN
TURNER
JAMES
WARD
FORD
==========================================================< 연관 배열을 선언하기 위해 반드시 써줘야하는 문법 >
TYPE emp_arr_typ -- 타입명칭ISTABLEOF VARCHAR2(10) --value에 해당하는 부분에 어떤 데어터타입을 가진 값을 가질건지 정해놓는
INDEX BY BINARY_INTEGER; -- 연관배열
emp_arr emp_arr_typ;
---------------------------------------------------------< BULK COLLECTINTO 문 >!!SQL튜닝할 때 많이 사용!!select ename BULK COLLECTINTO emp_arr
from emp;
emp테이블의 14개의 이름(ename)을 emp_arr의 value값에
한번에 넣을 수 있는 옵션
-----------------------------------------------------------FOR j IN1..emp_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j)); --[연관배열(key값)]END LOOP;
연관배열 이름.COUNT하면 그에 해당하는 수가 나옴 (여기선 14행)
그래서 그만큼 FOR LOOP함
j 는 key값이 됨
CREATEOR REPLACE procedure pro1
is
TYPE emp_arr_typ
ISTABLEOF NUMBER(10)
INDEX BY BINARY_INTEGER;
emp_arr emp_arr_typ;
BEGINselect empno BULK COLLECTINTO emp_arr
from emp;
FOR j IN1..emp_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j));
END LOOP;
END;
/
055. 조합변수 컬렉션 varray 사용법(튜닝시 유용 ★)
📖
💡 조합 변수의 종류 2가지 —(오라클 정규교재 핵심 요약! 이것만 잘 알기)
레코드 (값을 여러개 바인딩 할 수 있는 변수. 값을 가로로 담아냄):
사용자 정의 레코드 변수
%rowtype : 특정 테이블의 모든 유형의 datatype을 따르겠다
컬렉션 (세로로 데이터를 담아내는 변수 ) :
연관 배열 : 키와 값으로 구성된 컬렉션입니다.
key
value
1
7788
2
1566
…
…
varray (연관배열보다 성능이 좋아지는 경우가 많음)
연관 배열 처럼 키와 값으로 이루어진 컬렉션 변수 입니다.
varray 는 담아낼 데이터의 행의 갯수를 처음부터 지정해야합니다.
그리고 변수를 반드시 초기화 해야합니다
(값을 할댕할 수 있도록 깨끗하게 비워놓는)
중첩 테이블
예제)
CREATEOR REPLACE PROCEDURE pro1
IS
TYPE emp_arr_typ IS VARRAY(20) OF VARCHAR2(10);
emp_arr emp_arr_typ := emp_arr_typ(); -- 컬렉션 초기화BEGINselect ename BULK COLLECTINTO emp_arr
from emp;
FOR j IN1..emp_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j));
END LOOP;
END;
/===============================================================
#연관배열
TYPE emp_arr_typ ISTABLEOF VARCHAR2(10) INDEX BY BINARY_INTEGER;
#VARRAY
TYPE emp_arr_typ IS VARRAY(20) OF VARCHAR2(10);
emp_arr emp_arr_typ := emp_arr_typ();
- VARRAY(20) : 20개의 행을 사용하겠다고 지정하는 것
- VARCHAR2(10) : 값의 데이터타입과 길이 지정
- 초기화 : 할당연산자(:=) [VARRAY 타입명]();
CREATEOR REPLACE PROCEDURE pro1
IS
TYPE emp_arr_typ IS VARRAY(20) OF NUMBER(10);
emp_arr emp_arr_typ := emp_arr_typ();
BEGINselect empno BULK COLLECTINTO emp_arr
from emp;
FOR j IN1..emp_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(emp_arr(j));
END LOOP;
END;
/
056. 조합변수 컬렉션 중첩 테이블 사용법(튜닝시 유용 ★)
📖
조합변수 컬렉션 중첩 테이블 사용법
💡 조합 변수의 종류 2가지 —(오라클 정규교재 핵심 요약! 이것만 잘 알기)
레코드 (값을 여러개 바인딩 할 수 있는 변수. 값을 가로로 담아냄):
사용자 정의 레코드 변수
%rowtype : 특정 테이블의 모든 유형의 datatype을 따르겠다
컬렉션 (세로로 데이터를 담아내는 변수 ) :
연관 배열 : 키와 값으로 구성된 컬렉션입니다.
key
value
1
7788
2
1566
…
…
varray (연관배열보다 성능이 좋아지는 경우가 많음)
연관 배열 처럼 키와 값으로 이루어진 컬렉션 변수 입니다.
varray 는 담아낼 데이터의 행의 개수를 처음부터 지정해야합니다.
그리고 변수를 반드시 초기화 해야합니다
(값을 할댕할 수 있도록 깨끗하게 비워놓는)
중첩 테이블 (컬렉션 중 가장 많이 사용)
하나 이상의 값을 저장할 수 있는 컬렉션 데이터 유형 중 하나입니다.
연관배열,varray와 다른점은 중첩테이블을
테이블 전체의 데이터를 담을 수 있는 변수라는 것입니다.
중첩 테이블 변수는 1차원 배열 처럼 작동하며,
각 요소는 동일한 데이터 유형을 가질 수 있습니다.
테이블 형태의 변수 생성됨
KEY
DEPTNO
DNAME
LOC
1
ㅤ
ㅤ
ㅤ
2
ㅤ
ㅤ
ㅤ
3
ㅤ
ㅤ
ㅤ
…
ㅤ
ㅤ
ㅤ
KEY는 특정 레이블과 연결시키기 위해 가지고 있는 것
위와 같이 값이 비어있음!!
예제로 보는 중첩 테이블 문법)
createor replace procedure pro1
is
TYPE tnt_dept ISTABLEOF dept%ROWTYPE;--dept테이블 전체의 데이터 타입 따
v_dept tnt_dept;
BEGINselect* BULK COLLECTINTO v_dept --값이 한번에 들어감from dept;
FOR i IN v_dept.FIRST .. v_dept.LAST LOOP
DBMS_OUTPUT.PUT_LINE (v_dept(i).deptno ||': '|| v_dept(i).loc);
END LOOP;
END;
/SQL>exec pro1;
10: NEW YORK
20: DALLAS
30: CHICAGO
40: BOSTON
=========================================================
TYPE tnt_dept ISTABLEOF dept%ROWTYPE; --아래의 테이블 형태의 변수 생성됨
v_dept tnt_dept; ---아래의 테이블의 이름 지정-------------------------------------<FOR LOOP문 설명 >FOR i IN v_dept.FIRST .. v_dept.LAST LOOP
DBMS_OUTPUT.PUT_LINE (v_dept(i).deptno ||': '|| v_dept(i).loc);
END LOOP;
** v_dept.FIRST .. v_dept.LAST **-FIRST : 첫번째 키번호 즉, KEY 1번
-LAST : 마지막 키번호 즉, 여기선 KEY 4번
V_DEPT
KEY
DEPTNO
DNAME
LOC
1
ㅤ
ㅤ
ㅤ
2
ㅤ
ㅤ
ㅤ
3
ㅤ
ㅤ
ㅤ
4
ㅤ
ㅤ
ㅤ
위의 테이블에 BULK COLLECT INTO를 사용해 한번에 값을 집어넣음
문제풀며 익히기
Q1.
emp 테이블에서 이름과 월급을 다음과 같이 출력하는 중첩 테이블을 사용한
프로시져를 생성하세요
createor replace procedure pro1
is
TYPE tnt_emp ISTABLEOF emp%ROWTYPE;
v_emp tnt_emp;
BEGINselect* BULK COLLECTINTO v_emp
from emp;
FOR i IN v_emp.FIRST .. v_emp.LAST LOOP
DBMS_OUTPUT.PUT_LINE (v_emp(i).ename ||': '|| v_emp(i).sal);
END LOOP;
END;
/
2장. PL/SQL 의 성능을 높이는 방법 14가지
057. PL/SQL 튜닝1 (update 문장 작성시 Returning 절 사용하는 방법)
📖
update 문장 작성시 Returning 절 사용하는 방법
RETURNING INTO절
▣ 튜닝전 PL/SQL :
UPDATE emp
SET sal = sal *2WHERE empno = v_empno;
SELECT sal INTO v_sal
FROM emp
WHERE empno = v_empno;
--사원번호가 7788이라면, 사원번호가 7788인 사원의 월급을 2대로 갱신.--2배로 갱신된 월급을 V_SAL에 담음--만약 EMP테이블이 1억건이 넘는 대용량 TABLE이라면, --UPDATE 수행 후 사원번호가 7788인 사원의 결과를 SELECT하려면 오래걸림.--또 인덱스도 없다면, 테이블을 FULL TABLE 스캔 하면서 SELECT하고 INTO하면--오래걸림========================================================================
▣ 튜닝후 PL/SQL :
UPDATE emp
SET sal = sal *2WHERE empno = v_empno
RETURNING sal INTO v_sal; --바로 V_SAL에 갱신된 월급 담음--EMP 테이블 업데이트. 사원번호가 7788인 사원의 월급 2배로 갱신--RETURNING INTO절을 사용하여 갱신된 월급을 바로 V_SAL에 담는 것.--즉, update된 것만 확
예제 )
사원번호를 입력하면 해당 사원의 월급을 2배로 갱신하는 프로시저를 생성하는데 갱신 후의 월급이 아래와 같이 출력되게 하시오 ! (Returning 절 활용)
/*
SQL> execute pro238(7788);
수정된 월급은 6000
*/1. RETURNING 절 사용전:
SQL>set timing on--SQL 수행된 결과 시간 확인 명령SQL>CREATEOR REPLACE PROCEDURE pro238
( p_empno emp.empno%TYPE ) --입력매개변수 생성IS
v_sal emp.sal%TYPE; --스칼라변수 v_sal 생성BEGINUPDATE emp
SET sal = sal *2--월급을 2배로 갱신하는 update문WHERE empno = p_empno;
select sal into v_sal
from emp --2배로 갱신된 월급 집어넣기where empno = p_empno;
-- dbms_lock.sleep(5); -- 위의 select 문이 5초 정도 걸리는 SQL임을 나타내기 위해서-- 없어도됨. 선생님은 빼버리심
DBMS_OUTPUT.PUT_LINE ('수정된 월급은 : '|| v_sal );
END;
/SQL>execute pro238(7788);
수정된 월급은 : 6000SQL>@demo.sql2. RETURNING 절 사용후:
CREATEOR REPLACE PROCEDURE pro238
( p_empno emp.empno%TYPE )
IS
v_sal emp.sal%TYPE;
BEGINUPDATE emp
SET sal = sal *2WHERE empno = p_empno
RETURNING sal INTO v_sal;
DBMS_OUTPUT.PUT_LINE ('수정된 월급은 : '|| v_sal );
END;
/SQL>exec pro238(7788);
수정된 월급은 : 6000
emp 와 dept 테이블을 초기화하세요!SQL>@demo.sql
문제풀며 익히기
Q1.
위의 예제에 프로시져를 수행할 때 다음과 같이 몇개의 행이 갱신되었다는 메세지도
같이 출력되게 코드를 추가하세요
출력예시
SQL> exec pro238(7566);
수정된 월급은 : 5950
1개의 행이 갱신되었습니다.
update문장 다음
암시적 커서 : SQLD%ROWCOUNT를 실행하면,
몇개의 행이 업데이트 되었는지 출력 가능
UPDATE된 행의 개수
A.
CREATEOR REPLACE PROCEDURE pro238
( p_empno emp.empno%TYPE )
IS
v_sal emp.sal%TYPE;
BEGINUPDATE emp
SET sal = sal *2WHERE empno = p_empno
RETURNING sal INTO v_sal;
DBMS_OUTPUT.PUT_LINE ('수정된 월급은 : '|| v_sal );
DBMS_OUTPUT.PUT_LINE (SQL%rowcount ||' 개의 행이 갱신되었습니다.');
END;
/------------------------------------------------------------------SQL>exec pro238(7566);
수정된 월급은 : 59501 개의 행이 갱신되었습니다.
Q2.
이름을 입력해서 프로시져를 실행하면, 해당 사원의 커미션이 8000으로 갱신되는 프로시져를 생성하는데, 수정된 커미션이 화면에 출력되게 하고,
1 건의 행이 잘 갱신되었다고 메세지도 출력되게 하시오.
실행문 : exec update_comm(’SCOTT’);
출력예시 : 커미션이 8000으로 잘 갱신되었습니다.
1건의 행이 잘 갱신되었습니다
CREATEOR REPLACE PROCEDURE update_comm
( p_empno emp.empno%TYPE )
IS
v_sal emp.sal%TYPE;
BEGINUPDATE emp
SET sal = sal *2WHERE empno = p_empno
RETURNING sal INTO v_sal;
DBMS_OUTPUT.PUT_LINE ('수정된 월급은 : '|| v_sal );
DBMS_OUTPUT.PUT_LINE (SQL%rowcount ||' 개의 행이 갱신되었습니다.');
END;
//* 결과
SQL> EXEC UPDATE_COMM('SCOTT');
커미션이 8000으로 잘 갱신되었습니다
1 건의 행이 갱신되었습니다.
*/
058. PL/SQL 튜닝2 (update 문장 작성시 returning bulk collect into 사용법)
📖
update 문장 작성시 returning bulk collect into 사용법
여러건을 한번에 업데이트 하고 수정된 결과를 볼 것
수정된 결과도 여러개이기 때문에
그냥 RETURNING INTO절이 아니라
RETURNING BULK COLLECT INTO 를 사용해야함
예제)
CREATEOR REPLACE PROCEDURE pro239
( p_deptno emp.deptno%TYPE )
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type);
--레코드 변수의 타입을 만드는 것. ename과 sal을 받는 2개의 방 만들어짐
type tnt_emp istableof trd_emp;
--중첩테이블 타입을 사용해서 중첩테이블을 만들겠다는 것--그 말은 즉, 레코드 변수가 세로로 늘어난다는 것.
v_emp tnt_emp; -- 결과적으로 중첩테이블 컬렉션 변수를 만들었고 그 변수 명칭 생성BEGINUPDATE emp
SET sal = sal *2WHERE deptno = p_deptno
RETURNING ename, sal BULK COLLECTINTO v_emp;
-- 중첩테이블 컬렉션 변수에 ENAME과 갱신된 SAL을 통째로 넣음for i in1 .. v_emp.COUNT loop --1부터 V_EMP의 데이터가 있는 곳 까지 --즉 여기선 3까지-- i는 key
DBMS_OUTPUT.PUT_LINE (v_emp(i).ename || chr(9) || v_emp(i).sal );
end loop;
END;
/SQL>@demo.sqlSQL>exec pro239(10);
KING 10000
CLARK 4900
MILLER 2600
PL/SQL 처리가 정상적으로 완료되었습니다.
==========================================================
아래와 같이 생긴 중첩 테이블 컬렉션 변수를 만들려면
레코드 타입을 먼저 만들어 주고
그 후 중첩 테이블 타입을 만들어줘야함
V_EMP
KEY
ENAME
SAL * 2
1
KING
10000
2
CLARK
49000
3
MIILAR
2600
문제풀며 익히기
Q1.
위의 예제를 수정해서 다음과 같이 몇개의 행이 갱신되었습니다. 라는 메세지가
출력되게 하세요
출력예시
SQL> exec pro239(20);
JONES 5950
FORD 6000
SMITH 1600
SCOTT 6000
ADAMS 2200
5 개의 행이 갱신되었습니다.
CREATEOR REPLACE PROCEDURE pro239
( p_deptno emp.deptno%TYPE )
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
BEGINUPDATE emp
SET sal = sal *2WHERE deptno = p_deptno
RETURNING ename, sal BULK COLLECTINTO v_emp;
for i in1 .. v_emp.COUNT loop
DBMS_OUTPUT.PUT_LINE (v_emp(i).ename || chr(9) || v_emp(i).sal );
end loop;
DBMS_OUTPUT.PUT_LINE (SQL%rowcount ||' 개의 행이 갱신되었습니다.');
END;
/
059. PL/SQL 튜닝3 (insert 문장 작성시 Returning 절 사용하는 방법)
📖
insert 문장 작성시 Returning 절 사용하는 방법
INSERT INTO [테이블명]([컬럼명])
VALUES ([들어갈 값 혹은 컬럼명])
RETURNING [컬럼명] INTO [변수명]
사원번호, 이름, 월급을 입력해서 프로시저를 실행하면 사원 테이블에
해당 사원의 사원번호, 이름, 월급과 보너스가 입력되게 하는 프로시저를 만드는데, 보너스는 입력하는 월급의 20% 로 입력되게 하시오
CREATEOR REPLACE PROCEDURE pro1
( p_empno IN emp.empno%TYPE, --입력매개변수 3개 만들기
p_ename IN emp.ename%TYPE,
p_sal IN emp.sal%TYPE )
--in은 input의 약자이며, 아무것도 안쓰면 기본값이 in (out은 출력매개변수)--즉, 입력매개변수를 나타내는 키워드ISBEGININSERT INTO emp ( empno, ename, sal, bonus )
VALUES ( p_empno, p_ename, p_sal, p_sal *0.2 );
END;
/SQL>execute pro1(8897, 'JANE', 3000);
--3개의 입력 매개변수를 만들어 놨기 때문에, 3개의 값 넣어줘야함
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>SELECT ename, sal, bonus
FROM emp;
예제3)
위의 프로시저를 수정해서 프로시저를 실행하고 나면
바로 입력한 사원의 이름과 보너스가 출력되게 하시오.
출력예시
SQL> execute pro1(8898, 'JACK', 3000 );
입력한 사원 JACK 의 보너스는 600 입니다.
1. returning 절을 사용 안했을 때:
CREATEOR REPLACE PROCEDURE pro1
( p_empno IN emp.empno%TYPE,
p_ename IN emp.ename%TYPE,
p_sal IN emp.sal%TYPE )
IS
v_bonus emp.bonus%TYPE;
BEGININSERT INTO emp ( empno, ename, sal, bonus )
VALUES ( p_empno, p_ename, p_sal, p_sal *0.2 );
select bonus into v_bonus
from emp
where empno = p_empno;
--select into절을 한번 선언해줘야함.--emp테이블에 데이터가 많고 인덱스도 없다면, 어마어마하게 시간소모
DBMS_OUTPUT.PUT_LINE ( '입사한 사원 '|| p_ename ||' 의 보너스는 '|| v_bonus
||' 입니다.' );
END;
/SQL>execute pro1(8899, 'MILLER', 5000);
입사한 사원 MILLER 의 보너스는 1000 입니다.
2. returning 절을 사용 했을 때: (튜닝 후)
CREATEOR REPLACE PROCEDURE pro1
( p_empno IN emp.empno%TYPE,
p_ename IN emp.ename%TYPE,
p_sal IN emp.sal%TYPE )
IS
v_bonus emp.bonus%TYPE;
BEGININSERT INTO emp ( empno, ename, sal, bonus )
VALUES ( p_empno, p_ename, p_sal, p_sal *0.2 )
RETURNING bonus INTO v_bonus;
--INSERT 문장에서 만들어진 보너스가 바로 V_BONUS에 만들어짐
DBMS_OUTPUT.PUT_LINE ( '입사한 사원 '|| p_ename ||' 의 보너스는 '|| v_bonus
||' 입니다.' );
END;
/SQL>execute pro1(8899, 'MILLER', 5000);
입사한 사원 MILLER 의 보너스는 1000 입니다.
문제풀며 익히기
Q1.
부서(dept) 테이블에 데이터를 입력하는 프로시져를 생성하는데 프로시져를
실행하면 입력되는 데이터가 바로 출력되게하시오.
출력예시
SQL> exec pro1(50,'hr','seoul');
50 hr seoul
CREATEOR REPLACE PROCEDURE pro1
( p_deptno IN dept.deptno%TYPE,
p_dname IN dept.dname%TYPE,
p_loc IN dept.loc%TYPE )
IS
v_deptno dept.deptno%TYPE;
v_dname dept.dname%TYPE;
v_loc dept.loc%type;
--출력하려면 변수가 있어야함BEGININSERT INTO dept ( deptno, dname, loc )
VALUES ( p_deptno, p_dname, p_loc )
RETURNING deptno, dname, loc INTO v_deptno, v_dname, v_loc;
DBMS_OUTPUT.PUT_LINE ( p_deptno || chr(9) || v_dname || chr(9) || v_loc );
END;
/SQL>exec pro1(50, 'hr', 'seoul');
50 hr seoul
PL/SQL 처리가 정상적으로 완료되었습니다.
060. PL/SQL 튜닝4 (insert 문장 작성시 returning bulk collect into 절 사용하는 방법)
📖
insert 문장 작성시 returning bulk collect into 절 사용하는 방법
아까 업데이트 문에서는 returning bulk collect into를 사용했는데, inser할때도 되는가? NO 안됨
returning into 절만 사용했을때 : 하나의 행의 값만 변수에 입력할 수 있습니다. (059에서 한것)
returning bulk collect into 절을 사용했을 때: 여러개의 행의 값들을 변수에 담을 수 있다 ? (UPDATE는 됐음 059.에서) —NO 안됨
예제)
1. emp 테이블의 구조를 담는 emp_t 테이블을 생성합니다.
SQL>create table emp_t asselect*from emp where1=2;
2. 부서번호를 입력해서 프로시져를 실행하면 해당 부서번호의 모든 사원들의 데이터가
emp 테이블에서 emp_t 테이블에 입력되게 프로시져를 생성하세요.
-- emp 테이블의 10번의 데이터가 emp_t에 insert될 것-- 이때 서브쿼리를 사용해서 아래처럼 insert해야함/*
SQL> insert into emp_t
2 select *
3 from emp
4 where deptno = 10;
3 행이 생성되었습니다.
이걸 프로시저 안에 넣을 것임
*/SQL>execute insert_emp_t(10);
답:
CREATEOR REPLACE PROCEDURE insert_emp_t
( p_deptno emp.deptno%type )
ISBEGININSERT INTO emp_t
select*from emp
where deptno = p_deptno;
END;
/SQL>execute insert_emp_t(10);
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>select*from emp_t;
SQL>deletefrom emp_t;
3.insert 문에 return bulk collectinto 절을 사용하여
입력된 데이터가 모두 출력되게하시오
답:
CREATEOR REPLACE PROCEDURE insert_emp_t
( p_deptno emp.deptno%type )
IS
type trd_emp istableof emp%rowtype;
type tnt_emp istableof trd_emp; --중첩 테이블 만드는 과정
v_emp tnt_emp;
--emp테이블과 동일한 형태의 테이블 형태의 변수가 만들어진 것BEGININSERT INTO emp_t
select*from emp
where deptno = p_deptno
return empno,ename,job,mgr, hiredate, sal, comm, deptno
return bulk collectinto v_emp;
END;
/
경고: 컴파일 오류와 함께 프로시저가 생성되었습니다.
SQL>show err
PROCEDURE INSERT_EMP_T에 대한 오류:
LINE/COL ERROR
-------- -----------------------------------------------------------------8/9 PL/SQL: SQL Statement ignored
12/11 PL/SQL: ORA-00933: SQL 명령어가 올바르게 종료되지 않았습니다
※ 서브쿼리를 사용한 insert 문은 returing bulk collectinto 에 사용불가
문제풀며 익히기
Q1.
061. PL/SQL 튜닝5 (delete 문장 작성시 returning 절 사용하는 방법)
📖
delete 문장 작성시 returning 절 사용하는 방법
예제)
사원번호를 입력하여 프로시져를 실행하면 해당 사원번호의 데이터가 사원 테이블에서 지워지는 프로시져를 생성하는데 프로시져를 실행하면 다음과 같이 지워지는 사원의 데이터가 출력되게하시오
(SELECT INTO는 불가능하고, RETURNING INTO로만 가능)
SQL>@demo.sqlSQL>exec delete_emp(7788);
7788 SCOTT
답:
CREATEOR REPLACE PROCEDURE delete_emp
( p_empno emp.empno%TYPE )
IS
v_empno emp.empno%type;
v_ename emp.ename%type;
BEGINdeletefrom emp
WHERE empno = p_empno
RETURNING empno, ename INTO v_empno, v_ename;
DBMS_OUTPUT.put_line ( v_empno ||chr(9) || v_ename );
END;
/SQL>set serveroutput onSQL>exec delete_emp(7788);
7788 SCOTT
SQL>@demo.sql================================================
사원번호 7788이 들어왔다면,
7788의 행이 즉, 레코드 전체가 지워짐.
RETURNING empno, ename INTO v_empno, v_ename;
지운 데이터를 V_EMPNO, V_ENAME에 넣어서 볼 수 있게하는
SELECTINTO는 지운 데이터를 볼 수 없기 때문에 불가
문제풀며 익히기
Q1.
이름을 입력해서 프로시져를 실행하면, 해당 사원의 데이터가 지워지게 위의 예제를 수정하시오.
062. PL/SQL 튜닝6 (delete 문장 작성시 returning bulk collect into 절 사용하는 방법)
📖
delete 문장 작성시 returning bulk collect into 절 사용하는 방법
그냥 returning절만 사용하면 한건의 행이 지워진 것에 대해서만 가능한데,
returning bulk collect into 를 사용하면 여러건이 가능
예제)
returning 절을 사용하여 다음과 같이 삭제할 부서번호를 입력하여 프로시져를 수행하면 다음과 같이 지워진 부서번호의 사원의 사원이름과 부서번 출력되게하시오
SQL>@demo.sqlSQL>exec delete_deptno(10);
KING 10
CLARK 10
MILLER 10
답:
CREATEOR REPLACE PROCEDURE delete_deptno
( p_deptno emp.deptno%TYPE ) --입력매개변수 p_deptnoIS
type trd_emp is record(ename emp.ename%type, deptno emp.deptno%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
--이름과 부서번호를 담는 중첩 테이블 변수를 만드는 것BEGINdeletefrom emp
WHERE deptno = p_deptno
RETURNING ename,deptno BULK COLLECTINTO v_emp;
--V_EMP에 ENAME,DEPTNO를 한번에 담음 FOR i IN1 .. v_emp.COUNT LOOP --즉, 행이 3개이므로 3까지
DBMS_OUTPUT.put_line (v_emp(i).ename ||chr(9) || v_emp(i).deptno );
END LOOP;
END;
/SQL>exec delete_deptno(10);
KING 10
CLARK 10
MILLER 10SQL>select*2from emp
3where deptno =10;
선택된 레코드가 없습니다.
SQL>@demo.sql
V_EMP
KEY
ENAME
DEPTNO
1
KING
10
2
CLARK
10
3
MILLER
10
문제풀며 익히기
Q1.
직업을 입력하면 해당 직업인 사원들이 emp 테이블에서 삭제되는 프로시져를 생성하는데 프로시져를 실행하면 삭제되는 직업의 사원들의 이름과 직업이 출력되게 하시오
출력예시
SQL> exec delete_job('SALESMAN');
MARTIN SALESMAN
ALLEN SALESMAN
TURNER SALESMAN
WARD SALESMAN
CREATEOR REPLACE PROCEDURE delete_job
( p_job emp.job%TYPE )
IS
type trd_emp is record(ename emp.ename%type, job emp.job%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
BEGINdeletefrom emp
WHERE job = p_job
RETURNING ename,job BULK COLLECTINTO v_emp;
FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).ename ||chr(9) || v_emp(i).job );
END LOOP;
END;
/
063. PL/SQL 튜닝7 (for loop문 vs forall 문 문법 비교)
📖
for loop문 vs forall 문 문법 비교
FOR LOOP vs FORALL
for loop문 : 대량의 데이터를 1건씩 처리한다.
forall 문 : 대량의 데이터를 한번에 처리한다.
FOR LOOP문 단점
FOR문은 데이터를 1건씩 처리하기 때문에 반복 수행시 CPU사용량이 증가함.
따라서 반복문을 최소화하면서 코드 짜는게 좋음
잘못하면 데이터베이스를 다운시킬 수 있을 정도로 FORLOOP문은 CPU부하가 엄청남
▣ for loop문과 forall loop문의 문법 비교
1. 관련 테이블 생성
droptable test1 purge;
droptable test2 purge;
create table test1 (c1 number);
create table test2 (c1 number);
2.for loop문을 사용한 프로시져 생성
(TEST1테이블에 숫자 1~100000까지 입력하는 프로시저)
SQL>createor replace procedure for_pro
isbeginfor i in1 .. 100000 loop
insert/*+ FOR */into test1
values (i);
end loop;
end;
/--V$SQL이라는 다이나믹 퍼포먼스 뷰에서 위의 INSERT문을 찾기 쉬우라고--/*+ FOR */ 라는 힌트(레이블)를 포함해서 넣은것--아무이름으로 만들어도 상관없음/*
SQL> EXEC FOR_PRO;
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> SELECT COUNT(*) FROM TEST1;
COUNT(*)
----------
100000 */3. forall loop문을 사용한 프로시져 생성
SQL>createor replace procedure for_all_pro
is
type tnt istableof pls_integer; --중첩테이블 타입 만듬
l_v1 tnt := tnt (); --tnt := tnt()를 사용해서 초기화beginfor i in1 .. 100000 loop --중첩 테이블에 100000까지 입력하는 작업
l_v1.extend; --extend할때마다 테이블 행이 하나씩 들어나는 것
l_v1(i) := i; --key가 i일대 value에 i넣는것end loop;
forall i in l_v1.first .. l_v1.last
insert/*+ FOR_ALL */into test2 --나중에 수행확인 해보려고 넣은 힌트values (l_v1(i)); --insert문장이 한번만 수행되면서 한번에 넣음end;
/4. 두 프로시져의 수행 속도 차이 확인
set timing onexec for_pro;
exec for_all_pro;
set timing off
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR */%';
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR_ALL */%';
=============================================================<FOR LOOP >
col sql_text for a20 --추력되는 컬럼의 길이를 줄여주는 것SELECT sql_id, sql_text, executions, program_line#
--SQL문의 ID, 찾고자하는 TEXT문(여기선 INSERT문), 총몇번 수행했는지 보여주는 것,--INSERT문장을 포함하는 프로그램 라인수를 보여줌FROM v$sql--DBA와 튜너들이 많이 사용하는 다이나믹 퍼포먼스 뷰--우리가 수행한 모든 SQL이 기록됨--컴퓨터 끄기 전까지 모든 내용 메모리에 남아있음WHERE sql_text LIKE'INSERT /*+ FOR */%';
--V$SQL에 기록된 수많은 내용 중에 INSERT문을 찾는데, LIKE를 걸어서--특별히 넣어뒀던 힌트를 넣으면 찾기 쉬움===============================================================< FORALL >
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR_ALL */%';
SQL>selectcount(*) from test2;
COUNT(*)
----------100000
for loop
forall
key
value
ㅤ
ㅤ
ㅤ
ㅤ
문제풀며 익히기
Q1.
test1 테이블과 test2테이블의 데이터를 truncate하시오
truncatetable test1;
truncatetable test2;
Q2.
아래의 select문장을 sqlplus에서 쉽게 돌릴 수 있도록 script로 저장하시오.
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR */%';
064. PL/SQL 튜닝8 (insert 문의 for loop문 vs forall loop 문의 수행속도 비교)
📖
▣ insert 문의 for loop문 vs forall loop 문의 수행속도 비교
for loop vs forall loop
for loop : 매번 반복 수행
forall loop : 1번만 수행 - 따라서 주로 forall이라고 부름(sql 문법에 loop도 안씀)
▣ insert 문의 for loop 문 vs forall loop문 실습
(test_original 테이블에 있는 20만건의 데이터를 다른 테이블로 이행해볼것)
1. 관련 테이블 생성
droptable test_original PURGE; --purge옵션 : 휴지통에 넣지 말고 드롭해라--휴지통도 공간을 차지하기 때문에 날려버리라는 것create table test_original
asselect empno, sal, deptno,
lpad(ename, 5, ename) as ename,
lpad(addr, 5, addr) as addr
from ( select level +10000000as empno,
mod(level,1000) +10000as sal,
mod(level,20) as deptno, --계층형 질의문을 이용해서
chr(97+mod(level,26)) as ename, --테스트 데이터 20만건을 만들었음
chr(65+mod(level,26)) as addr --테스트 데이터는 대용량이어야 하므로from dual --주로 계층형 질의문 사용connectby level <=200000
);
/* 조회
SQL> select count(*) from test_original;
COUNT(*)
----------
200000 */DROPTABLE test_backup PURGE;
CREATE TABLE test_backup ASSELECT*FROM test_original WHERE0=1;
-- test_original 테이블에서 구조만 가져와서 백업 테이블 만든 것/* 조회
SQL> select count(*) from test_backup;
COUNT(*)
----------
0 */2. forall 문을 사용하지 않은 프로시져 생성(즉, 20만번 insert될 것)
createor replace procedure no_forall_proc
is
TYPE tnt_test ISTABLEOF test_original%ROWTYPE; --중첩테이블 타입 생성
v_test tnt_test; --중첩테이블 변수 만든것. (쉽게 말해 테이블처럼 생긴 변수)BEGINSELECT* BULK COLLECTINTO v_test FROM test_original ORDERBY empno;
-- test_original에 있는 데이터를 bulk collect into를 사용해 한번에 들어감FOR i IN v_test.FIRST .. v_test.LAST LOOP
INSERT/*+ FOR */INTO test_backup VALUES v_test(i);
END LOOP;
-- V_TEST라는 중첩테이블 변수에 있는 KEY값을 이용해서 데이터를 -- TEST_BACKUP 테이블로 20만건 이행-- 나중에 찾기 편하기 위해 힌트 넣어COMMIT;
END;
/SQL>SQL>SET TIMING ONSQL>exec no_forall_proc;
PL/SQL 처리가 정상적으로 완료되었습니다.
경 과: 00:00:02.69-- pc 사양에 따라 다름
col sql_text for a20 --20줄로 제한해서 보여주는..? 찾아보SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR */%';
-- v$sql이라는 다이나믹 퍼포먼스 뷰. 오라클에서 수행한 모든 sql문을 볼 수 있음-- 따라서 힌트를 사용해서 찾는게 좋음
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------3pcuf8txcvfnz INSERT/*+ FOR */ I 20000011
NTO TEST_BACKUP VALU
ES (:B1 ,:B2 ,:B3 ,:
B4 ,:B5 )
KEY
EMPNO
SAL
DEPTNO
ENAME
ADDR
1
ㅤ
ㅤ
ㅤ
ㅤ
ㅤ
2
ㅤ
ㅤ
ㅤ
ㅤ
ㅤ
…
ㅤ
ㅤ
ㅤ
ㅤ
ㅤ
20만
ㅤ
ㅤ
ㅤ
ㅤ
ㅤ
5. forall 문을 사용한 프로시져 생성
createor replace procedure forall_proc
is
TYPE tnt_test ISTABLEOF test_original%ROWTYPE;
v_test tnt_test;
BEGINSELECT* BULK COLLECTINTO v_test
FROM test_original
ORDERBY empno;
FORALL i IN v_test.FIRST .. v_test.LAST --forall문은 뒤에 loop 안씀INSERT/*+ FOR_ALL */INTO test_backup VALUES v_test(i);
--딱 한번만 수행. for loop는 20만번 수행END;
/6. 수행 속도 비교
--test_backup 테이블을 drop 하고 다시 생성합니다.DROPTABLE test_backup PURGE;
CREATE TABLE test_backup ASSELECT*FROM test_original WHERE0=1;
SQL>exec forall_proc;
경 과: 00:00:00.66
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR_ALL */%';
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------
dsytrn9rcz35k INSERT/*+ FOR_ALL * 1 17
/ INTO TEST_BACKUP V
ALUES (:B1 ,:B2 ,:B3
,:B4 ,:B5 )
문제풀며 익히기
Q1.
emp 테이블의 데이터를 emp_test 테이블에 입력할 수 있게 emp테이블과 똑같은 구조로 emp_test테이블을 생성하시오.
FROALL문을 이용해서 EMP테이블의 모든 데이터를 EMP_TEST테이블에 한번에 입력하는 프로시저를 FORALL_EMP라는 이름으로 생성하시오.
createor replace procedure forall_emp
is
type tnt_test istableof emp%rowtype;
v_test tnt_test;
beginselect* bulk collectinto v_test
from emp
orderby empno;
forall i IN v_test.FIRST .. v_test.LAST
INSERT/* FOR_ALL */INTO emp_test values v_test(i);
end;
/
Q3.
위의 FORALL_EMP 프로시져를 실행했을 때, 그 안의 INSERT문이 딱 1번만 수행되었는지
V$SQL을 조회해서 확인하시오.
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR_ALL */%';
/*
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------
dsytrn9rcz35k INSERT /*+ FOR_ALL * 1 11
/ INTO TEST_BACKUP V
ALUES (:B1 ,:B2 ,:B3
,:B4 ,:B5 )
*/
Q4.
지금 만든 프로시져를 DROP하시오
DROPPROCEDURE FORALL_EMP;
Q5. (DBA만 할 수 있는 명령어. 가끔 TEST 할 때 튜닝을 위해서만 함 거의 할 일 없으)
V$SQL로 볼 수 있는 메모리의 내용을 모두 다 지우시오.
ALTERSYSTEM FLUSH SHARED_POOL;
065. PL/SQL 튜닝9 (update 문의 for loop문 vs forall 문의 수행속도 비교)
📖
▣update 문의 for loop문 vs forall 문의 수행속도 비교
1. 테스트할 테이블 생성 (20만건이 넘는 emp테이블 재생성)
SQL>droptable emp PURGE;
CREATE TABLE emp ASSELECT empno, sal, deptno,
LPAD(ename, 5, ename) AS ename,
LPAD(addr, 5, addr) AS addr
FROM (
SELECT LEVEL +10000000AS empno,
MOD(LEVEL, 1000) +10000AS sal,
CASEWHENMOD(LEVEL, 4) =0THEN10WHENMOD(LEVEL, 4) =1THEN20WHENMOD(LEVEL, 4) =2THEN30WHENMOD(LEVEL, 4) =3THEN40ENDAS deptno,
CHR(97+MOD(LEVEL, 26)) AS ename,
CHR(65+MOD(LEVEL, 26)) AS addr
FROM DUAL
CONNECTBY LEVEL <=200000
)
WHERE deptno IN (10, 20, 30, 40); ---부서번호 10,20,30,40select deptno, count(*)
from emp
groupby deptno;
/*
DEPTNO COUNT(*)
---------- ----------
20 50000
30 50000
40 50000
10 50000
데이터들이 5만건씩 균등하게 들어가있음 */2.for loop 문을 사용해서 update 를 수행하는 프로시져 생성하기
CREATEOR REPLACE PROCEDURE for_update_deptno
IS///* 두개의 중첩 테이블 변수 만들기 *///
TYPE trd_emp IS RECORD (deptno emp.deptno%TYPE, sal emp.sal%TYPE);
1) TYPE tnt_emp ISTABLEOF trd_emp;
v_emp tnt_emp;
-- v_emp라는 중첩테이블 변수 만든 것-- key, deptno, sal이 컬럼으로 들어감-- 레코드 타입을 생성하고, 중첩테이블 타입을 생성함 즉, 타입 2번 생성-- 값이 비워져있는 테이블변수형태2) TYPE tnt_deptno ISTABLEOF emp.deptno%TYPE;
v_deptno tnt_deptno := tnt_deptno(10, 20, 30, 40);
-- emp테이블의 부서번호만 담는 중첩테이블 생성-- key와 depno가 컬럼으로 들어감-- 1)중첩테이블과는 다르게 할당연산자로 중첩테이블의 값을 할당해줌-- 그말은 즉 초기화 해주고 값이 넣어져있는 중첩테이블 변수 만드는BEGINFOR i IN v_deptno.FIRST .. v_deptno.LAST LOOP
update/*+ FOR */ emp
set sal = sal *1.2WHERE deptno = v_deptno(i) --해당 key 의 value를 가져옴
RETURNING max(deptno), SUM(sal) BULK COLLECTINTO v_emp;
--부서번호를 v_emp에 담아줘야하는데, max를 쓰지 않으면 부서번호가 여러개가 되어--MAX를 사용하게 되는 것--갱신된 상태의 토탈월급으로 V_EMP라는 중첩테이블에 데이터를 넣고 싶은데--MAX를 사용하지 않으면,20만건의 부서번호가 넣어지게됨.--SELECT MAX(DEPTNO), SUM(SAL) FROM EMP GOURP BY DEPTNO;의 값을 한번에 V_EMP에--넣게 되는것 => 부서번호 10번의 MAX는 10, 20번의 MAX는 20이기 때문/* MAX(DEPTNO) SUM(SAL)
----------- ----------
20 524950000
30 525000000
40 525050000
10 524900000 */--MAX를 넣어주게 되어여 GROUP BY 한 결과처럼 부서번호가 4개가 되어 매칭시킴--즉, 각 부서번호에 각 부서의 토탈월급을 V_EMP에 업데이트 해주는 것FOR j IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line(v_emp(j).deptno || chr(9) || v_emp(j).sal);
END LOOP;
END LOOP; --이중루프구조로 사용하여 갱신할 때마다 매번 출력이 되게 함END; --튜닝 전 sql임.. 수업을 위해 만들다보니 이렇게 된 것/--즉 그냥 update문장이 4번 수행된다는 것을 보여주기 위함--부서번호 10일 때 전체 결과 출력~마지막 부서버호 40일 때 값을 우리에게 보여주는 SQL>set timing onSQL>exec for_update_deptno;
10629880000206299400003063000000040630060000
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'UPDATE /*+ FOR */%';
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------69jnr6bfugbyw UPDATE/*+ FOR_ALL * 4 12
/ EMP SET SAL = SAL
* 1.2 WHERE DEPTNO =
:B1 RETURNING MAX(D
EPTNO), SUM(SAL) INT
O :O0 ,:O1
//* 만약 EXCUTIONS 즉 수행이 4번되었다가 아니라
UPDATE를 많이 수행해버려서 잘못 나오면 아래와 같이 입력후 다시 하면 됨*/SQL>dropprocedure update_deptno
SQL>alterSYSTEM FLUSH SHARED_POOL;
3. 다시 emp 테이블을 재생성 합니다.
SQL>droptable emp PURGE;
CREATE TABLE emp ASSELECT empno, sal, deptno,
LPAD(ename, 5, ename) AS ename,
LPAD(addr, 5, addr) AS addr
FROM (
SELECT LEVEL +10000000AS empno,
MOD(LEVEL, 1000) +10000AS sal,
CASEWHENMOD(LEVEL, 4) =0THEN10WHENMOD(LEVEL, 4) =1THEN20WHENMOD(LEVEL, 4) =2THEN30WHENMOD(LEVEL, 4) =3THEN40ENDAS deptno,
CHR(97+MOD(LEVEL, 26)) AS ename,
CHR(65+MOD(LEVEL, 26)) AS addr
FROM DUAL
CONNECTBY LEVEL <=200000
)
WHERE deptno IN (10, 20, 30, 40);
select deptno, count(*)
from emp
groupby deptno;
DEPTNO COUNT(*)
---------- ----------20500003050000405000010500004. forall 문을 사용해서 update 하는 프로시져 생성하기
SQL>CREATEOR REPLACE PROCEDURE forall_update_deptno
IS
type trd_emp is record(deptno emp.deptno%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
type tnt_deptno istableof emp.deptno%type;
v_deptno tnt_deptno := tnt_deptno( 10, 20, 30, 40 );
BEGIN
FORALL i in v_deptno.first .. v_deptno.last
update/*+ FOR_ALL */ emp
set sal = sal *1.2WHERE deptno = v_deptno(i)
RETURNING max(deptno), sum(sal) BULK COLLECTINTO v_emp;
/* FORALL문은 FOR LOOP문과 다르게 업데이트가 한 번만 수행됨 */FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).deptno ||chr(9) || v_emp(i).sal );
END LOOP;
END;
/SQL>exec forall_update_deptno;
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'UPDATE /*+ FOR_ALL */%';
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------69jnr6bfugbyw UPDATE/*+ FOR_ALL * 1 12
/ EMP SET SAL = SAL
* 1.2 WHERE DEPTNO =
:B1 RETURNING MAX(D
EPTNO), SUM(SAL) INT
O :O0 ,:O1
UNDO도 많이 차지하기 때문에 경과시간이 크게 차이나지는 않음
문제풀며 익히기
Q1.
이번에는 위의 FORALL문 프로시져를 수정해서 부서번호별 토탈월급이 아니라
부서번호별 평균월급이 출력되게 하시오.(ROLLBACK후 수행)
SQL>ROLLBACK;
SQL>CREATEOR REPLACE PROCEDURE forall_update_deptno
IS
type trd_emp is record(deptno emp.deptno%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
type tnt_deptno istableof emp.deptno%type;
v_deptno tnt_deptno := tnt_deptno( 10, 20, 30, 40 );
BEGIN
FORALL i in v_deptno.first .. v_deptno.last
update/*+ FOR_ALL */ emp
set sal = sal *1.2WHERE deptno = v_deptno(i)
RETURNING max(deptno), AVG(sal) BULK COLLECTINTO v_emp;
FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).deptno ||chr(9) || v_emp(i).sal );
END LOOP;
END;
/
066. PL/SQL 튜닝10 (delete 문의 for loop문 vs forall 문의 수행횟수 비교)
📖
delete 문의 for loop문 vs forall 문의 수행횟수 비교
1. 테스트할 테이블 생성
SQL>droptable emp PURGE;
CREATE TABLE emp ASSELECT empno, sal, deptno,
LPAD(ename, 5, ename) AS ename,
LPAD(addr, 5, addr) AS addr
FROM (
SELECT LEVEL +10000000AS empno,
MOD(LEVEL, 1000) +10000AS sal,
CASEWHENMOD(LEVEL, 4) =0THEN10WHENMOD(LEVEL, 4) =1THEN20WHENMOD(LEVEL, 4) =2THEN30WHENMOD(LEVEL, 4) =3THEN40ENDAS deptno,
CHR(97+MOD(LEVEL, 26)) AS ename,
CHR(65+MOD(LEVEL, 26)) AS addr
FROM DUAL
CONNECTBY LEVEL <=200000
)
WHERE deptno IN (10, 20, 30, 40);
select deptno, count(*)
from emp
groupby deptno;
2.for loop 문을 사용했을 때의 프로시져로 delete 수행
CREATEOR REPLACE PROCEDURE for_delete_deptno
IS
TYPE trd_emp IS RECORD (deptno emp.deptno%TYPE, sal emp.sal%TYPE);
TYPE tnt_emp ISTABLEOF trd_emp;
v_emp tnt_emp;
TYPE tnt_deptno ISTABLEOF emp.deptno%TYPE;
v_deptno tnt_deptno := tnt_deptno(10, 20, 30, 40);
BEGINFOR i IN v_deptno.FIRST .. v_deptno.LAST LOOP
DELETE/*+ FOR */FROM emp
WHERE deptno = v_deptno(i)
RETURNING max(deptno), SUM(sal) BULK COLLECTINTO v_emp;
FOR j IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line(v_emp(j).deptno || chr(9) || v_emp(j).sal);
END LOOP;
END LOOP;
END;
/SQL>exec for_delete_deptno;
10524900000205249500003052500000040525050000
PL/SQL 처리가 정상적으로 완료되었습니다.
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'DELETE /*+ FOR */ %';
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------
afs5qg79x6wg2 DELETE/*+ FOR_ALL * 4 12
/ FROM EMP WHERE DEP
TNO = :B1 RETURNING
MAX(DEPTNO), SUM(SAL
) INTO :O0 ,:O1
--4번 수행
--사실 DB성능상 1억건을 한번에 업데이트 하는 것 보다
--천만건씩 10번 수행하는게 훨씬 빠름..!
--즉, 나눠서 수행하는게 더 좋
3. 다시 emp 테이블을 재생성 합니다.
SQL>droptable emp PURGE;
CREATE TABLE emp ASSELECT empno, sal, deptno,
LPAD(ename, 5, ename) AS ename,
LPAD(addr, 5, addr) AS addr
FROM (
SELECT LEVEL +10000000AS empno,
MOD(LEVEL, 1000) +10000AS sal,
CASEWHENMOD(LEVEL, 4) =0THEN10WHENMOD(LEVEL, 4) =1THEN20WHENMOD(LEVEL, 4) =2THEN30WHENMOD(LEVEL, 4) =3THEN40ENDAS deptno,
CHR(97+MOD(LEVEL, 26)) AS ename,
CHR(65+MOD(LEVEL, 26)) AS addr
FROM DUAL
CONNECTBY LEVEL <=200000
)
WHERE deptno IN (10, 20, 30, 40);
select deptno, count(*)
from emp
groupby deptno;
DEPTNO COUNT(*)
---------- ----------20500003050000405000010500005만건씩 잘 분배 됨.
4. forall 문을 사용했을 때의 프로시져로 delete 수행
SQL>CREATEOR REPLACE PROCEDURE forall_delete_deptno
IS
type trd_emp is record(deptno emp.deptno%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
type tnt_deptno istableof emp.deptno%type;
v_deptno tnt_deptno := tnt_deptno( 10, 20, 30, 40 );
BEGIN
FORALL i in v_deptno.first .. v_deptno.last
DELETE/*+ FOR_ALL */FROM emp
WHERE deptno = v_deptno(i)
RETURNING max(deptno), sum(sal) BULK COLLECTINTO v_emp;
FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).deptno ||chr(9) || v_emp(i).sal );
END LOOP;
END;
/SQL>exec forall_delete_deptno;
10524900000205249500003052500000040525050000
PL/SQL 처리가 정상적으로 완료되었습니다.
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'DELETE /*+ FOR_ALL */ %';
SQL_ID SQL_TEXT EXECUTIONS PROGRAM_LINE#
-------------------------- -------------------- ---------- -------------
afs5qg79x6wg2 DELETE/*+ FOR_ALL * 1 12
/ FROM EMP WHERE DEP
TNO = :B1 RETURNING
MAX(DEPTNO), SUM(SAL
) INTO :O0 ,:O1
--한번에 UPDATE,DELETE되는게 DB 성능상 조금 더 느림
--그냥 forall문에 대해서 설명하기 위해 해본것
문제풀며 익히기
Q1.
위의 FORALL문을 사용한 프로시져를 이용해서 DEPT테이블의 모든 데이터를 한번에 지우게끔 프로시져를 만들고 수행하시오.
CREATEOR REPLACE PROCEDURE forall_delete_dept
IS
type tnt_deptno istableof dept.deptno%type;
v_deptno tnt_deptno := tnt_deptno( 10, 20, 30, 40 );
BEGIN
FORALL i in v_deptno.first .. v_deptno.last
DELETE/*+ FOR_ALL */FROM dept
WHERE deptno = v_deptno(i);
END;
/
067. PLSQL 튜닝11 (Using deterministic vs Not Using deterministic 속도 비교)
📖
Using deterministic vs Not Using deterministic 속도 비교
deterministic 은 '결정적인' 이라는 한글 뜻인데요.
어떤 입력이 들어와서 언제 나 똑같은 과정을 거쳐서 똑같은 결과를 내놓는다면
입력되는 값의 결과는 이미 결정되어 있다는 것입니다.
PL/SQL 사용자 정의 함수의 성능을 높이기 위해서 deterministic 절을 사용할 수 있는데요.
deterministic 절을 함수에서 사용하면 함수를 반복적으로 호출하는 대신
이전에 계산된 결과를 사용하도록 할 수 있습니다.
ex) input값 순서 : A, B, B, A
INPUT A → 함수(혹은 사용자 정의 함수) → RETURN1
일때, A가 다음에 또 들어왔을 때
함수를 다시 호출하지 않고 이전에 계산된 결과를 사용하도록 함
실습1)
어떤 값을 입력받아서 그 값이 숫자면 Y 를 리턴하고 숫자가 아니면 N 을
리턴하는 함수를 생성하시오
createor replace function is_number
(p_value in varchar2) --INPUT파라미터(안써도 기본값 IN임)returnchar--입력매개변수에는 길이를 작성하면 안is
p_num number(10); -- 쓰레기 값이 저장된 내부변수 선언(값을 할당하지 않았으므로)begin
p_num := to_number(p_value);
return'Y';
exception
when value_error thenreturn'N';
end;
/----------------------------SELECT TO_NUMBER('A')
FROM DUAL;
!!ERROR!!
ORA-01722: 수치가 부적합합니다
SELECT TO_NUMBER('3')
FROM DUAL;
문자 3은 숫자로 변환 되기 때문에 에러나지 않음
---------------------------------//숫자형
SELECT sal, IS_number(sal)
FROM emp;
SAL |IS_NUMBER(SAL)|----+--------------+5000|Y |2850|Y |2450|Y |2975|Y |1250|Y |1600|Y |1500|Y |950|Y |1250|Y |3000|Y |800|Y |3000|Y |1100|Y |1300|Y |//문자형
SELECT ename, is_number(ename) ASRESULTFROM emp;
ENAME |RESULT|------+------+
KING |N |
BLAKE |N |
CLARK |N |
JONES |N |
MARTIN|N |
ALLEN |N |
TURNER|N |
JAMES |N |
WARD |N |
FORD |N |
SMITH |N |
SCOTT |N |
ADAMS |N |
MILLER|N |
실습2)
alter table emp
add mgr2 varchar2(10);
update emp
set mgr2 = mgr;
select ename, mgr, mgr2 from emp;
ENAME MGR MGR2
-------------------- ---------- --------------------
KING
BLAKE 78397839
CLARK 78397839
JONES 78397839
MARTIN 76987698
ALLEN 76987698
TURNER 76987698
JAMES 76987698
WARD 76987698
FORD 75667566
SMITH 79027902
SCOTT 75667566
ADAMS 77887788
MILLER 77827782update emp
set mgr2='A999'where ename='KING';
1 행이 업데이트되었습니다.
commit;
커밋이 완료되었습니다.
실습3)
함수를 사용하여 emp 테이블의 데이터를 select 합니다
col isnumber for a10
--컬럼 별칭의 isnumber를 for a10즉, 10자리 길이로 맞추겠다는 것select empno, ename, mgr2, is_number(mgr2) as isnumber
from emp;
EMPNO ENAME MGR2 ISNUMBER
------- -------------------- -------------------- ----------7839 KING A999 N
7698 BLAKE 7839 Y
7782 CLARK 7839 Y
7566 JONES 7839 Y
7654 MARTIN 7698 Y
7499 ALLEN 7698 Y
7844 TURNER 7698 Y
7900 JAMES 7698 Y
7521 WARD 7698 Y
7902 FORD 7566 Y
7369 SMITH 7902 Y
7788 SCOTT 7566 Y
7876 ADAMS 7788 Y
7934 MILLER 7782 Y
=> A999는 문자형이기 때문에 INSUMBER에 N이 출력됨
=> MGR2 7839가 3개 있는데, 처음것에서 Y가 결과임을 기억하고 있으면,
다음에 값이 들어올때 그냥 바로 Y RETURN하면 됨.
그것을 하게하는 함수가 DETERMINISTIC절임
실습4)
deterministic 절 없이 함수를 다음과 같이 수정합니다
SQL>SET SERVEROUTPUT ONcreateor replace function is_number
(p_value in varchar2)
returncharis
p_num number;
begin//이게 추가됨. 입력매개변수의 값을 출력하는 줄
dbms_output.put_line('is_number ('||p_value||') called');
p_num := to_number(p_value);
return'Y';
exception
when value_error thenreturn'N';
end;
/
실습5)
함수를 사용하여 emp 테이블의 데이터를 select 합니다
set serveroutput onselect empno, ename, mgr2, is_number(mgr2) as isnumber
from emp;
EMPNO ENAME MGR2 ISNUMBER
---------- -------------------- -------------------- ----------7839 KING A999 N
7698 BLAKE 7839 Y
7782 CLARK 7839 Y
7566 JONES 7839 Y
7654 MARTIN 7698 Y
7499 ALLEN 7698 Y
7844 TURNER 7698 Y
7900 JAMES 7698 Y
7521 WARD 7698 Y
7902 FORD 7566 Y
7369 SMITH 7902 Y
7788 SCOTT 7566 Y
7876 ADAMS 7788 Y
7934 MILLER 7782 Y
14 행이 선택되었습니다.
is_number (A999) called
is_number (7839) called
is_number (7839) called
is_number (7839) called
is_number (7698) called
is_number (7698) called
is_number (7698) called
is_number (7698) called
is_number (7698) called
is_number (7566) called
is_number (7902) called
is_number (7566) called
is_number (7788) called
is_number (7782) called
실습6)
deterministic 옵션을 주고 함수를 생성합니다
createor replace function is_number
(p_value in varchar2)
returnchar//이게 추가됨
deterministic--앞으로 한번 입력된 값을 기억하고, 같은 input값이 오 기억된 것을 출력is
p_num number;
begin
dbms_output.put_line('is_number ('||p_value||') called');
p_num := to_number(p_value);
return'Y';
exception
when value_error thenreturn'N';
end;
/
실습 7)
함수를 사용하여 결과를 출력합니다
select empno, ename, mgr2, is_number(mgr2) as isnumber
from emp;
EMPNO ENAME MGR2 ISNUMBER
---------- -------------------- -------------------- ----------7839 KING A999 N
7698 BLAKE 7839 Y
7782 CLARK 7839 Y
7566 JONES 7839 Y
7654 MARTIN 7698 Y
7499 ALLEN 7698 Y
7844 TURNER 7698 Y
7900 JAMES 7698 Y
7521 WARD 7698 Y
7902 FORD 7566 Y
7369 SMITH 7902 Y
7788 SCOTT 7566 Y
7876 ADAMS 7788 Y
7934 MILLER 7782 Y
14 행이 선택되었습니다.
is_number (A999) called
is_number (7839) called
is_number (7698) called
is_number (7566) called
is_number (7902) called
is_number (7788) called
is_number (7782) called=> 실행. 입력매개변수를 출력하는 문장을 덜 수행하게됨. 즉, 실행을 덜한것
=>7839를 한번만 실행하고, 그 다음에 같은 값이 input되면 함수를 실행하지 않고
=> 기억한 결과값을 바로 출력하게
문제풀며 익히기
Q1.
부서번호를 입력하면 해당 부서번호의 토탈월급이 출력되는 fun51 함수에 deterministic 옵션을 주고 함수를 생성 하고 작동되는지 테스트 하시오
CREATEOR REPLACE FUNCTION fun51
( p_deptno emp.deptno%TYPE )
RETURN NUMBER
//이것만 추가하면
DETERMINISTICAS
v_sum NUMBER(10);
BEGIN
dbms_output.put_line('is_number ('||p_deptno||') called');
SELECTSUM(sal) INTO v_sum
FROM emp
WHERE deptno = p_deptno;
RETURN v_sum;
END;
/set serveroutput onSELECT ename, sal, deptno, fun51(deptno)
FROM emp;
ENAME SAL DEPTNO FUN51(DEPTNO)
-------------------- ---------- ---------- -------------
KING 5000108750
BLAKE 2850309400
CLARK 2450108750
JONES 29752010875
MARTIN 1250309400
ALLEN 1600309400
TURNER 1500309400
JAMES 950309400
WARD 1250309400
FORD 30002010875
SMITH 8002010875
SCOTT 30002010875
ADAMS 11002010875
MILLER 130010875014 행이 선택되었습니다.
is_number (10) called
is_number (30) called
is_number (10) called
is_number (20) called
경 과: 00:00:00.01=> 앞에 수행했던 것을 기억하기 때문에 단4번만 수행된
=> 그럼 왜 10은 두번...??
연달아 수행되지 않아서인가....????그럼 마지막 10은 왜..? (검색해보기)
Q2.
사원테이블의 직업을 입력하면, 해당 직업의 토탈월급이 출력되는 함수를 fun_job으로 생성하는데, 한번 호출된 데이터는 다시 실행되지 않게 함수를 생성하고 활용하시오.
createor replace function fun_job
(p_job emp.job%type)
return NUMBERber
deterministicAS
v_sum NUMBER(10);
BEGIN
dbms_output.put_line('is_number ('||p_job||') called');
SELECTSUM(sal) INTO v_sum
FROM emp
WHERE job = p_job;
RETURN v_sum;
END;
/select ename, sal, job, fun_job(job)
from emp;
ENAME SAL JOB FUN_JOB(JOB)
-------------------- ---------- ------------------ ------------
KING 5000 PRESIDENT 5000
BLAKE 2850 MANAGER 8275
CLARK 2450 MANAGER 8275
JONES 2975 MANAGER 8275
MARTIN 1250 SALESMAN 5600
ALLEN 1600 SALESMAN 5600
TURNER 1500 SALESMAN 5600
JAMES 950 CLERK 4150
WARD 1250 SALESMAN 5600
FORD 3000 ANALYST 6000
SMITH 800 CLERK 4150
SCOTT 3000 ANALYST 6000
ADAMS 1100 CLERK 4150
MILLER 1300 CLERK 415014 행이 선택되었습니다.
is_number (PRESIDENT) called
is_number (MANAGER) called
is_number (SALESMAN) called
is_number (CLERK) called
is_number (ANALYST) called
경 과: 00:00:00.01
068. PL/SQL 튜닝12 (함수 생성시 result cache 사용하는 방법 ★ 아는 사람만 쓰는 기)
📖
함수 생성시 result cache 사용하는 방법
RESULT CACHE 는 SQL과 PL/SQL의 성능 개선을 위한 오라클의 기술중 하나입니다.
RESULT CACHE 는 오라클 메모리의 RESULT CACHE MEMORY 로 불리는 영역에
SQL 및 PL/SQL 함수의 결과를 저장하고 , 이후 동일 QUERY 조회 시
RESULT CACHE 에 저장 되어 있는 QUERY 결과 값을 그대로 활용하는 기능입니다.
단어장에 특별히 북마크? 해놓은 것과 같은 것.
결과가 업데이트 되면, 다시 emp에서 가져와서 메모리에 놓은 뒤 result cache에 다시 넣음
실습1) result cache 사용 안 했을 때
connect/as sysdba => 우리는 connect sys/oracle_4U as sysdba
--oracle_4U 오라클 설치시 설정했던 dba 비밀번호grantexecuteon dbms_lock to c##scott;
--dbms_lock은 잠깜 멈춤 기능을 주는 패키connect c##scott/tiger
set timing oncreateor replace function test_result_cache
( p_in in number )
return number
asbegin
dbms_lock.sleep(10); --잠깐 10초 멈춤return( p_in ); --p_in의 값 returnend;
/select test_result_cache(7) from dual;
TEST_RESULT_CACHE(7)
---------------------7
경 과: 00:00:10.01>> dbms_lock.sleep(10); 에 의해 10초 대기했기 때문
실습2) result cache 사용 했을 때
createor replace function test_result_cache
( p_in in number )
return number
//아래 명령어 추가
result_cache
-- 메모리에 올려놓는 asbegin
dbms_lock.sleep(10);
return( p_in );
end;
/SQL>select test_result_cache(7) from dual;
TEST_RESULT_CACHE(7)
--------------------7
경 과: 00:00:10.02SQL>select test_result_cache(7) from dual;
TEST_RESULT_CACHE(7)
--------------------7
경 과: 00:00:00.00==> 처음에 실행할 때만 10초 걸리고 다시 실행하면 바로 결과 출력됨
문제풀며 익히기
Q1.
다음의 함수에 result cache 를 사용해서 성능이 높아지도록 하시오
SQL>CREATEOR REPLACE FUNCTION fun179
( p_ename emp.ename%TYPE )
RETURN VARCHAR2
//여기에 추가
RESULT_CACHE
AS
v_rank NUMBER(5);
v_msg VARCHAR2(20);
BEGIN
dbms_lock.sleep(1); --일부러 넣은것! 실행시간이 빠르기 때문에 확인차 넣은것SELECT 순위 INTO v_rank
FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='고소득';
ELSE
v_msg :='저소득';
END IF;
RETURN v_msg;
END;
/
col 소득 for a10
SELECT ename, sal, fun179(ename) 소득
FROM emp
WHERE fun179(ename) ='고소득';
ENAME SAL 소득
-------------------- ---------- ----------
KING 5000 고소득
JONES 2975 고소득
FORD 3000 고소득
SCOTT 3000 고소득
경 과: 00:00:18.12
한번 더 수행 해도 ...
SQL>SELECT ename, sal, fun179(ename) 소득
2FROM emp
3WHERE fun179(ename) ='고소득';
ENAME SAL 소득
-------------------- ---------- ----------
KING 5000 고소득
JONES 2975 고소득
FORD 3000 고소득
SCOTT 3000 고소득
경 과: 00:00:01.02
069. PL/SQL 튜닝13 (Using parallel_enable vs Not Using parallel_enable 속도 비교)
📖
Using parallel_enable vs Not Using parallel_enable 속도 비교
Using parallel_enable
하나의 일을 혼자서 하는게 아니라 여러 프로세서들이 나눠서 같이 동시에 작업하는것을 병렬 작업이라고 합니다.
그런데 PL/SQL 함수를 생성할 때 parallel_enabled 옵션을 사용하게 되면 함수를 실행할 때 병렬로 작업을 해서 성능을 높일 수 있습니다.
1. 함수 (싱글처리 : 즉, 이 함수를 실행하는 프로세서가 1개라는 것 )
createor replace function func_s
(p_val number)
return number
is
v_ret number;
beginselect dbms_random.value(1, p_val) into v_ret
from dual;
/* 만약 p_val 이 10이라면 1~10까지 랜덤하게 들어가는 것
실수가 랜덤하게 들어가게하는 오라클 내장 패키지임 dbms_randoem.value()
SQL> select dbms_random.value(1,10)
2 from dual;
DBMS_RANDOM.VALUE(1,10)
-----------------------
5.14155345
경 과: 00:00:00.01
SQL> /
DBMS_RANDOM.VALUE(1,10)
-----------------------
7.3454166
경 과: 00:00:00.00
SQL> /
DBMS_RANDOM.VALUE(1,10)
-----------------------
6.14250179
경 과: 00:00:00.00
*/return v_ret;
end;
/2. 함수 (병렬처리)
createor replace function func_p (p_val number)
return number
//아래의 명령어 입력
parallel_enable
//이 함수 실행시 병렬처리. 여러개의 프로세서가 함수를 실행
is
v_ret number;
beginselect dbms_random.value(1, p_val) into v_ret
from dual;
return v_ret;
end;
/3. 테스트 테이블 생성(계층형 질의문을 활용한 200만건짜리 test_table 생성)
(계층형 질의문 사용시 데이터를 크게 만들 수 있음)
droptable test1;
set timing oncreate table test1
asselect empno, salary, deptno,
lpad(ename, 5, ename) as ename,
lpad(addr, 5, addr) as addr
from ( select level +10000000as empno,
mod(level,1000) +10000as salary,
mod(level,20) as deptno,
chr(97+mod(level,26)) as ename,
chr(65+mod(level,26)) as addr
from dual
connectby level <=2000000
);
경 과: 00:00:06.00SQL>selectcount(*) from test1;
COUNT(*)
----------2000000
경 과: 00:00:00.08------------------------------------------------------------------select/*+ parallel(10) */avg(hash_val)
from (
select func_s(empno) as hash_val
from TEST1
);
Elapsed: 00:01:42.52/* parallel()이라는 힌트를 주면 병렬로 작업하며,
안의 숫자의 의미는 10개의 프로세서가 작업한다는 것 *//* 한개의 프로세서가 200만건을 혼자 읽음 . 따라서 병렬로 힌트를 줬지만
parallel_enable을 주지 않았기 때문에 상관없이 오래걸림*/--------------------------------------------------------------------select/*+ parallel(10) */avg(hash_val)
from (
select func_p(empno) as hash_val
from TEST1
)
;
Elapsed: 00:00:21.40/* 프로세서가 여러개가 나눠서 읽기 때문에 빠름 *//* express edition 버전은 똑같음.. 병렬을 사용하지 못하기 때문 */!!주의!!
개발자들이 /*+ parallel(10) */ 사용하는건 자제시켜야함
남발하면 cpu성능이 너무 느려지기 때문
SQL>showparameter cpu_count
NAME TYPE VALUE------------------------------------ ---------------------- ------------------------------
cpu_count integer20=>20코어!=> 병렬을 42개 줄 수 있는것
문제풀며 익히기
Q1.
068.문제에서 만들었던 fun179를 가져와서 병렬로 수행할 수 있도록
CREATEOR REPLACE FUNCTION fun179
( p_ename emp.ename%TYPE )
RETURN VARCHAR2
//아래의 명령어 입력
PARALLEL_ENABLE
AS
v_rank NUMBER(5);
v_msg VARCHAR2(20);
BEGIN
dbms_lock.sleep(1); --일부러 넣은것! 실행시간이 빠르기 때문에 확인차 넣은것SELECT 순위 INTO v_rank
FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='고소득';
ELSE
v_msg :='저소득';
END IF;
RETURN v_msg;
END;
/
select/*+ parallel(10) */ fun179(ename) 소득
from emp
where fun179(ename) ='고소득';
Q2.
위의 sql을 다시 수행하는데 병렬도를 16으로 주고 실행하시오.
select/*+ parallel(16) */ fun179(ename) 소득
from emp
where fun179(ename) ='고소득';
병렬도가 높을수록 더 빨리 수행됨
위에 lock이 있어서 오래걸릴 수밖에 없긴 함
070. PL/SQL 튜닝14 (pragma autonomous_transaction 사용했을때와 사용하지 않았을때 비교)
📖
pragma autonomous_transaction 사용했을때와 사용하지 않았을때 비교
▣ pragma autonomous_transaction 옵션이란 ?
프로시저내에서 호출하는 다른 프로시저에 트랜잭션을 별도의 독립된 트랜잭션으로 사용하려 할 때 사용하는 옵션(pl/sql을 제어하는 옵션)
프로시저 내에서 다른 프로시저를 호출 할 수 있는데, 프로시저 내에 insert, delete, update문이 다른 프로시져 내에 있던것, 그 때 그 트랜잭션을 별도의 독립된 트랜잭션으로 사용하려고 할 때 사용하는 옵션
< log_insert 프로시져 >< dept_insert 프로시져 >insert into log_table ... insert into dept ...
commit; log_insert ;
--------------------------------------------------------------------commit;하는 것에 집중 끝낼 때 다른 프로시저 실행
=> dept_insert프로시져는 그럼 커밋이 될까?
커밋이 됨! 근데, 커밋을 안하게 할 수 있다는 것.
즉, 다른 프로시져와 관계없이 나의 프로시져는 rollback하고 싶을 수 있음
따라서 별도의 트랜잭션으로 만들어주면 커밋되지 않게 할 수 있음
---------------------------------------------------------------------
두개의 프로시져를 각각 생성하고 나서 다음과 같이 실행했을 때
exec dept_insert;
rollback;
=======================================================================
log_insert 프로시져가 뭘 했든 난 커밋을 안하고 rollback하고 싶을 때
pragma autonomous_transaction 사용
트랜잭션(transaction) 이란?
작업(DML 문들 : INSERT, UPDATE, DELETE, MERGE) 의 단위.
COMMIT 이나 ROLLBACK 을 하면 작업이 종료가 됨.
예제
예제 1)
아래의 테이블을 생성하고 데이터를 입력합니다.
droptable log_table;
CREATE TABLE log_table
(
username VARCHAR2(10),
message VARCHAR2(50) );
INSERT INTO log_table
VALUES ( user,'test 입니다'); --user에 user명이 담김(show user하면 보이는)SELECT*FROM log_table;
/* 결과
USERNAME MESSAGE
---------- -----------------
SCOTT test 입니다
*/
예제2)
위의 log_table 에 data 를 입력하는 프로시저를 생성하는데,
아래와 같이 프로시저를 생성하면 data 가 입력되는 프로시저를 생성하시오 !
(commit 넣을것)
SQL>execute log_insert('두번째 test 입니다.');
SQL>select*from log_table;
USERNAME MESSAGE
---------- ------------------------------
c##SCOTT test 입니다
c##SCOTT 두번째 test 입니다.
답:
CREATEOR REPLACE PROCEDURE log_insert
( p_msg VARCHAR2 )
ISBEGININSERT INTO log_table
VALUES ( user, p_msg );
COMMIT; --커밋이 있다는 것 눈여겨보기END;
/SQL>exec log_insert('두번째 test 입니다.');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>select*from log_table;
USERNAME MESSAGE
---------- ------------------------------
SCOTT test 입니다
SCOTT 두번째 test 입니다.
예제3)
부서테이블에 data 를 입력하는 프로시저를 생성하시오 ! (commit 넣지 말것)
CREATEOR REPLACE PROCEDURE dept_insert
( p_deptno dept.deptno%TYPE, p_loc dept.loc%TYPE )
ISBEGININSERT INTO dept (deptno, loc)
VALUES ( p_deptno, p_loc );
END;
/* 실행 및 결과
SQL>exec dept_insert(50, 'seoul');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
-- dept_insert 프로시져내에 log_insert 프로시저를 호출하는 구문 추가하기 CREATEOR REPLACE PROCEDURE dept_insert
( p_deptno dept.deptno%TYPE, p_loc dept.loc%TYPE )
ISBEGININSERT INTO dept (deptno, loc)
VALUES ( p_deptno, p_loc );
//begin과 end사이에 넣어주면 됨
log_insert('세번째 test 입니다.');
//안에 커밋이 들어있는 프로시저
END;
/-- 다음과 같이 수행해보기.SQL>select*from dept;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
SQL>execute dept_insert(60, 'busan');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
60 busan
6 개의 행이 선택되었습니다.
SQL>rollback;
롤백이 완료되었습니다.
SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
60 busan
6 개의 행이 선택되었습니다.
--> dept_insert 프로시저에 COMMIT 이 있기 때문에 rollback 이 수행되지 않는다.
예제4)
pragma autonomous_transaction 문법을 참고해서
log_insert 프로시저 내의 DML 문이 별도의 트랜잭션이 됨을 테스트 하시오
CREATEOR REPLACE PROCEDURE log_insert
( p_msg VARCHAR2 )
IS//아래 명령어를 추가한 것.
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN//명령어를 추가한 덕분에 아래의insert .. commit;문은
//별도의 독립적인 트랜잭션이 된 것
////////////////////////////////////////////INSERT INTO log_table
VALUES ( user, p_msg );
COMMIT;
///////////////////////////////////////////END;
/SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
60 busan
6 개의 행이 선택되었습니다.
SQL>execute dept_insert(70, 'daejeon');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
60 busan
70 daejeon
7 개의 행이 선택되었습니다.
SQL>rollback;
롤백이 완료되었습니다.
SQL>select*from dept;
DEPTNO DNAME LOC
---------- -------------- -------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 seoul
60 busan
6 개의 행이 선택되었습니다.
3장. PL/SQL 로 SQL 자동화하기 ★
- PL/SQL을 배우는 이유가 자동화 때문이다.
로 SQL 자동화하기 —이것 때문에 pl/sql 배운것이므로 잘 알아두
071. SQL 자동화1 (Dynamic SQL1)
📖
Dynamic SQL1
Dynamic SQL 이란 이름 그대로 동적으로 변할 수 있는 SQL입니다.
반대의 경우는 Static SQL 이 있는데 Static SQL은 정적 SQL로 변하지 않는 SQL 입니다.
Dynamic SQL : PL/SQL 내의 실행절에서 동적으로 변경 될 수 있는 SQL
Static SQL : PL/SQL 내의 실행절에서 동적으로 변경 될 수 없는 정해진 SQL
문법 : BEGIN과 END 사이에 작성
EXECUTE IMMEDIATE [큰 TEXT를 담기 위해 만든 변수 명]
INTO [레코드변수명]
USING [입력매개변수명];
필요한 이유
selectsum( decode (deptno, 10, sal, 0) ) as "10",
sum( decode (deptno, 20, sal, 0) ) as "20",
sum( decode (deptno, 30, sal, 0) ) as "30",
sum( decode (deptno, 40, sal, 0) ) as "40",
... -> 계속 유지보수 해줘야함,,,
from emp;
ex)
프로시져에 입력되는 값에 따라 아래의 첫번째 SQL이 실행될 수 도 있고
두번째 SQL이 실행될 수 도 있습니다
select sal + comm into v_sal
from emp
where job='SALESMAN';
select sal + bonus into v_sal
from emp
where job='ANALYST';
========================================================exec pro1('SALESMAN')을 넣었을 때
1번 SQL이 실행되게 하고
exev pro1('CLERK') 을 넣으면
2번 SQL이 실행되게 하는 등 각각 데이터에 따라 다르게 하고 싶으면
입력되는 데이터에 따라서 실행되는 SQL이 달라지게 하기 위해
다이나믹 SQL을 사용
1.execute immediate 절을 사용안했을 때
CREATEOR REPLACE PROCEDURE find_ename
( p_empno emp.empno%TYPE )
IS
v_emp emp%ROWTYPE; --레코드 변수 생성BEGINselect*into v_emp
from emp
where empno = p_empno;
DBMS_OUTPUT.PUT_LINE( '이름 : '|| v_emp.ename || chr(9) ||'월급 : '|| v_emp.sal );
END;
/SQL>SET SERVEROUTPUT ONSQL>exec find_ename(7788);
이름 : SCOTT 월급 : 3000
2.execute immediate 절을 사용했을 때
CREATEOR REPLACE PROCEDURE find_ename2
( p_empno emp.empno%TYPE )
IS
v_stmt VARCHAR2(200); --큰 TEXT를 담을 변수 만든것
v_emp emp%ROWTYPE;
BEGIN
v_stmt :='SELECT *
FROM emp
WHERE empno = :eno';
-- :eno 는 바인드변수이며, eno라는 이름은 아무거나 의미있는 명으로 넣으면 됨-- 여기서는 empno를 담을것이라서 이렇게 이름 부여-- select into없이 select문장만 사용-- 문자형의 변수이기 때문에 select문을 싱글쿼테이션마크로 둘러쌈EXECUTE IMMEDIATE v_stmt INTO v_emp USING p_empno;
-- 바인드 변수에 값을 넣으건데, p_empno를 사용해서 값을 할당(?)
DBMS_OUTPUT.PUT_LINE( '이름 : '|| v_emp.ename || chr(9) ||'월급 : '|| v_emp.sal );
END;
/SQL>exec find_ename2(7566);
이름 : JONES 월급 : 2975
PL/SQL 처리가 정상적으로 완료되었습니다.
경 과: 00:00:00.00
문제풀며 익히기
Q1.
부서 테이블(dept)에서 부서위치(loc)와 부서명(dname) 을 출력하는 프로시져를 생성하세요.
다음과 같이 부서번호를 입력해서 프로시져를 실행하면 해당 부서번호의 부서위치와 부서번호가 출력되게 execute immediate 절을 이용해서 출력하세요
출력예시 :
SQL> exec find_loc_dname(10);
부서명 : ACCOUNTING 부서위치 : NEW YORK
createor replace procedure find_loc_dname
( p_deptno dept.deptno%type )
AS
v_stmt VARCHAR2(200);
v_dept dept%ROWTYPE;
BEGIN
v_stmt :='select * from dept where deptno = :dno';
EXECUTE IMMEDIATE v_stmt INTO v_dept USING p_deptno;
dbms_output.put_line('부서명 : '|| v_dept.dname || chr(9) ||'부서위치 : '|| v_dept.loc );
END;
/SQL>exec find_loc_dname(10);
부서명 : ACCOUNTING 부서위치 : NEW YORK
PL/SQL 처리가 정상적으로 완료되었습니다.
072. SQL 자동화2 (Dynamic SQL2)
📖
SQL 자동화2 (Dynamic SQL2)
EXECUTE IMMEDIATE 사용 이유
SQL 자동화를 사용하기 위하여 사용한 것.
자동화 스크립트를 알기 전에 이해해야 하기 때문에 배울 것
프로시져에 execute immediate 절을 써서 여래개의 행을 출력하는 예제
이걸 사용해야 자동으로 연결해서 자동화 할 수 있음
1.execute immediate 절을 사용안했을 때
CREATEOR REPLACE PROCEDURE find_ename
( p_deptno emp.deptno%TYPE )
IS
type trd_emp is record(ename emp.ename%type, deptno emp.deptno%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
/* 최종적으로 중첩테이블 조합변수 v_emp를 만드는데, 그 과정을 보면
먼저 레코트 타입을 생성하고(컬럼명과 타입 지정), 중첩테이블 타입을 선언!
KEY, ENAME, DEPTNO를 컬럼으로 가지는 v_emp라는 중첩테이블 조합변수가 완성됨 */BEGINselect ename, deptno bulk collectinto v_emp
from emp
where deptno = p_deptno;
/* SELECT .. INTO : 하나의 값만 담을 수 있는 스칼라변수에만 넣을 수 있는 문법.
SELECT .. BULK COLLECT INTO : 여러개의 행의 데이터들을 한번에 담을 수 있음
부서번호를 where절에서 조회하고 있기 때문에 ,
여러개의 행이 들어간다는 것을 알 수 있음.
즉 중첩테이블은 여러개의 값을 가질 수 있기 때문에 bulk 사용 */FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).ename ||chr(9) || v_emp(i).deptno );
END LOOP;
/* i 부터 마지막 키 값 번호만큼 index 카운터가 변경되는 것
만약 부서번호가 10이 입력되었으면 3까지 index 카운터가 변경됨 */END;
/SQL>set serveroutput onSQL>exec find_ename(10);
KING 10
CLARK 10
MILLER 10----------------------------------------------------------------------------2.execute immediate 절을 사용했을 때
CREATEOR REPLACE PROCEDURE find_ename2
(p_deptno IN emp.deptno%TYPE)
IS
type trd_emp is record(ename emp.ename%type, deptno emp.deptno%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
/*위의 3줄은 동일하게 중첩테이블 조합변수 생성 문장 */
v_stmt VARCHAR2(200);
/* 문자데이터를 200바이트 담을 큰 문자형 변수를 만들어 줌 */BEGIN
v_stmt :='SELECT ename, deptno FROM emp WHERE deptno = :dno';
/* 문자형 변수에 select문장을 할당함.
SELECT INTO나 SELECT BULK INTO를 사용하지 않음.
단, 특징은 앞에 콜론을 붙인 바인드 변수를 사용함
특정 값이 바인딩 되어 들어온다는 것을 의미함.
아래 줄의 EXECUT BULK INTO USING 에 있는 P_DPETNO의 값을
:DNO에 바인딩해옴
매번 SQL에 다른 값을 넣어 출력해줘야하니까 바인드 변수 사용한것 */EXECUTE IMMEDIATE v_stmt BULK COLLECTINTO v_emp USING p_deptno;
/* SELECT문장을 즉시실행해라. 실행해서 V_STMT가 얻게된 결과를
BULK COLLECT INTO를 사용해서 V_EMP라는 조합변수로 넣는 것을.
마지막에는 USING을 사용해 사용할 입력 매개변수를 작성해줘야함 */FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(v_emp(i).ename || CHR(9) || v_emp(i).deptno);
END LOOP;
END;
/SQL>exec find_ename2(10);
KING 10
CLARK 10
MILLER 10
문제풀며 익히기
Q1.
위의 예제의 코드를 수정해서 직업을 입력해서 프로시져를 실행하면 해당 직업인 사원들의 이름과 월급과 직업이 출력되게 프로시져를 생성하세요
출력예시
SQL> exec find_ename3('SALESMAN');
MARTIN 1250 SALESMAN
ALLEN 1600 SALESMAN
TURNER 1500 SALESMAN
WARD 1250 SALESMAN
CREATEOR REPLACE PROCEDURE find_ename3
(p_job IN emp.job%TYPE)
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type, job emp.job%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
v_stmt VARCHAR2(200);
BEGIN
v_stmt :='SELECT ename, sal, job FROM emp WHERE job = :job_b';
EXECUTE IMMEDIATE v_stmt BULK COLLECTINTO v_emp USING p_job;
FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(v_emp(i).ename || CHR(9) ||
v_emp(i).sal || CHR(9) ||
v_emp(i).job);
END LOOP;
END;
/SQL>exec find_ename3('SALESMAN');
MARTIN 1250 SALESMAN
ALLEN 1600 SALESMAN
TURNER 1500 SALESMAN
WARD 1250 SALESMAN
073. SQL 자동화3 (Dynamic SQL3)
📖
Dynamic SQL3
Dynamic SQL
Dynamic SQL 이란 이름 그대로 동적으로 변할 수 있는 SQL입니다.
반대의 경우는 Static SQL 이 있는데 Static SQL은 정적 SQL로 변하지 않는 SQL 입니다.
Dynamic SQL : PL/SQL 내의 실행절에서 동적으로 변경 될 수 있는 SQL
Static SQL : PL/SQL 내의 실행절에서 동적으로 변경 될 수 없는 정해진 SQL
▣입력되는 값에 따라 PL/SQL 내에서 수행되어지는 SQL이 달라지는 예제
예: 프로시져에 입력되는 값에 따라 아래의 첫번째 SQL이 실행될 수 도 있고
두번째 SQL이 실행될 수 도 있습니다.
select sal + comm into v_sal
from emp
where job='SALESMAN';
select sal + sal*0.2into v_sal
from emp
where job='ANALYST' ;
1.execute immediate 절을 사용했을 때
CREATEOR REPLACE PROCEDURE find_sal
(p_job emp.job%TYPE)
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
/* 중첩테이블 조합변수를 생성하는 코드 3줄 */
v_stmt VARCHAR2(200);
BEGIN
IF p_job ='SALESMAN'THEN
v_stmt :='SELECT ename, sal + comm FROM emp WHERE job = :j';
ELSIF p_job ='ANALYST'THEN
v_stmt :='SELECT ename, sal + sal*0.2 FROM emp WHERE job = :j';
ELSE
v_stmt :='SELECT ename, sal FROM emp WHERE job = :j';
END IF;
/* 입력된 매개변수의 값에 따라 특정 SELECT 문장을 V_STMT에 담는 SQL문장들임
SELECT절의 바인드 변수에 어떤 값이 들어올지 모르니까 바인드 변수를 사용하고
USING절에 들어오는 입력매개변수를 넣어줘서 그 값을 바인드 변수에 바인드(할당) */EXECUTE IMMEDIATE v_stmt BULK COLLECTINTO v_emp USING p_job;
/* V_STMT에 있는 SELECT문장을 실행해서 V_EMP에 담는 것
P_JOB이라는 입력매개변수에 입력되는 값에 따라서 특정 SELECT이 담기는 것 */FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).ename ||chr(9) || v_emp(i).sal );
END LOOP;
/* 입력되는 값이 달라지니까 카운트되는 값도 바뀔 것 */END;
/SQL>exec find_sal('SALESMAN');
MARTIN 2650
ALLEN 1900
TURNER 1500
WARD 1750SQL>exec find_sal('ANALYST');
FORD 3600
SCOTT 3600SQL>exec find_sal('CLERK');
JAMES 950
SMITH 800
ADAMS 1100
MILLER 1300
2.execute immediate 절을 사용안했을 때 - 참고용
CREATEOR REPLACE PROCEDURE find_sal
(p_job IN emp.job%TYPE)
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
BEGINselect ename, CASEWHEN job ='SALESMAN'THEN sal + comm
WHEN job ='ANALYST'THEN sal + sal *0.2ELSE sal
END
bulk collectinto v_emp
from emp
where job = p_job;
/* select into가 아니라 bulk collect into를 사용해서 v_emp라는 조합변수에
값을 넣었다는 것에 주목해줘야함
FOR i IN 1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line(v_emp(i).ename || CHR(9) || v_emp(i).sal);
END LOOP;
END;
/
SQL> exec find_ename(10);
KING 10
CLARK 10
MILLER 10
find_sal 프로시저의 경우 CASE 문을 사용하여 여러 조건에 따라 다른 계산을 수행하고
그 결과를 하나의 SQL 문으로 처리하는 것이 더 효율적이며 간단합니다.
CASE 문을 사용하면 여러 조건을 고려할 수 있으며 하나의 SQL 문으로 원하는 결과를
얻을 수 있습니다.
그러면 동적 SQL 및 EXECUTE IMMEDIATE를 사용하려는 이유는
일반적으로 SQL 문이 런타임 중에 동적으로 생성(즉, 자동으로 변경)되는 경우,
예를 들어 동적으로 테이블 이름이나 열 이름을 결정해야 하는 경우에 유용합니다
(프로시져 실행할 때 프로시져 입력값으로 테이블 이름이나 열 이름을 넣어야할 경우)
다음 영상에서 예제가 나옵니다.
문제풀며 익히기
Q1.
execute immediate 을 사용한 위의 스크립트를 이용해서 부서번호를 입력해 프로시져를 실행하면, 해당 부서번호인 사원들의 이름과 월급이 출력되게 하시오.
단, 부서번호가 30번인 사원들은 월급을 출력할 때 SAL+COMM이 출력되게 하고, 부서번호가 10번인 사원들은 월급을 출력할 때 SAL+1000이 출력되게 하고, 나머지 부서번호는 그냥 자기 월급만 출력되게 하시오.
CREATEOR REPLACE PROCEDURE find_sal
(p_deptno emp.deptno%TYPE)
IS
type trd_emp is record(ename emp.ename%type, sal emp.sal%type);
type tnt_emp istableof trd_emp;
v_emp tnt_emp;
v_stmt VARCHAR2(200);
BEGIN
IF p_deptno =30THEN
v_stmt :='SELECT ename, sal + comm FROM emp WHERE deptno = :dno';
ELSIF p_deptno =10THEN
v_stmt :='SELECT ename, sal + 1000 FROM emp WHERE deptno = :dno';
ELSE
v_stmt :='SELECT ename, sal FROM emp WHERE deptno = :dno';
END IF;
EXECUTE IMMEDIATE v_stmt BULK COLLECTINTO v_emp USING p_deptno;
FOR i IN1 .. v_emp.COUNT LOOP
DBMS_OUTPUT.put_line (v_emp(i).ename ||chr(9) || v_emp(i).sal );
END LOOP;
END;
/SQL>exec find_sal(10);
KING 6000
CLARK 3450
MILLER 2300SQL>exec find_sal(30);
BLAKE
MARTIN 2650
ALLEN 1900
TURNER 1500
JAMES
WARD 1750
074. SQL 자동화4 (Dynamic SQL4 - 현장에서 유용 ★)
📖
SQL 자동화4 (Dynamic SQL4)
동적 SQL 사용하는 상황 예시
테이블 이름 또는 열 이름이 런타임에 결정되는 경우:
SQL 쿼리의 대상이 되는 테이블이나 열 이름이 런타임에 결정되는 경우에는
동적 SQL이 필요합니다.
※ "런타임" 의 뜻은 프로그램이 실행 중인 시간을 가리킵니다.
동적 SQL을 사용하는 상황에서 "런타임"은 프로그램이 실행 중일 때,
즉 코드가 실행되고 데이터베이스와 상호작용할 때를 의미합니다.
-------------------------------------------------------------
테이블이름이나 열 이름이 결정되는 경우가 보통
프로시져 생성 코드 작성 시 결정되었는데,
런타임시에 테이블이름이나 컬럼명을 받을 수 있다는 것.
즉, 이미 생성이 되었는데, execute할때 테이블이름이나 열이름을 넣어서
생성하면서 실행할 수 있다는 것.
select문이 달라지면서 바뀌게 할 수도 있음
동적인 테이블 생성 또는 스키마 변경:
데이터베이스 객체를 생성, 수정 또는 삭제해야 하는 경우 동적 SQL이 필요
DDL문: create, alter ,drop ,truncate, rename
pl/sql에서 DDL문장을 사용하는 경우에
exeute immediate를 사용함
PL/SQL에서 EXECUTE IMMEDIATE를 사용하는 상황
1. 입력되는 값에 따라서 입력값이 달라져야하는 경우
2. pl/sql내에서 DDL문장을 사용하는 경우
쿼리문을 동적으로 결정해야 하는 경우:
테이블에 입력되어 있는 데이터에 따라서 쿼리문이 런타임시에 동적으로 구성되어야 할 때 필요합니다.
예제) 아래는 테이블 이름이 런타임에 결정되는 경우에 동적 SQL이 필요한 예제
1. 테이블 이름 또는 열 이름이 런타임에 결정되는 경우:
CREATEOR REPLACE PROCEDURE dynamic_table_query
IS
v_result NUMBER;
v_sql VARCHAR2(200);
BEGINFOR table_rec IN (SELECT table_name FROM user_tables) LOOP
/* 서브쿼리를 사용한 FOR LOOP문인데,
TBALE_REC이라는 레코드 변수를 암시적으로 선언한것.
서브 쿼리 부분은 CURSOR선언 부분임.서브 쿼리 형태로 CURSOR 넣음
내용은 데이터베이스 테이블 명들을 출력하는 쿼리임. 그것을 CURSOR로 선언*//*
SQL> SELECT TABLE_NAME FROM USER_TABLES;
TABLE_NAME
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ORDER2
ORDER3
CRIME_TIME2
CRIME_TIME
...
EMP_TEMP3
EMP_TEMP4
DEPT_TEMP
SYS_TEMP_FBT
EMP18
TEST2
ERROR
57 행이 선택되었습니다. */
v_sql :='SELECT COUNT(*) FROM '|| table_rec.table_name;
/* table_rec.table_name 에 있는 테이블을 COUNT하는
SELECT문을 V_SQL에 넣은 것
예를 들어 ORER2라는 테이블명이 table_rec.table_name에 들어가고
그 테이블을 카운트 */EXECUTE IMMEDIATE v_sql INTO v_result;
/* COUNT하는 건수가 V_RESULT에 들어감 */
DBMS_OUTPUT.PUT_LINE('테이블 '|| table_rec.table_name ||'의 레코드 수: '|| v_result);
/* 이게 바로 런타임시 테이블명이 결정되는 것 */END LOOP;
END;
/SQL>EXEC DYNAMIC_TABLE_QUERY;
테이블 ORDER2의 레코드 수: 3
테이블 ORDER3의 레코드 수: 9
테이블 CRIME_TIME2의 레코드 수: 272
테이블 CRIME_TIME의 레코드 수: 34
테이블 TELECOM_TABLE의 레코드 수: 6
테이블 BONUS의 레코드 수: 14
테이블 EMP55의 레코드 수: 0
테이블 EMP56의 레코드 수: 0
테이블 EMP57의 레코드 수: 0
테이블 EMP61의 레코드 수: 0
테이블 EMP65의 레코드 수: 1
테이블 EMP66의 레코드 수: 0
테이블 DEPT700의 레코드 수: 4
테이블 EMP700의 레코드 수: 14
테이블 DEPT901의 레코드 수: 0
테이블 DEPT902의 레코드 수: 0
테이블 DEPT_BACKUP의 레코드 수: 1
테이블 EMP_TEST2의 레코드 수: 14
테이블 SALGRADE의 레코드 수: 5
테이블 SALGRADE_TEST의 레코드 수: 5
테이블 HIGH_SAL의 레코드 수: 1
테이블 NORMAL_SAL의 레코드 수: 1
테이블 EMP_T의 레코드 수: 0
테이블 EMP100의 레코드 수: 2
테이블 MARKET_2022의 레코드 수: 312811
테이블 MARKET_2017의 레코드 수: 316078
테이블 PRICE의 레코드 수: 9517
테이블 EMP18_BACKUP의 레코드 수: 31
테이블 EMP_BACKUP2의 레코드 수: 18
테이블 EMP_BACKUP9의 레코드 수: 0
테이블 EMP600의 레코드 수: 14
테이블 DEPT600의 레코드 수: 4
테이블 EMP301의 레코드 수: 1
테이블 EMP99의 레코드 수: 1
테이블 CUPPANG_ORDER의 레코드 수: 100
테이블 ORD_ITEMS의 레코드 수: 1
테이블 SALES_NO_INDEX의 레코드 수: 918843
테이블 SALES_WITH_INDEX의 레코드 수: 918843
테이블 EMP51의 레코드 수: 0
테이블 EMP52의 레코드 수: 1
테이블 TEST_ORIGINAL의 레코드 수: 200000
테이블 TEST_BACKUP의 레코드 수: 200000
테이블 EMP_TEST의 레코드 수: 0
테이블 DEPT의 레코드 수: 6
테이블 EMP의 레코드 수: 14
테이블 TEST1의 레코드 수: 2000000
테이블 LOG_TABLE의 레코드 수: 4
테이블 T1의 레코드 수: 3
테이블 EMP18_BACKUP7의 레코드 수: 0
테이블 EMP_TEMP3의 레코드 수: 0
테이블 EMP_TEMP4의 레코드 수: 0
테이블 DEPT_TEMP의 레코드 수: 0
테이블 SYS_TEMP_FBT의 레코드 수: 0
테이블 DAILY_MACHUL의 레코드 수: 0
테이블 EMP18의 레코드 수: 30
테이블 TEST2의 레코드 수: 0
테이블 ERROR의 레코드 수: 1
PL/SQL 처리가 정상적으로 완료되었습니다.
EX) 현장에서 가장 큰 테이블 부터 순서대로 정리해와! 하면 유
문제풀며 익히기
Q1.
위의 예제를 수정하여 테이블 이름이 E 로 시작하는 테이블들만 결과로 출력되게
코드를 수정하세요 !
CREATEOR REPLACE PROCEDURE dynamic_table_query
IS
v_result NUMBER;
v_sql VARCHAR2(200);
BEGINFOR table_rec IN (SELECT table_name FROM user_tables
WHERE table_name like'E%') LOOP
v_sql :='SELECT COUNT(*) FROM '|| table_rec.table_name;
EXECUTE IMMEDIATE v_sql INTO v_result;
DBMS_OUTPUT.PUT_LINE('테이블 '|| table_rec.table_name ||'의 레코드 수: '|| v_result);
END LOOP;
END;
/SQL>exec dynamic_table_query;
테이블 EMP_TEMP3의 레코드 수: 0
테이블 EMP_TEMP4의 레코드 수: 0
테이블 EMP의 레코드 수: 14
테이블 EMP100의 레코드 수: 2
테이블 EMP18_BACKUP의 레코드 수: 31
테이블 EMP18_BACKUP7의 레코드 수: 0
테이블 EMP301의 레코드 수: 1
테이블 EMP51의 레코드 수: 0
테이블 EMP52의 레코드 수: 1
테이블 EMP55의 레코드 수: 0
테이블 EMP56의 레코드 수: 0
테이블 EMP57의 레코드 수: 0
테이블 EMP600의 레코드 수: 14
테이블 EMP61의 레코드 수: 0
테이블 EMP65의 레코드 수: 1
테이블 EMP66의 레코드 수: 0
테이블 EMP700의 레코드 수: 14
테이블 EMP99의 레코드 수: 1
테이블 EMP_BACKUP2의 레코드 수: 18
테이블 EMP_BACKUP9의 레코드 수: 0
테이블 EMP_T의 레코드 수: 0
테이블 EMP_TEST의 레코드 수: 0
테이블 EMP_TEST2의 레코드 수: 14
테이블 EMP18의 레코드 수: 30
테이블 ERROR의 레코드 수: 1
PL/SQL 처리가 정상적으로 완료되었습니다.
Q2. (NUM_ROWS..! sql 튜닝 수업듣고 다시! dba만 아는 것)
전체 결과를 다시 출력하는데, 테이블의 건수가 높은 것부터 출력되게하시오.
CREATEOR REPLACE PROCEDURE dynamic_table_query
IS
v_result NUMBER;
v_sql VARCHAR2(200);
BEGINFOR table_rec IN (SELECT table_name FROM user_tables
ORDERBY NUM_ROWS DESC) LOOP
v_sql :='SELECT COUNT(*) FROM '|| table_rec.table_name;
EXECUTE IMMEDIATE v_sql INTO v_result;
DBMS_OUTPUT.PUT_LINE('테이블 '|| table_rec.table_name ||'의 레코드 수: '|| v_result);
END LOOP;
END;
/
075. SQL 자동화5 (Dynamic SQL5)
📖
Dynamic SQL5
동적 SQL 사용하는 상황 예시
테이블 이름 또는 열 이름이 런타임에 결정되는 경우:
SQL 쿼리의 대상이 되는 테이블이나 열 이름이 런타임에 결정되는 경우에는
동적 SQL이 필요합니다.
※ "런타임" 의 뜻은 프로그램이 실행 중인 시간을 가리킵니다.
동적 SQL을 사용하는 상황에서 "런타임"은 프로그램이 실행 중일 때,
즉 코드가 실행되고 데이터베이스와 상호작용할 때를 의미합니다.
-------------------------------------------------------------
테이블이름이나 열 이름이 결정되는 경우가 보통
프로시져 생성 코드 작성 시 결정되었는데,
런타임시에 테이블이름이나 컬럼명을 받을 수 있다는 것.
즉, 이미 생성이 되었는데, execute할때 테이블이름이나 열이름을 넣어서
생성하면서 실행할 수 있다는 것.
select문이 달라지면서 바뀌게 할 수도 있음
동적인 테이블 생성 또는 스키마 변경(테이블 변경):
데이터베이스 객체를 생성, 수정 또는 삭제해야 하는 경우 동적 SQL이 필요
DDL문: create, alter ,drop ,truncate, rename
pl/sql에서 DDL문장을 사용하는 경우에
exeute immediate를 사용함
PL/SQL에서 EXECUTE IMMEDIATE를 사용하는 상황
1. 입력되는 값에 따라서 입력값이 달라져야하는 경우
2. pl/sql내에서 DDL문장을 사용하는 경우
쿼리문을 동적으로 결정해야 하는 경우:
테이블에 입력되어 있는 데이터에 따라서 쿼리문이 런타임시에 동적으로 구성되어야 할 때 필요합니다.
▣ PL/SQL 내에서 DDL 문장 수행하기 예제
모든 테이블 각각에 대해 백업 테이블 만들기
1. SYS 유져로 접속해서 c##scott 유져에게 다음의 권한을 부여합니다.
-- SYS 유져로 접
C:\Users\itwill>sqlplus sys/oracle_4U as sysdba
-- SELECT 권한 부여GRANTSELECTON user_tables TO C##SCOTT;
-- CREATE TABLE 권한 부여GRANTCREATE TABLETO C##SCOTT;
2. c##scott 유져로 접속해서 테이블을 생성합니다.
create table emp_backup
asselect*from emp;
select*from emp_backup;
droptable emp_backup;
3. c##scott 유져로 접속해서 아래의 프로시져를 생성합니다.
CREATEOR REPLACE PROCEDURE create_proc
ISCURSOR emp_cursor IS--레코드변수와 커서 선언SELECT table_name --해당 select문을 emp_cursor라는 이름으로FROM user_tables; --메모리에 올려놓음
v_stmt VARCHAR2(100);
BEGINFOR emp_record IN emp_cursor LOOP
--레코드변수에 emp_cursor에 있는 행 하나씩 할당됨
v_stmt :='create table '|| emp_record.table_name ||'_backup as select * from '|| emp_record.table_name ;
/* 들어온 첫번째 테이블부터 순서대로 emp_record.table_name에 들어가고 출력하고
sql을 exexcute immediate절로 실행한 sql */
DBMS_OUTPUT.PUT_LINE(v_stmt);
EXECUTE IMMEDIATE v_stmt;
END LOOP;
END;
/SQL>set serveroutput onSQL>exec create_proc;
*1행에 오류:
ORA-00955: 기존의 객체가 이름을 사용하고 있습니다.
ORA-06512: "C##SCOTT.CREATE_PROC", 16행
ORA-06512: "C##SCOTT.CREATE_PROC", 16행
ORA-06512: 1행
=> 따라서 흔하지 않는 이름으로 변경
CREATEOR REPLACE PROCEDURE create_proc
ISCURSOR emp_cursor ISSELECT table_name
FROM user_tables;
v_stmt VARCHAR2(100);
BEGINFOR emp_record IN emp_cursor LOOP
v_stmt :='create table '|| emp_record.table_name ||'_backup100 as select * from '|| emp_record.table_name ;
--100붙임..!
DBMS_OUTPUT.PUT_LINE(v_stmt);
EXECUTE IMMEDIATE v_stmt;
END LOOP;
END;
/SQL>exec create_proc;
create table ORDER2_backup100 asselect*from ORDER2
create table ORDER3_backup100 asselect*from ORDER3
create table CRIME_TIME2_backup100 asselect*from CRIME_TIME2
create table CRIME_TIME_backup100 asselect*from CRIME_TIME
create table TELECOM_TABLE_backup100 asselect*from TELECOM_TABLE
create table BONUS_backup100 asselect*from BONUS
create table EMP55_backup100 asselect*from EMP55
create table EMP56_backup100 asselect*from EMP56
create table EMP57_backup100 asselect*from EMP57
...
create table EMP18_backup100 asselect*from EMP18
create table EMP_TEMP3_backup100 asselect*from EMP_TEMP3
create table EMP_TEMP4_backup100 asselect*from EMP_TEMP4
create table DEPT_TEMP_backup100 asselect*from DEPT_TEMP
create table SYS_TEMP_FBT_backup100 asselect*from SYS_TEMP_FBT
create table ERROR_backup100 asselect*from ERROR
create table TEST2_backup100 asselect*from TEST2
PL/SQL 처리가 정상적으로 완료되었습니다.
※ 프로시져를 실행했는데 만약 권한이 없다는 오류가 나면 프로시져를 다음과 같이 다시 생성해주세요.
CREATEOR REPLACE PROCEDURE create_proc
authid current_user-- 이 명령어 사ISCURSOR emp_cursor ISSELECT table_name
FROM user_tables;
v_stmt VARCHAR2(100);
BEGINFOR emp_record IN emp_cursor LOOP
v_stmt :='create table '|| emp_record.table_name ||'_backup as select * from '|| emp_record.table_name ;
DBMS_OUTPUT.PUT_LINE(v_stmt);
EXECUTE IMMEDIATE v_stmt;
END LOOP;
END;
/4. 잘 만들어졌는지 확인합니다.
select table_name
from user_tables
where table_name like'%BACKUP%';
SQL>selectcount(*)
2from user_tables -- 내가 소유하고 있는 테이블 조회하는 사전3where table_name like'%BACKUP%'; --BACKUP 대문자로 작성COUNT(*)
----------149
문제풀며 익히기
Q1.
테이블명에 BACKUP 이라는 단어를 포함하고 있는 테이블들을 모두 DROP 하는 프로시져를 생성하세요(주의: 반드시 SCOTT에서 해야함. SYS에서 하면 안됨- SHOW USER로 확인)
출력예시
SQL> exec drop_proc;
drop table TEST_ORIGINAL_BACKUP
drop table TEST1_BACKUP
drop table EMP_BACKUP
drop table DEPT_BACKUP
drop table ERROR_BACKUP
drop table NORMAL_SAL_BACKUP
drop table HIGH_SAL_BACKUP
drop table LOG_TABLE_BACKUP
drop table TEST2_BACKUP
drop table DAILY_MACHUL_BACKUP
drop table EMP_T_BACKUP
SQL 쿼리의 대상이 되는 테이블이나 열 이름이 런타임에 결정되는 경우에는
동적 SQL이 필요합니다.
※ "런타임" 의 뜻은 프로그램이 실행 중인 시간을 가리킵니다.
동적 SQL을 사용하는 상황에서 "런타임"은 프로그램이 실행 중일 때,
즉 코드가 실행되고 데이터베이스와 상호작용할 때를 의미합니다.
-------------------------------------------------------------
테이블이름이나 열 이름이 결정되는 경우가 보통
프로시져 생성 코드 작성 시 결정되었는데,
런타임시에 테이블이름이나 컬럼명을 받을 수 있다는 것.
즉, 이미 생성이 되었는데, execute할때 테이블이름이나 열이름을 넣어서
생성하면서 실행할 수 있다는 것.
select문이 달라지면서 바뀌게 할 수도 있음
동적인 테이블 생성 또는 스키마 변경(테이블 변경):
데이터베이스 객체를 생성, 수정 또는 삭제해야 하는 경우 동적 SQL이 필요
DDL문: create, alter ,drop ,truncate, rename
pl/sql에서 DDL문장을 사용하는 경우에
exeute immediate를 사용함
PL/SQL에서 EXECUTE IMMEDIATE를 사용하는 상황
1. 입력되는 값에 따라서 입력값이 달라져야하는 경우
2. pl/sql내에서 DDL문장을 사용하는 경우
쿼리문을 동적으로 결정해야 하는 경우 ( 가장중요 ★ ):
테이블에 입력되어 있는 데이터에 따라서 쿼리문이 런타임시에 동적으로 구성되어야 할 때 필요합니다.
SELECT deptno, avg( decode( job, 'ANALYST', sal, null ) ) as ANALYST,
avg( decode( job, 'CLERK', sal, null ) ) as CLERK,
avg( decode( job, 'MANAGER', sal, null ) ) as MANAGER,
avg( decode( job, 'PRESIDENT', sal, null ) ) as PRESIDENT,
avg( decode( job, 'SALESMAN', sal, null ) ) as SALESMAN
FROM emp
GROUPBY deptno;
DEPTNO ANALYST CLERK MANAGER PRESIDENT SALESMAN
---------- ---------- ---------- ---------- ---------- ----------1013002450500030950285014002030009502975
077. SQL 자동화7 (Dynamic SQL7)
📖
Dynamic SQL7
1. 아래의 SQL 로 작성해서 사용했을 때 불편한점은 무엇일까요?
select job, sum( decode(deptno, 10, sal, null) ) as "dept10",
sum( decode(deptno, 20, sal, null) ) as "dept20",
sum( decode(deptno, 30, sal, null) ) as "dept30"
from emp
groupby job;
--부서가 추가되면 계속 데이터를 직접 작성해서 추가해줘야2. 위의 SQL 을 PL/SQL의 다이나믹 SQL 로 구현하고 실행하세요.
CREATEOR REPLACE PROCEDURE job_deptno_sum
( p_x OUT sys_refcursor )
/* OUT을 사용함..! 즉 P_X변수는 출력데이터를 담을 출력매개변수인것
SYS_REFCURSOR : 테이블 형태의 변수 타입을 의미함
왜? 출력용 매개변수는 테이블 형태의 결과여야함.즉 프로시져 수행 결과가
테이블 형태로 출력되어야하는 것*/IS
l_query VARCHAR2(400) :='SELECT job ';
BEGINFOR x IN ( SELECTDISTINCT deptno FROM emp orderby deptno ) LOOP
/* 서브쿼리 = CURSOR
즉, 서브쿼리의 결과가 커서 메모리로 업로드 됨
커서에서 데이터를 하나씩 불러 X 에 넣음 */
l_query := l_query ||', SUM(DECODE(deptno, '|| x.deptno ||',sal, null ))
as dept'|| x.deptno;
/* 1.
커서메모리에서 부서번호 10번이 들어왔다면,
l_query = select job || ~~~ ||
x.deptno = 10
따라서
select job, sum(decode(deptno, 10, sal, null)) as dept10 이 만들어져서
l_query에 할당됨
2.
커서메모리에서 부서번호 20번이 들어왔다면,
l_query = select job, sum(decode(deptno, 10, sal, null)) as dept10
x_deptno = 20
따라서 최종적으로
l_query := select job, sum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
3.
커서메모리에서 부서번호 30번이 들어왔다면,
l_query = select job, sum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
x_deptno = 30
따라서 최종적으로
l_query := select job, sum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
, sum(decode(deptno, 30, sal, null)) as dept30 */END LOOP;
l_query := l_query ||' FROM emp GROUP BY job ';
/*
l_query = select job, sum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
, sum(decode(deptno, 30, sal, null)) as dept30
따라서 최종적으로
l_query := select job, sum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
, sum(decode(deptno, 30, sal, null)) as dept30
FROM emp GROUP BY job */OPEN p_x FOR l_query;
/* 결과적으로 l_query를 가지고 p_x를 여는데,
p_x는 출력매개변수이며, sys_refcursor에 의해 테이블형태의 변수이다.
따라서 l_query를 실행한 결과를 테이블 형태의 출력용 매개변수 p_x에 담게 됨 */END;
/--실행 및 결과SQL> variable x refcursor;
/* 실행하기 전 값을 담을 호스트 변수 x를 만들어주는데,
refcursor : 테이블형태의 변수를 선언할 때 쓰는 데이터 타입임 */SQL>exec job_deptno_sum(:x);
/* x에 담긴 최종 실행된 결과를 바인딩하여 exec하는 것 */
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> print x;
JOB DEPT10 DEPT20 DEPT30
------------------ ---------- ---------- ----------
PRESIDENT 5000
MANAGER 245029752850
SALESMAN 5600
CLERK 13001900950
ANALYST 6000
문제풀며 익히기
Q1.
위의 스크립트를 수정해서 아래의 sql의 결과를 출력하시오.
selectsum(decode(deptno, 10, sal, null)) as dept10
, sum(decode(deptno, 20, sal, null)) as dept20
, sum(decode(deptno, 30, sal, null)) as dept30
FROM emp;
set serveroutput onCREATEOR REPLACE PROCEDURE deptno_sum
( p_x OUT sys_refcursor )
IS
l_query VARCHAR2(400) :='SELECT ';
BEGINFOR x IN ( SELECTDISTINCT deptno FROM emp orderby deptno ) LOOP
l_query := l_query ||', SUM(DECODE(deptno, '|| x.deptno ||',sal, null )) as dept'|| x.deptno;
END LOOP;
l_query := l_query ||' FROM emp ';
dbms_output.put_line( l_query );
OPEN p_x FOR l_query;
END;
/!!ERROR!!
쉼표 위치에 의해 에러 발생
set serveroutput onCREATEOR REPLACE PROCEDURE deptno_sum
( p_x OUT sys_refcursor )
IS
l_query VARCHAR2(400) :='SELECT ';
BEGINFOR x IN ( SELECTDISTINCT deptno FROM emp orderby deptno ) LOOP
l_query := l_query ||' SUM(DECODE(deptno, '|| x.deptno ||',sal, null ))
as dept'|| x.deptno ||',' ;
END LOOP;
l_query := rtrim(l_query, ',' );
l_query := l_query ||' FROM emp ';
dbms_output.put_line( l_query );
OPEN p_x FOR l_query;
END;
/SQL> variable x refcursor;
SQL>exec deptno_sum(:x);
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> print x;
DEPT10 DEPT20 DEPT30
---------- ---------- ----------8750108759400
답:
CREATEOR REPLACE PROCEDURE pivot_hiredate_sal
(p_x OUT sys_refcursor)
--테이블 형태의 출력매개변수 (직업별, 입사년도별 토탈월급이 담겨질것임)IS
l_query VARCHAR2(2000) :='SELECT job';
/* l_query는 문자형 스칼라변수 */BEGINFORyearIN (SELECTDISTINCT TO_CHAR(hiredate, 'RRRR') AS hire_year
FROM emp ORDERBY hire_year) LOOP
/* YEAR는 레코드변수이며, 커서 선언절은 생략하고 서브쿼리에 커서를 선언
HIRE_YEA
--------
1980
1981
1982
1983 이 결과가 커서 메모리에 넣어주고, 이 결과가 하나씩
YEAR라는 레코드 변수에 들어갈 것임*/
l_query := l_query ||', SUM(DECODE(TO_CHAR(hiredate, ''RRRR''),
'''|| year.hire_year ||''', sal, NULL))
AS "'|| year.hire_year ||'"';
/* 싱글 하나를 표현하기 위해 싱글 두개를 사용
<''RRRR''> : 싱글 두개가 싱글 하나를 의미함
<''' || year.hire_year || '''>
앞의 '''세개 중 마지막 것은
맨앞의 ', SUM(DECODE(~ 에 있는 싱글쿼테이션과 짝지어서 문자라는 것을 표현
해준 전체 싱글 쿼테이션 마크이고, 닫기 전에
뒤에 || year.hire_year || 는 문자형이기 때문에 그것을 싱글쿼테이션으로
둘러주기 위해 ''을 넣은것. ''는 하나의 싱글쿼테이션 마크와 동일한 의미
<''', sal, NULL)) AS "'>
'''중 마지막 *//* 최종적으로
select job, sum(decode( to_char(hiredate, 'rrr'),
'1980', sal, null )) as "1980",
sum(decode( to_char(hiredate, 'rrr'),
'1981', sal, null )) as "1981",
sum(decode( to_char(hiredate, 'rrr'),
'1982', sal, null )) as "1982",
sum(decode( to_char(hiredate, 'rrr'),
'1983', sal, null )) as "1983"
*/END LOOP;
l_query := l_query ||' FROM emp GROUP BY job ';
OPEN p_x FOR l_query;
END;
/
VARIABLE x REFCURSOR;
--테이블 형태의 호수트 변수를 선언(선언위해 앞에 variable 넣어줘야함)EXEC pivot_hiredate_sal(:x);
PRINT x;
JOB 1980198119821983------------------ ---------- ---------- ---------- ----------
PRESIDENT 5000
MANAGER 8275
SALESMAN 5600
CLERK 80095013001100
ANALYST 30003000
CREATEOR REPLACE PROCEDURE pivot_hiredate_sal
(p_x OUT sys_refcursor)
IS
l_query VARCHAR2(2000) :='SELECT deptno';
BEGINFORyearIN (SELECTDISTINCT TO_CHAR(hiredate, 'RRRR') AS hire_year FROM emp ORDERBY hire_year) LOOP
l_query := l_query ||', AVG(DECODE(TO_CHAR(hiredate, ''RRRR''), '''|| year.hire_year
||''', sal, NULL)) AS "'|| year.hire_year ||'"';
END LOOP;
l_query := l_query ||' FROM emp GROUP BY deptno ';
OPEN p_x FOR l_query;
END;
/SQL> VARIABLE x REFCURSOR;
SQL>EXEC pivot_hiredate_sal(:x);
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> PRINT x;
DEPTNO 1980198119821983---------- ---------- ---------- ---------- ----------1037251300301566.66667208002987.530001100
싱글쿼테이션 마크는 두개가 하나임
또한 연결연산자 직전이 아니라 문자열 싱글쿼테이션 표시 닫기 전에 넣어주거나
열은 직후 넣어줘야함.
각 연결연산자 마다 끊어져서 매번 문자형 표현하는 싱글쿼테이션 마크 필요함
Q2. (SQL문제)
우리반 테이블에서 성별별 통신사별 평균 나이를 출력하시오
SELECT gender, ROUND(AVG( DECODE( telecom, 'kt', age, null) ) )as kt,
ROUND(AVG( DECODE( telecom, 'lg', age, null) ) )as lg,
ROUND(AVG( DECODE( telecom, 'sk', age, null) ) ) as sk
FROM emp18
GROUPBY gender;
GENDER KT LG SK
-------------------- ---------- ---------- ----------
남 262728
여 272832
Q3. (PL/SQL 문제)
위의 결과를 출력하는 프로시져를 pivot_gender_telecom 라는 이름으로 생성하시오
CREATEOR REPLACE PROCEDURE pivot_gender_telecom
(p_x OUT sys_refcursor)
IS
l_query VARCHAR2(2000) :='SELECT gender';
BEGINFOR t IN (SELECTDISTINCT telecom FROM emp18 where telecom isnot nullorderby telecom ) LOOP
l_query := l_query ||', ROUND(AVG(DECODE(telecom, '''|| t.telecom
||''', age, NULL))) AS '|| t.telecom ;
END LOOP;
l_query := l_query ||' FROM emp18 GROUP BY gender ';
OPEN p_x FOR l_query;
END;
/SQL> variable x refcursor;
SQL>exec pivot_gender_telecom(:x);
SQL> print x;
GENDER KT LG SK 알뜰KT
-------------------- ---------- ---------- ---------- ----------
남 26272831
여 272832
080. SQL 자동화10 (Dynamic SQL10)
📖
Dynamic SQL10
예제) 직업별, 부서위치별 토탈월급을 출력하세요
select e.job, sum( decode(d.loc, 'NEW YORK', e.sal, null)) as "NEW YORK",
sum( decode(d.loc, 'DALLAS', e.sal, null)) as "DALLAS",
sum( decode(d.loc, 'CHICAGO', e.sal, null)) as "CHICAGO",
sum( decode(d.loc, 'BOSTON', e.sal, null)) as "BOSTON"
from emp e, dept d
where e.deptno = d.deptno
groupby e.job;
예제2) 위의 SQL 을 프로시져로 생성해서 수행될 수 있도록 하시오
CREATEOR REPLACE PROCEDURE pivot_loc_sal
(p_x OUT sys_refcursor)
IS
l_query VARCHAR2(4000) :='SELECT e.job';
BEGINFOR x IN (SELECT loc from dept ) LOOP
/* dept테이블에서 부서위치를 출력하는 select문의 결과가 커서로 만들어지고
그 데이터가 레코드변수 x에 들어감
LOC
--------------------------
NEW YORK
DALLAS
CHICAGO
BOSTON */
l_query := l_query ||', SUM(DECODE(d.loc, '''|| x.loc ||''', e.sal, null)) AS "'|| x.loc ||'"';
/* 최종적으로 만들어진것
l_query :=
select e.job, sum(decode(d.loc, 'NEW YORK', e.sal, null)) as "NEW YORK",
sum( decode(d.loc, 'DALLAS', e.sal, null)) as "DALLAS",
sum( decode(d.loc, 'CHICAGO', e.sal, null)) as "CHICAGO",
sum( decode(d.loc, 'BOSTON', e.sal, null)) as "BOSTON" */END LOOP;
l_query := l_query ||' FROM emp e, dept d WHERE e.deptno
= d.deptno GROUP BY e.job ORDER BY e.job';
/* 진짜 최종
l_query :=
select e.job, sum( decode(d.loc, 'NEW YORK', e.sal, null)) as "NEW YORK",
sum( decode(d.loc, 'DALLAS', e.sal, null)) as "DALLAS",
sum( decode(d.loc, 'CHICAGO', e.sal, null)) as "CHICAGO",
sum( decode(d.loc, 'BOSTON', e.sal, null)) as "BOSTON"
from emp e, dept d
where e.deptno = d.deptno
group by e.job;
JOB NEW YORK DALLAS CHICAGO BOSTON
------------------ ---------- ---------- ---------- ----------
PRESIDENT 5000
MANAGER 2450 2975 2850
SALESMAN 5600
CLERK 1300 1900 950
ANALYST 6000 */OPEN p_x FOR l_query;
--쿼리 실행 결과가 p_x에 담김END;
/
VARIABLE x REFCURSOR; --호스트 변수 선언EXEC pivot_loc_sal(:x); --실행
PRINT x; --출력
JOB NEW YORK DALLAS CHICAGO BOSTON
------------------ ---------- ---------- ---------- ----------
ANALYST 6000
CLERK 13001900950
MANAGER 245029752850
PRESIDENT 5000
SALESMAN 5600
문제풀며 익히기
Q1.
이번에는 아래의 SQL의 결과가 프로시져로 자동으로 생성될 수 있도록 하세요.
select e.job, sum( decode( d.dname, 'ACCOUNTING', e.sal, null ) ) as "ACCOUNTING",
sum( decode( d.dname, 'RESEARCH', e.sal, null ) ) as "RESEARCH",
sum( decode( d.dname, 'SALES', e.sal, null ) ) as "SALES",
sum( decode( d.dname, 'OPERATIONS', e.sal, null ) ) as "OPERATIONS"
from emp e, dept d
where e.deptno = d.deptno
groupby e.job;
SQL>select*from dept;
DEPTNO DNAME LOC
-------- ---------------------------- --------------------------10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> VARIABLE x REFCURSOR;
SQL>EXEC pivot_dname_sal(:x);
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> PRINT x;
JOB ACCOUNTING RESEARCH SALES OPERATIONS
------------------ ---------- ---------- ---------- ----------
ANALYST 6000
CLERK 13001900950
MANAGER 245029752850
PRESIDENT 5000
SALESMAN 5600
CREATEOR REPLACE PROCEDURE pivot_dname_sal
(p_x OUT sys_refcursor)
IS
l_query VARCHAR2(4000) :='SELECT e.job';
BEGINFOR x IN (SELECT dname from dept ) LOOP
l_query := l_query ||', SUM(DECODE(d.dname, '''|| x.dname ||''', e.sal, null)) AS "'|| x.dname ||'"';
END LOOP;
l_query := l_query ||' FROM emp e, dept d WHERE e.deptno = d.deptno GROUP BY e.job ORDER BY e.job';
OPEN p_x FOR l_query;
END;
/
VARIABLE x REFCURSOR;
EXEC pivot_dname_sal(:x);
PRINT x;
JOB ACCOUNTING RESEARCH SALES OPERATIONS
------------------ ---------- ---------- ---------- ----------
ANALYST 6000
CLERK 13001900950
MANAGER 245029752850
PRESIDENT 5000
SALESMAN 5600
081. chatgpt 와 함께 PL/SQL 오류 찾기
📖
chatgpt 와 함께 PL/SQL 오류 찾기
chatgpt를 이용하면 빠르고 편하게 코드 에러의 원인을 찾을 수 있습니다.
■ 실습1
chatgpt 사이트로 접속합니다.
https://chat.openai.com/
chatgpt 에게 아래와 같이 질문을 합니다.
다음의 PL/SQL 코드가 에러가 나는데 이유가 뭐지 ?
createor replace procedure pro1
( p_deptno emp.deptno%type )
is
v_deptno emp.deptno%type := p_deptno;
v_sumsal number(10,2) ;
beginselectsum(sal) int v_sumsal
from emp
where deptno = v_deptno ;
dbms_output.put_line ( '토탈월급은 : '|| v_sumsal );
end;
/
문제풀며 익히기
Q1.
다음 코드의 에러의 원인을 chatgpt 와 함께 해결해보세요
다음의 코드의 에러의 원인을 알려줘
CREATEOR REPLACE FUNCTION fun178
( p_ename emp.ename%TYPE )
RETURN NUMBER
AS
v_rank NUMBE(5);
BEGINSELECT 순위 INTO v_rank
FROM (
SELECT ename, DENS_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
RETURN v_ran;
END;
/
주어진 코드에서 다수의 오류가 발견되었습니다. 아래는 코드의 수정된 버전과 함께 각각의 오류에 대한 설명입니다:
```sqlCREATEOR REPLACE FUNCTION fun178
( p_ename emp.ename%TYPE )
RETURN NUMBER
AS
v_rank NUMBER(5); -- 오류 1: NUMBE를 NUMBER로 수정BEGINSELECT 순위 INTO v_rank -- 오류 2: 순위를 v_rank로 수정FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위 -- 오류 3: DENS_RANK를 DENSE_RANK로 수정FROM emp
)
WHERE ename =UPPER(p_ename);
RETURN v_rank;
END;
/
```
주요 수정 내용은 다음과 같습니다:
1.**데이터 타입 오류:** `v_rank` 변수의 데이터 타입을 `NUMBE(5)`에서 `NUMBER(5)`로 수정하였습니다.
2.**변수명 오류:** `SELECT 순위 INTO v_rank`에서 `순위`를 `v_rank`로 수정하였습니다.
3.**함수명 오류:** `DENS_RANK()`를 `DENSE_RANK()`로 수정하였습니다. 올바른 함수명을 사용해야 합니다.
이제 수정된 코드는 정상적으로 실행될 것입니다.
082. chatgpt 를 활용하여 PL/SQL 튜닝하기
📖
chatgpt 를 활용하여 PL/SQL 튜닝하기
저희는 앞에 예제63번에서 for loop 문을 forall 문으로 변경하는 PL/SQL 튜닝을 배웠습니다.
이 예제를 chatgpt 에게 물어보며 PL/SQL튜닝을 해보도록 하겠습니다.
예제63번. PL/SQL 튜닝7 (for loop문 vs forall 문 문법 비교)
for loop문 : 반복문을 여러번 반복해서 수행한다.
forall 문 : 반복문을 1번만 수행한다.
▣ for loop문과 forall loop문의 문법 비교
1. 관련 테이블 생성
droptable test1 purge;
droptable test2 purge;
create table test1 (c1 number);
create table test2 (c1 number);
2.for loop문을 사용한 프로시져 생성
SQL>createor replace procedure for_pro
isbeginfor i in1 .. 100000 loop
insert/*+ FOR */into test1 --10만번 반values (i);
end loop;
end;
/SQL>exec for_pro;
PL/SQL 처리가 정상적으로 완료되었습니다.
경 과: 00:00:01.21SQL>selectcount(*) from test1;
COUNT(*)
----------1000003. forall loop문을 사용한 프로시져 생성
SQL>createor replace procedure for_all_pro
is
type tnt istableof pls_integer;
l_v1 tnt := tnt ();
-- l_v1이라는 중첩 테이블을 만들고, := tnt()로 초기화 시킴beginfor i in1 .. 100000 loop
l_v1.extend;
l_v1(i) := i;
end loop;
--1부터 10만까지의 숫자를 for loop문으로 l_v1중첩 테이블 변수에 할당 함
forall i in l_v1.first .. l_v1.last
insert/*+ FOR_ALL */into test2
values (l_v1(i));
end;
/4. 두 프로시져의 수행 속도 차이 확인
set timing onexec for_pro;
경 과: 00:00:01.21exec for_all_pro;
경 과: 00:00:00.03set timing off
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR */%';
SQL>
col sql_text for a20
SELECT sql_id, sql_text, executions, program_line#
FROM v$sqlWHERE sql_text LIKE'INSERT /*+ FOR_ALL */%';
문제풀며 익히기
Q1.
083. chatgpt 와 함께 SQL 작성하기
📖
chatgpt 와 함께 SQL 작성하기
이번에는 chatgpt 와 함께 SQL 작성을 해보도록 하겠습니다
#1. chatgpt 홈페이지로 접속합니다.
https://chat.openai.com/
#2. 다음과 같이 질문을 합니다.
직업(job) 이 SALESMAN이고 월급이 1000 이상인 사원들의 이름과 월급과 직업을 출력해줘.
아래의 스크립트를 이용하면 되.
CREATE TABLE EMP (
EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4) ,
HIREDATE DATE,
SAL NUMBER(7,2),
COMM NUMBER(7,2),
DEPTNO NUMBER(2) );
INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,'81-11-17',5000,NULL,10);
INSERT INTO EMP VALUES (7698,'BLAKE','MANAGER',7839,'81-05-01',2850,NULL,30);
INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,'81-05-09',2450,NULL,10);
INSERT INTO EMP VALUES (7566,'JONES','MANAGER',7839,'81-04-01',2975,NULL,20);
INSERT INTO EMP VALUES (7654,'MARTIN','SALESMAN',7698,'81-09-10',1250,1400,30);
INSERT INTO EMP VALUES (7499,'ALLEN','SALESMAN',7698,'81-02-11',1600,300,30);
INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,'81-08-21',1500,0,30);
INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,'81-12-11',950,NULL,30);
INSERT INTO EMP VALUES (7521,'WARD','SALESMAN',7698,'81-02-23',1250,500,30);
INSERT INTO EMP VALUES (7902,'FORD','ANALYST',7566,'81-12-11',3000,NULL,20);
INSERT INTO EMP VALUES (7369,'SMITH','CLERK',7902,'80-12-09',800,NULL,20);
INSERT INTO EMP VALUES (7788,'SCOTT','ANALYST',7566,'82-12-22',3000,NULL,20);
INSERT INTO EMP VALUES (7876,'ADAMS','CLERK',7788,'83-01-15',1100,NULL,20);
INSERT INTO EMP VALUES (7934,'MILLER','CLERK',7782,'82-01-11',1300,NULL,10);
commit;
SELECT ENAME, SAL, JOB
FROM EMP
WHERE JOB ='SALESMAN'AND SAL >=1000;
문제풀며 익히기
Q1. (chat GPT활용)
사원(emp) 테이블에서 최대월급을 받는 사원들의 이름과 월급을 출력해줘
SELECT ENAME, SAL
FROM EMP
WHERE SAL = (SELECTMAX(SAL) FROM EMP);
Q2. (chat GPT활용)
사원(emp) 테이블에서 이름과 월급과 월급에 대한 순위를 출력해줘
SELECT ENAME, SAL, DENSE_RANK() OVER (ORDERBY SAL DESC) AS SALARY_RANK
FROM EMP;
4장. 트리거(Trigger)
💡
트리거의 종류?
DML 문 트리거(before trigger, after trigger, instead of trigger)
DDL 문 트리거
DB 의 작업 ( startup, shutdown ) 을 위한 트리거
startup : 누가 올리고
shutdown : 누가 내렸는지 기록을 남길 수 있음
083. DML 문의 before 트리거의 예제 ( DML 문의 작업하기 전의 트리거 )
📖
트리거
-두 테이블 사이의 데이터를 서로 동기화 할 때 사용
-방아쇠, 발생하다
-특정 조건이 발생했을 때 자동으로 특정 이벤트를 실행하는 DB OBJECT
CREATEOR REPLACE TRIGGER [트리거 명]
BEFORE UPDATEON [테이블명]
CREATEOR REPLACE TRIGGER [트리거 명]
BEFORE UPDATEOF [컬럼명] ON [테이블명]
FOREACHROW/* UPDATE를 사용했기 때문에, UPDATE하지 못하도록 트리거 생성한것
EMP 테이블 전체를 갱신 못하게 하려면 ON 사용
특정 컬럼을 갱신하지 못하게 하려면
OF [특정컬럼] ON [테이블명] FOR EACH ROW
- FOR EACH ROW: 특정 조건의 행만 갱신 못하게 할 수 있음 */
DML 문 트리거
before 트리거
after 트리거
instead of 트리거
트리거의 필요성
DBA입장
누군가가 우리회사 인사 DB(월급테이블)에 접속해서 자신의 월급을 300만원에서 900만원으로 갱신하는 EVENT가 발생했다면,
트리거를 이용해서 갱신한 행이 어떤것이고, 어떻게 갱신했으며, 어느 컴퓨터 자리에서 갱신했는지에 대한 그 이력을 특정 테이블에 입력되게 할 수 있다.
개발자 입장
영화를 예매하는 순간, 관련 데이터가 여러개의 테이블에 동시에 입력되어서 계산되고, 합산되어 총 매출액을 자동으로 출력하는 시스템을 만들 때 트리거가 사용됨
ex) 실시간으로 insert되는 예매 테이블이 존재하는데, 이 테이블을 동기화하는 테이블 b를 만들어 (동시에 입력되게 하는) 실시간으로 계산되게 하여 화면에 뿌려줌
OGG(Oracle Golden Gate) - 공급이 거의 없기 때문에 연봉 높음
ogg의 기능 중에 데이터 동기화, 데이터 이행이 있음
트리거를 쉽게 사용할 수 있게 tool을 만들어둠
골든게이트의 데이터 동기화 기능의 핵심 엔진 코드가 트리거
예제)
사원 테이블에 토요일과 일요일에 데이터 수정작업을 못하도록 트리거를 생성하시오
CREATEOR REPLACE TRIGGER secuer_emp
BEFORE UPDATEON emp --emp는 테이블 명/* UPDATE를 사용했기 때문에, UPDATE하지 못하도록 트리거 생성한것
EMP 테이블 전체를 갱신 못하게 하려면 ON 사용
특정 컬럼을 갱신하지 못하게 하려면
OF [특정컬럼] ON [테이블명] FOR EACH ROW
- FOR EACH ROW: 특정 조건의 행만 갱신 못하게 할 수 있음 */BEGIN
IF ( TO_CHAR(SYSDATE, 'DY') IN ('토', '일', '목') ) THEN
RAISE_APPLICATION_ERROR ( -20500, '토요일 일요일 목요일에 일하지 마세요' );
/* if 현재 요일이 토,일,목 중 하나면,
RAISE_APPLICATION_ERRO : 에러를 발생시켜라
-20500 : 에러번호 사용자 지정한 것. 자유롭게 하 */END IF;
END;
/UPDATE emp
SET sal = sal *2WHERE ename ='SCOTT';
!!ERROR!!1행에 오류:
ORA-20500: 토요일 일요일 목요일에 일하지 마세요
ORA-06512: "C##SCOTT.SECUER_EMP", 4행
ORA-04088: 트리거 'C##SCOTT.SECUER_EMP'의 수행시 오류
==============================
위 쿼리를 실행한 날짜가 목요일이기 때문에 위와 같이 에러 발생됨
문제풀며 익히기
Q1.
목요일에 사원 테이블을 delete 또는 insert 할 때 에러가 아래와 같이 출력되게
트리거를 생성하시오
출력예시 : 사원 테이블에 아무런 작업을 하지 마세요 ~~
CREATEOR REPLACE TRIGGER tg_242
BEFORE DELETEORINSERTON emp
/* emp테이블에 delete나 insert를 하기 전(BEFORE)에 작동하는 trigger 를 의미함 */BEGIN
IF ( TO_CHAR(SYSDATE, 'DY') = ('') ) THEN
RAISE_APPLICATION_ERROR ( -20501, '사원 테이블에 아무런 작업을 하지 마세요~~ ' );
END IF;
END;
/INSERT INTO emp (ename)
VALUES ('SCOTT');
DELETEFROM emp
WHERE ename ='SCOTT';
================================SQL>INSERT INTO emp (ename)
2VALUES ('SCOTT');
INSERT INTO emp (ename)
*1행에 오류:
ORA-20501: 사원 테이블에 아무런 작업을 하지 마세요~~
ORA-06512: "C##SCOTT.TG_242", 4행
ORA-04088: 트리거 'C##SCOTT.TG_242'의 수행시 오류
SQL>DELETEFROM emp
2WHERE ename ='SCOTT';
DELETEFROM emp
*1행에 오류:
ORA-20501: 사원 테이블에 아무런 작업을 하지 마세요~~
ORA-06512: "C##SCOTT.TG_242", 4행
ORA-04088: 트리거 'C##SCOTT.TG_242'의 수행시 오류
CREATEOR REPLACE TRIGGER restrict_sal
BEFORE UPDATEOF sal ON emp
FOREACHROWBEGIN
IF :NEW.sal >=9000THEN/* :NEW라는 바인드 변수를 사용하게 되면, EMP테이블에 새로 갱신되는 월급이
바인드변수에 자동으로 잡혀서 들어오게 됨 */
RAISE_APPLICATION_ERROR ( -20502, '월급은 9000이상으로 수정할 수 없습니다.' );
END IF;
END;
/----------------------------------CHECK제약은 무조건 갱신 못하게 하는 것이고
TRIGGER는 다양한 IF문을 넣어줄 수 있음
Q3.
SCOTT의 월급을 5000으로 수정하시오
SQL>UPDATE EMP
2SET SAL =50003WHERE ENAME ='SCOTT';
1 행이 업데이트되었습니다.
SQL>select sal
2from emp
3where ename ='SCOTT';
SAL
----------5000
사원 테이블에 부서번호를 10번, 20번, 30번 외에 다른 부서번호로 갱신하지 못하게 트리거를 생성하시오
UPDATE emp
SET deptno =70WHERE ename ='KING';
ORA-20192 부서번호를 10번, 20번, 30번 외에 다른 부서번호로는 수정할 수 없어요.
CREATEOR REPLACE TRIGGER restrict_deptno
BEFORE UPDATEOF deptno ON emp
FOREACHROWBEGIN
IF :NEW.deptno NOTIN (10,20,30) THEN
RAISE_APPLICATION_ERROR ( -20192,
'부서번호를 10번, 20번, 30번 외에 다른 부서번호로는 수정할 수 없어요' );
END IF;
END;
/SQL>UPDATE emp
2SET deptno =703WHERE ename ='KING';
UPDATE emp
*1행에 오류:
ORA-20192: 부서번호를 10번, 20번, 30번 외에 다른 부서번호로는 수정할 수 없어요
ORA-06512: "C##SCOTT.RESTRICT_DEPTNO", 4행
ORA-04088: 트리거 'C##SCOTT.RESTRICT_DEPTNO'의 수행시 오류
SQL>DROPTRIGGER RESTRICT_DEPTNO;
트리거가 삭제되었습니다.
SQL>DROPTRIGGER RESTRICT_SAL;
트리거가 삭제되었습니다.
084. DML 문의 after 트리거의 예제 ( DML 문의 작업 후 트리거 )
📖
💡
DML 문 트리거
before 트리거
after 트리거
instead of 트리거
트리거
-두 테이블 사이의 데이터를 서로 동기화 할 때 사용
-방아쇠, 발생하다
-특정 조건이 발생했을 때 자동으로 특정 이벤트를 실행하는 DB OBJECT
CREATEOR REPLACE TRIGGER [트리거 명]
AFTER UPDATEON [테이블명]
CREATEOR REPLACE TRIGGER [트리거 명]
AFTER UPDATEOF [컬럼명] ON [테이블명]
FOREACHROW/* UPDATE를 사용했기 때문에, UPDATE하지 못하도록 트리거 생성한것
EMP 테이블 전체를 갱신 못하게 하려면 ON 사용
특정 컬럼을 갱신하지 못하게 하려면
OF [특정컬럼] ON [테이블명] FOR EACH ROW
- FOR EACH ROW: 특정 조건의 행만 갱신 못하게 할 수 있음 */
AFTER TRIGGER
DML작업이 발생한 (성공한) 후에 작동되는 트리거(EVENT 발생)
예제)
사원 테이블의 월급을 갱신하면 갱신되기 전 월급과 갱신된 후의 월급이
아래의 테이블에 입력되게 하시오
CREATE TABLE audit_emp
(
ename VARCHAR2(10),
old_sal NUMBER(10),
new_sal NUMBER(10),
happen_date DATE
);
CREATEOR REPLACE TRIGGER emp_update
AFTER UPDATEOF sal ON emp
FOREACHROWBEGININSERT INTO audit_emp
VALUES ( :OLD.ename, :OLD.sal, :NEW.sal, SYSDATE );
/* emp 테이블에 update를 하게 되면 audit_emp라는 감사 테이블에
누구의 월급이 갱신되었는지 예전이름과, 예전 월급, 새로운 월급, 날짜
가 입력되게 하는 것 */END;
/UPDATE emp
SET sal = sal *2WHERE ename ='SCOTT';
SELECT*FROM emp
WHERE ename ='SCOTT';
/* 결과
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO BONUS
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 82/12/22 6000 20
*/--> emp 테이블의 데이터가 업데이트 됨 !!SELECT*FROM audit_emp;
/* 결과
ENAME OLD_SAL NEW_SAL HAPPEN_DATE
----- ------- ------- -------------------
SCOTT 3000 6000 2015/04/20 16:18:52
*/--> 트리거가 발생되어 audit_emp 테이블에 업데이트 내용이 반영됨 !
(즉, audit_emp 테이블에 데이터가 삽입됨)
문제풀며 익히기
Q1.
우리반 테이블(emp3)의 나이를 변경할 때, 아래의 테이블에 이름과 변경된 나이와 변경후 나이를 입력하는 트리거를 생성하시오
CREATE TABLE change_age
(
ename VARCHAR2(10),
OLD_AGE NUMBER(10),
NEW_AGE NUMBER(10)
);
CREATEOR REPLACE TRIGGER tg246
AFTER UPDATEOF age ON emp18
FOREACHROWBEGININSERT INTO change_age
VALUES ( :OLD.ename, :OLD.age, :NEW.age );
END;
/-- 트리거 동작 확인하기UPDATE emp18
SET age =100WHERE ename ='오운학';
--> 트리거가 실행되도록 emp3 테이블의 age2 데이터를 변경함.SELECT*FROM change_age;
/* 결과
ENAME OLD_AGE NEW_AGE
-------------------- ---------- ----------
오운학 27 100
*/--> 트리거가 실행되어, change_age 테이블에 데이터가 삽입된 것을 확인할 수 있음.
Q2.
아래의 테이블을 생성하고 emp 테이블을 삭제하는 작업을 하면 삭제된 data 중에 empno, ename, sal 가 아래의 테이블에 입력되게 하는 트리거를 생성하시오
CREATE TABLE emp_delete_audit
(
empno NUMBER(10),
ename VARCHAR2(10),
sal NUMBER(10)
);
CREATEOR REPLACE TRIGGER after_delete_emp
AFTER DELETEON emp
FOREACHROWBEGININSERT INTO emp_delete_audit
VALUES ( :OLD.empno, :OLD.ename, :OLD.sal );
END;
/-- 트리거 동작 확인하기DELETEFROM emp
WHERE empno =7788;
SELECT*FROM emp
WHERE empno =7788;
/* 결과
선택된 레코드가 없습니다.
*/--> 데이터가 지워진것 확인됨 !SELECT*FROM emp_delete_audit;
/* 결과
EMPNO ENAME SAL
---------- ---------- ----------
7788 SCOTT 6000
*/--> 트리거가 발생하여 emp_delete_audit 테이블에 데이터가 삽입된 것을 확인할 수 있음 !
Q3.
dept 테이블과 똑같은 테이블을 생성하시오( 테이블명은 detp900 )
CREATE TABLE dept900
ASSELECT*FROM dept;
SELECT*FROM dept900;
/* 결과
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
*/
Q4.
dept 테이블의 data 를 지우면 dept900 테이블에 같은 data 가 같이 지워지게 트리거를 생성하시오
CREATEOR REPLACE TRIGGER tg249
AFTER DELETEON dept
FOREACHROWBEGINDELETEFROM dept900
WHERE deptno = :OLD.deptno OR
loc = :OLD.loc OR
dname = :OLD.dname;
END;
/SQL>DELETEFROM dept
WHERE deptno =10;
1 행이 삭제되었습니다.
SQL>SELECT*FROM dept900;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>---------------------------------------------------------------CREATEOR REPLACE TRIGGER tg249
AFTER DELETEON dept
FOREACHROWBEGIN
IF DELETING THENWHEREEND;
/
Q5.
이번에는 dept 테이블에 insert를 하면, dept900테이블에도 inser가 되는 trigger를 생성하시오
CREATEOR REPLACE TRIGGER tg249
AFTER DELETEON dept
FOREACHROWBEGININSERT INTO dept900
VALUES( :NEW.deptno, :NEW.dname, :NEW.loc);
END;
/SQL>INSERT INTO DEPT(deptno, dname, loc)
2VALUES(70, 'aaa', 'bbb');
1 개의 행이 만들어졌습니다.
SQL>select*from dept900;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Q6.
지금까지 생성한 트리거를 모두 DROP하시오
SELECT trigger_name
FROM user_triggers;
EMP_UPDATE
TG246
TG249
//트리거 삭제 스크립트 만들기 //SELECT'drop trigger '|| trigger_name ||';'FROM user_triggers;
//아래에 나온 결과값. 복사해서 실행//droptrigger EMP_UPDATE;
droptrigger TG246;
droptrigger TG249;
Q7. (데이터 동기화하기 ★ 많은 회사에서 하고싶어함)
dept테이블에 INSERT와 DELETE를 하면 dept900 테이블에 INSERT와 같은 데이터가 INSERT와 DELETE가 되게 트리거를 생성하시오.
SQL>droptable dept900;
테이블이 삭제되었습니다.
SQL>create table dept900
2as3select*4from dept;
테이블이 생성되었습니다.
CREATEor REPLACE trigger dept_dept900
AFTER DELETEorINSERTON dept --BEFORE/AFTER 상관없FOREACHROWBEGIN
IF DELETING THENDELETEFROM dept900
WHERE deptno = :OLD.deptno or
loc = :OLD.loc or
dname = :OLD.dname;
/* DELETEING이 발생하면 삭제해라
3개의 열 중에 하나의 데이터가 들어와서 지워지면 */
ELSIF INSERTING THENINSERT INTO dept900
VALUES( :NEW.deptno, :NEW.dname, :NEW.loc);
/*새로 들어오는 값을 INSER해야하므로 NEW */END IF;
END;
/=====================================================================
Q8.
dept 테이블의 20번 데이터를 지우면, dept900에도 지워지는지 확인하시오
dept 테이블에 데이터를 입력하면, dept900도 입력되는지 확인하시오.
DELETEFROM dept
WHERE deptno =20;
SELETE *FROM dept900;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------70 aaa bbb
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>INSERT INTO dept(deptno, dname, loc)
VALUES(80, 'aaa', 'bbb');
1 개의 행이 만들어졌습니다.
SQL>select*from dept900;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------70 aaa bbb
30 SALES CHICAGO
40 OPERATIONS BOSTON
80 aaa bbb
Q9.
dept_dept900 트리거를 수정해서 dept테이블의 데이터를 update하면, dept900테이블의 데이터도 update 되게 코드를 수정하시오.
CREATEor REPLACE trigger dept_dept900
AFTER DELETEorINSERTorUPDATEON dept
FOREACHROWBEGIN
IF DELETING THENDELETEFROM dept900
WHERE deptno = :OLD.deptno or
loc = :OLD.loc or
dname = :OLD.dname;
ELSIF INSERTING THENINSERT INTO dept900
VALUES( :NEW.deptno, :NEW.dname, :NEW.loc);
ELSIF UPDATING THENUPDATE dept900
SET deptno = :NEW.deptno,
loc = :NEW.loc,
dname = :NEW.dname
WHERE deptno = :OLD.deptno or
loc = :OLD.loc or
dname = :OLD.dname;
END IF;
END;
/
Q10.
dept 테이블의 10번 부서번호의 loc를 aaaa로 변경하시오
SQL>UPDATE dept
SET loc ='aaaa'WHERE deptno =30;
1 행이 업데이트되었습니다.
SQL>select*from dept900;
DEPTNO DNAME LOC
---------- ---------------------------- --------------------------70 aaa bbb
30 SALES aaaa
40 OPERATIONS BOSTON
80 aaa bbb
085. instead of 트리거 - DBA를 위한 트리거 ★
📖
💡
트리거(trigger) 종류
1. DML 트리거
- before trigger
- after trigger
- instead of trigger ( P 8-26 )
2. DDL 트리거
3. DB 관리에 대한 트리거
☞ intead of trigger 를 사용하는 이유 ?
복합뷰에 data 를 입력하기 위해서 사용한다.
view 의 종류 2가지
ㅤ
단순 view
복합 view
TABLE 개수
1개
2개 이상
그룹함수 group by절
포함 안함
포함
DML 가능 여부
가능
불가능할 수도 있다
예제1)
DALLAS 에서 근무하는 사원들의 이름과 월급과 부서위치와 부서번호를 출력하는 복합뷰를 생성하시오 ! (VIEW 이름은 emp252 )
CREATEOR REPLACE VIEW emp252
ASSELECT e.empno, e.ename, e.sal, d.loc, d.deptno
FROM emp e, dept d
WHERE e.deptno = d.deptno
AND d.loc ='DALLAS';
--복합뷰인 이유, 조인으로 하였기 때문SELECT*FROM emp252;
/* 결과
ENAME SAL LOC DEPTNO
---------- ---------- ------------- ----------
JONES 2975 DALLAS 20
FORD 3000 DALLAS 20
SMITH 800 DALLAS 20
SCOTT 3000 DALLAS 20
ADAMS 1100 DALLAS 20
*/
예제2) emp252 view 의 SMITH 의 월급을 5000 으로 수정하시오
UPDATE emp252
SET sal =5000WHERE ename ='SMITH';
!!ERROR!!19C는 에러남.21C는 에러 안남(21C의 큰 특징)
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
--> 위 문제는 복합뷰를 사용하여 값을 갱신하는 것이 불가능할 수 있음을 보여준다.
예제3) emp252 view 에 data 를 입력하는데 아래의 정보를 입력하시오
입력 값
사원번호 : 9393
사원이름 : JANE
월급 : 4000
부서위치 : DALLAS
INSERT INTO emp252 (empno, ename, sal, loc)
VALUES (9393, 'JANE', 4000, 'DALLAS');
!!ERROR!!
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
-------------------------------------21C도 VIEW에 INSERT는 안됨
--> 위 문제는 복합뷰를 사용하여 값을 입력하는 것이 불가능할 수 있음을 보여준다.
예제4)
사원번호, 이름, 월급, 부서번호, 부서명을 출력하는 view 를 생성하시오 !
(view 이름은 emp255)
CREATEVIEW emp255
ASSELECT e.empno, e.ename, e.sal, e.deptno, d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno;
SELECT*FROM emp255;
/* 결과
EMPNO ENAME SAL DEPTNO DNAME
---------- ---------- ---------- ---------- -------------
7839 KING 5000 10 ACCOUNTING
7698 BLAKE 2850 30 SALES
7782 CLARK 2450 10 ACCOUNTING
7566 JONES 2975 20 RESEARCH
7654 MARTIN 1250 30 SALES
7499 ALLEN 1600 30 SALES
7844 TURNER 1500 30 SALES
7900 JAMES 950 30 SALES
7521 WARD 1250 30 SALES
7902 FORD 3000 20 RESEARCH
7369 SMITH 800 20 RESEARCH
7788 SCOTT 3000 20 RESEARCH
7876 ADAMS 1100 20 RESEARCH
7934 MILLER 1300 10 ACCOUNTING
14 개의 행이 선택되었습니다.
*/
INSERT INTO emp255 (empno, ename, sal, deptno, dname)
VALUES (8999, 'EYKIM', 4000, 10, 'HR');
!!ERROR!!
ORA-01779: 키-보존된것이 아닌 테이블로 대응한 열을 수정할 수 없습니다
VIEW에 INSERT 불가
예제6) 위의 insert 문이 가능하도록 instead of 트리거를 생성하시오
createor replace trigger emp_dept_insert
instead ofinserton emp255
referencingnewas n -- NEW 를 N 으로 별칭해줌.foreachrowdeclare
dept_cnt number;
begin
if :n.empno isnot nulltheninsert into emp(empno, ename, sal, deptno) --원래는 컬럼 전부 써야함values (:n.empno, :n.ename, :n.sal, :n.deptno); --교육목적으로 몇개만 넣음end if;
if :n.deptno isnot nullthenselectcount(*) into dept_cnt
from dept where deptno = :n.deptno;
if dept_cnt >0and (:n.dname isnot null) thenupdate dept set dname = :n.dname where deptno = :n.deptno;
elseinsert into dept(deptno, dname) values(:n.deptno, :n.dname);
end if;
end if;
end;
/INSERT INTO emp255 (empno, ename, sal, deptno, dname)
VALUES (8999, 'EYKIM', 4000, 10, 'HR');
SELECT*FROM emp;
문제풀며 익히기
Q1.
086. DDL 트리거
📖
DDL 트리거 : DDL문장이 실행될 때 작동하는 트리거
- DDL 명령어 ~> CREATE, ALTER, DROP, TRUNCATE, RENAME
예제1)
SCOTT 유저에 테이블을 DROP 할때에 DROP 하지 못하도록 트리거를 생성하시오
CREATEOR REPLACE TRIGGER no_ddl
BEFORE DROPORTRUNCATEON C##SCOTT.SCHEMA
BEGIN
RAISE_APPLICATION_ERROR( -20001, ' DROP 하지 마세요! 저에게 왜 그러시나요?' );
END;
/
트리거가 생성되었습니다.
SQL>droptable emp;
droptable emp
*1행에 오류:
ORA-04088: 트리거 'C##SCOTT.NO_DDL'의 수행시 오류
ORA-00604: 순환 SQL 레벨 1 에 오류가 발생했습니다
ORA-20001: DROP 하지 마세요! 저에게 왜 그러시나요?
ORA-06512: 3행
============================================- SCOTT.SCHEMA : SCOTT이 가지고 있는 모든 객체
문제풀며 익히기
Q1.
사원 테이블의 월급에 인덱스를 생성하시오.
CREATE INDEX emp_sal
ON emp(sal);
Q2.
위의 index를 삭제하시오.
SQL>drop index emp_sal;
drop index emp_sal
*1행에 오류:
ORA-04088: 트리거 'C##SCOTT.NO_DDL'의 수행시 오류
ORA-00604: 순환 SQL 레벨 1 에 오류가 발생했습니다
ORA-20001: DROP 하지 마세요! 저에게 왜 그러시나요?
ORA-06512: 3행
087. DDL 트리거 2 ( ALTER )
📖
☞ 컬럼 추가나 삭제는 업무시간에 수행하면 안된다.
업무 외 시간에 해야 한다. 업무시간에 하게 되면 컬럼 추가나 삭제로 인해서
영향을 받는 테이블을 액세스 하는 쿼리가 느려지기 때문이다.
사원 테이블에 컬럼 추가, 삭제를 업무시간에 하지 못하도록 트리거를 생성하시오 !
( 업무시간 : 9:00 ~ 18:00 )
CREATEOR REPLACE TRIGGER tg270
BEFORE ALTERON C##SCOTT.SCHEMA
BEGIN
IF ( TO_CHAR( SYSDATE, 'HH24:MI' ) BETWEEN'09:00'AND'18:00' ) THEN
RAISE_APPLICATION_ERROR( -20002, '업무시간에 alter문 사용하지 마세요~' );
END IF;
END;
/ALTER TABLE emp
ADD email VARCHAR2(200);
1행에 오류:
ORA-04088: 트리거 'C##SCOTT.TG270'의 수행시 오류
ORA-00604: 순환 SQL 레벨 1 에 오류가 발생했습니다
ORA-20002: 업무시간에 alter문 사용하지 마세요~
ORA-06512: 5행
문제풀며 익히기
Q1.
사원테이블의 월급 컬럼을 drop하시오
ALTER TABLE emp
DROPCOLUMN sal;
!!ERROR!!ALTER TABLE emp
*1행에 오류:
ORA-04088: 트리거 'C##SCOTT.TG270'의 수행시 오류
ORA-00604: 순환 SQL 레벨 1 에 오류가 발생했습니다
ORA-20002: 업무시간에 alter문 사용하지 마세요~
ORA-06512: 5행
088. DB 관리에 대한 트리거 - DBA에게 필요한 트리거 ★
📖
DB 관리에 대한 트리거
- DB 에 로그인 또는 로그아웃을 했을때 ⇒ DB에 로그인은 아무나 하면 안되기 때문
- DB 를 shutdown 이나 startup 했을때
“위의 작업들을 했을 때 누가 했는지 그 정보를 테이블에 저장할 수 있음”
예제)
-- ☞ logOn trigger 생성droptrigger no_ddl;
select*from user_triggers;
--트리거 있으면 삭제후 진행DROPTABLE log_inform purge;
---------------------------------------------CREATE TABLE logon_inform
(
user_id VARCHAR2(10),
log_date TIMESTAMP,
action VARCHAR2(20)
);
---------------------------------------------CREATEOR REPLACE TRIGGER logon_trace
AFTER LOGON ON DATABASE
BEGININSERT INTO logon_inform
VALUES ( USER, SYSTIMESTAMP, '로그인 했음');
END;
/
DB에 접속 했을 때 작동되는 트리거
------------------------------------------------/* 트리거 수행 확인
C:\Users\itwill>sqlplus scott/tiger
SQL*Plus: Release 11.2.0.1.0 Production on 화 4월 21 14:45:05 2
Copyright (c) 1982, 2010, Oracle. All rights reserved.
다음에 접속됨:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64b
With the Partitioning, OLAP, Data Mining and Real Application T
SQL> select * from logon_inform;
USER_ID LOG_DATE ACTION
---------- ------------------------------ --------------------
SCOTT 15/04/21 14:53:52.943000 로그인 했음
SYS 15/04/21 14:53:46.055000 로그인 했음
*/--> 오라클 내부적으로 로그인을 수행해서 그 기록까지 테이블에 기록된다.
문제풀며 익히기
Q1.
아래의 테이블을 생성하고 DB 에 log off 했을때 그정보가 아래의 테이블에 입력되게 하시오
DROPTABLE logoff_inform purge;
---------------------------------CREATE TABLE logoff_inform
(
user_id VARCHAR2(10),
log_date TIMESTAMP,
action VARCHAR2(20)
);
-----------------------------------CREATEOR REPLACE TRIGGER logoff_trace
BEFORE LOGOFF ON DATABASE
BEGININSERT INTO logoff_inform
VALUES ( USER, SYSTIMESTAMP, '로그오프 했음' );
END;
//* 트리거 수행 확인
SQL> exit
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options에서 분리되었습니다.
C:\Users\itwill>sqlplus c##scott/tiger
SQL*Plus: Release 21.0.0.0.0 - Production on 목 12월 28 15:18:52 2023
Version 21.3.0.0.0
Copyright (c) 1982, 2021, Oracle. All rights reserved.
마지막 성공한 로그인 시간: 목 12월 28 2023 15:16:13 +09:00
다음에 접속됨:
Oracle Database 21c Express Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0
SQL> select * from logoff_inform;
USER_ID LOG_DATE ACTION
--------------- ----------------- -------------------
C##SCOTT 23/12/28 15:18:41.246000 로그오프 했음
*/=> 어느 자리에서 했는지는 알 수 없음
Q2. 그런데 만약 어느 컴퓨터에서 오라클 접속을 시도했는지 확인하려면?
/*
현재 DB 에 접속한 세션들에 대한 정보를 확인하는 방법
*/SELECT sid, serial#, username, status, terminal -- termial : 컴퓨터 이름FROM v$session
WHERE USERNAME ='C##SCOTT'; --대문자!주의!
DBAEVER나 SQLdeveloper에서 확인해야함
/* 결과 1 ( sqlplus에서 수행한 결과 )
SID SERIAL# USERNAME STATUS TERMINAL
---------- ---------- ------------------------------ -------- ----------------
6 387 SCOTT ACTIVE STU
70 2283 SCOTT INACTIVE STUVE
*//* 결과 2 ( DBever 에서 수행한 결과 )
SID SERIAL# USERNAME STATUS TERMINAL
---------- ---------- ------------------------------ -------- ----------------
6 387 SCOTT INACTIVE STU
70 2283 SCOTT ACTIVE STU
sid : SCOTT 이 여러개니까 어디 세션에 있는 USER 인지를 보여줌.세션의 번호
status : 계정의 상태를 보여줌.
terminal : PC 이름.
*/SQL>connect sys/oracle_4U as sysdba
연결되었습니다.
SQL>grantselecton v_$session to c##scott;
권한이 부여되었습니다.
SQL>connect c##scott/tiger
연결되었습니다.
-- log on 트리거.txtCREATEOR REPLACE TRIGGER LOGON_TRACE
AFTER LOGON ON DATABASE --데이터베이스에 접속한 누구나 이 트리거가 작동--즉, DB작동하면 발생하는 트리거DECLARE
v_sid v$session.sid%TYPE;
v_serial v$session.serial#%TYPE;
v_username v$session.username%TYPE;
v_terminal v$session.terminal%TYPE;
BEGINSELECT sid, serial#, username, terminal
into v_sid, v_serial, v_username, v_terminal
FROM v$session
WHERE AUDSID = USERENV('SESSIONID') -- 접속한 자기 자신에 대한 정보를 볼 수 있음AND ROWNUM <=1 ;
IF v_username ='C##SCOTT'THENINSERT INTO logon_inform
--원래는 logon_inform 테이블이 아니라 터미널에 접속하면 그 값이 입력될 수 있게 테이블을 생성해서 해야함VALUES ( USER, SYSTIMESTAMP, '로그인 했음');
/*
IF v_username = 'C##SCOTT' AND v_terminal = 'STU' THEN
INSERT INTO logon_inform
VALUES ( USER, SYSTIMESTAMP, '로그인 했음');
-- 터미널이름이 안잡혀서 AND v_terminal = 'STU' 빼버림 *//* execute immediate ' drop table scott.emp ';
--> SCOTT 이 접속 하자마자 emp 테이블이 drop 됨.
그래서 원래 이 내용이었는데 수업에서 위의 INSERT 문으로 대체 */END IF;
END ;
//* 트리거 수행 확인
SQL> connect / as sysdba
연결되었습니다.
-- 트리거 생성 ( sys 유저로 가서 트리거 생성해야 됨)
SQL> connect scott/tiger;
연결되었습니다.
SQL> select * from emp;
select * from emp
*
1행에 오류:
ORA-00942: 테이블 또는 뷰가 존재하지 않습니다
*//*
☞ 트리거의 상태
오라클 11g 이전까지는 트리거가 생성 되면 바로 작동된다.
오라클 11g 이후부터는 트리거를 생성했지만,
비활성화를 시켜서 트리거가 필요할때 활성화를 시킬 수 있다.
*/
089. 패키지 만들기
📖
패키지 사용이유
PL/SQL 코드를 암호화 할 때 사용
코드를 암호화해야 제품으로 상용화 가능
자회사를 차리고 싶다면 패키지를 만들어야함
☞ 패키지를 사용해야 하는 이유 ?
1. 정보(코드) 를 숨길 수 있다. (즉, 암호화 가능)
2. 쉬운 유지보수 관리 ~> 비슷한 업무의 프로시저들을 모아서
하나의 코드로 묶어서 관리함
3. 성능이 향상이 된다. ( 패키지로 묶어서 잘 정리해 놓았기 때문 )
4. 오버로딩이 가능하다.
SQL> exec pro24(1); <-- 1위만 보여달라
현대 오토에버 5600
SQL> exec pro24(1,3) <-- 1위부터 3위까지 보여달라
현대 오토에버 5600
국민은행 5400
SQL> exec pro24(’SCOTT’)
☞ 패키지의 구조
1. 명세 ~> body (몸체) 에서 구현할 프로시저의 이름과 전역변수의 이름을 선언
2. 몸체 ~> 실제 구현 코드 (프로시져나 함수)
( 예 : 책의 내용 즉, 그 동안 배웠던 프로시져와 함수 코드 )
예제 1)
사원 테이블에 rnk(순위) 컬럼을 추가하시오.
ALTER TABLE emp
ADD rnk NUMBER(10);
예제2) 추가한 rnk 컬럼의 값을 해당 사원의 월급의 순위로 갱신하시오
BEGINFOR emp_record IN (
SELECT empno, DENSE_RANK() OVER (ORDERBY sal desc) 순위
FROM emp )
/* for loop문을 사용한 레코드 변수 emp_record에 사원의 순위가 들어갈 것 */
LOOP
UPDATE emp
SET rnk = emp_record.순위
WHERE empno = emp_record.empno;
--레코드 변수 행이 14개이기 때문에 14번 들어가게 될 것END LOOP;
COMMIT;
END;
/
* PL/SQL 수행 확인
SELECT ename, rnk
FROM emp
ORDERBY2;
ENAME RNK
-------------------- ----------
KING 1
SCOTT 2
FORD 2
JONES 3
BLAKE 4
CLARK 5
ALLEN 6
TURNER 7
MILLER 8
WARD 9
MARTIN 9
ADAMS 10
JAMES 11
SMITH 12
예제3) 위의 코드를 패키지로 생성하시오
0. 패키지 만들기 전 rnk 컬럼 값을 모두 NULL 로 수정하기.
UPDATE emp
SET rnk =NULL;
1. 명세부분 생성
CREATEOR REPLACE PACKAGE crank -- package 명 CRANKASPROCEDURE crank_play; -- procedure 명 CRANK_PLAYEND crank;
/2. 바디부분 생성( 중요 ★ )
CREATEOR REPLACE PACKAGE BODY CRANK
-- package body를 꼭 넣어야하며, 명세부분의 package 명과 똑같아야 한다!ASPROCEDURE crank_play -- 명세부분에서 선언한 procedure 이름도 똑같이ISBEGINFOR emp_record IN (
SELECT empno, DENSE_RANK() OVER (ORDERBY sal desc) 순위
FROM emp )
LOOP
UPDATE emp
SET rnk = emp_record.순위
WHERE empno = emp_record.empno;
END LOOP;
END crank_play; -- procedure 이름END crank; -- package 이름/3. 패키지 실행
EXECUTE crank.crank_play;
-- EXECUTE [패키지이름].[프로시저이름];4. 확인
SELECT ename, rnk
FROM emp
ORDERBY2;
/* 결과
ENAME RNK
-------------------- ----------
KING 1
SCOTT 2
FORD 2
JONES 3
BLAKE 4
CLARK 5
ALLEN 6
TURNER 7
MILLER 8
WARD 9
MARTIN 9
ADAMS 10
JAMES 11
SMITH 12
*/
문제풀며 익히기
Q1. 아래의 함수를 패키지로 작성하시오
CREATEOR REPLACE FUNCTION fun179
( p_ename emp.ename%TYPE )
RETURN VARCHAR2
AS
v_rank NUMBER(5); --숫자형 스칼라변수
v_msg VARCHAR2(20); --문자형 스칼라변수BEGINSELECT 순위 INTO v_rank -- 특정 사원의 순위를 v_rank에 넣음FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='고소득';
ELSE
v_msg :='저소득';
END IF;
RETURN v_msg;
END;
/select ename, fun179(ename)
from emp;
1. 명세부 작성
CREATEOR REPLACE PACKAGE pcg_rank
ASFUNCTION fun179(p_ename emp.ename%TYPE)
RETURN VARCHAR2;
-- 입력매개변수와 return까지 다 써줘야함 END pcg_rank;
/2. 바디부 작성
CREATEOR REPLACE PACKAGE BODY pcg_rank
ASFUNCTION fun179 (p_ename emp.ename%TYPE)
RETURN VARCHAR2
IS
v_rank NUMBER(5);
v_msg VARCHAR2(20);
BEGINSELECT 순위 INTO v_rank
FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='고소득';
ELSE
v_msg :='저소득';
END IF;
RETURN v_msg;
END fun179;
END pcg_rank;
/-- 패키지 동작 확인SELECT ename,
sal,
pcg_rank.fun179(ename) 소득 -- 패키지이름.프로시져이름FROM emp;
/* 결과
ENAME SAL 소득
---------- ---------- -------------------
KING 5000 고소득
BLAKE 2850 저소득
CLARK 2450 저소득
JONES 2975 고소득
MARTIN 1250 저소득
ALLEN 1600 저소득
TURNER 1500 저소득
JAMES 950 저소득
WARD 1250 저소득
FORD 3000 고소득
SMITH 800 저소득
SCOTT 3000 고소득
ADAMS 1100 저소득
MILLER 1300 저소득
*/
Q2. (select 문제)
pcg_rank.fun179(ename)을 사용해서 소득이 고소득인 사원의 이름과 월급과 소득 여부를 출력하시오
select ename, sal, pcg_rank.fun179(ename)
from emp
where pcg_rank.fun179(ename) ='고소득';
090. 패키지 코드 암호화 하기 (PL.SQL코드 암호화 하기)
📖
패키지 코드 암호화 하기 (PL.SQL코드 암호화 하기)
- PL/SQL 코드를 암호화 하면 좋은점? 내가 만든 코드를 상용화 할 수 있다
예제 1) 089.에서 만든 패키지가 잘 동작되는지 확인
1. 명세부 작성
CREATEOR REPLACE PACKAGE pcg_rank
ASFUNCTION fun179(p_ename emp.ename%TYPE)
RETURN VARCHAR2;
-- 입력매개변수와 return까지 다 써줘야함 END pcg_rank;
/2. 바디부 작성
CREATEOR REPLACE PACKAGE BODY pcg_rank
ASFUNCTION fun179 (p_ename emp.ename%TYPE)
RETURN VARCHAR2
IS
v_rank NUMBER(5);
v_msg VARCHAR2(20);
BEGINSELECT 순위 INTO v_rank
FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) 순위
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='고소득';
ELSE
v_msg :='저소득';
END IF;
RETURN v_msg;
END fun179;
END pcg_rank;
/-- 패키지 동작 확인SELECT ename,
sal,
pcg_rank.fun179(ename) 소득 -- 패키지이름.프로시져이름FROM emp;
/* 결과
ENAME SAL 소득
---------- ---------- -------------------
KING 5000 고소득
BLAKE 2850 저소득
CLARK 2450 저소득
JONES 2975 고소득
MARTIN 1250 저소득
ALLEN 1600 저소득
TURNER 1500 저소득
JAMES 950 저소득
WARD 1250 저소득
FORD 3000 고소득
SMITH 800 저소득
SCOTT 3000 고소득
ADAMS 1100 저소득
MILLER 1300 저소득
*/
예제2)
명령프롬프트 창에서 메모장을 열어서 패키지 명세 부분을 작성한 후 pack01.sql이라는 이름으로 저장하기
(주의. 가급적 메모장에 넣을 때 주석은 넣지 말고, 마지막 끝인 / 는 왼쪽 벽 끝에 붙이기)
SQL> ed pack01.sql
////////////////메모장 내용//////////////////CREATEOR REPLACE PACKAGE pcg_rank
ASFUNCTION fun179(p_ename emp.ename%TYPE)
RETURN VARCHAR2;
END pcg_rank;
/
예제3)
명령프롬프트 창에서 메모장을 열어서 패키지 본문을 작성하고 pack01_body.sql이라는 이름으로 저장하기
(한글 있으면 암호화 오류나니까 영어로 바꾸기)
CREATEOR REPLACE PACKAGE BODY pcg_rank
ASFUNCTION fun179 (p_ename emp.ename%TYPE)
RETURN VARCHAR2
IS
v_rank NUMBER(5);
v_msg VARCHAR2(20);
BEGINSELECT rnk INTO v_rank
FROM (
SELECT ename, DENSE_RANK() OVER ( ORDERBY sal DESC ) rnk
FROM emp
)
WHERE ename =UPPER(p_ename);
IF v_rank <4THEN
v_msg :='high';
ELSE
v_msg :='low';
END IF;
RETURN v_msg;
END fun179;
END pcg_rank;
/
예제4)
지금 접속한 sqlplus 프롬프트 창에서 exit명령어를 수행하고 빠져 나와서 지금 작성한 2개의 sql 파일이 잘 있는지 확인하기
SQL> exit;
Oracle Database 21c Express Edition Release21.0.0.0.0- Production
Version 21.3.0.0.0에서 분리되었습니다.
C:\Users\itwill>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: A4CD-4883
C:\Users\itwill 디렉터리
2023-12-29 오전 10:22<DIR> .
2023-11-24 오후 03:25<DIR> ..
2023-12-14 오후 06:257,77020231214.sql2023-12-13 오후 02:4947 afiedt.buf
2023-11-24 오후 03:08<DIR> Contacts
2023-12-11 오전 11:571,832 demo.sql
2023-12-27 오전 09:38<DIR> Desktop
2023-11-28 오전 11:31<DIR> Documents
2023-12-28 오후 03:58<DIR> Downloads
2023-11-24 오후 03:08<DIR> Favorites
2023-12-22 오후 04:31130 for.sql
2023-12-19 오후 02:011,830 init_emp.sql
2023-11-24 오후 03:08<DIR> Links
2023-11-24 오후 03:08<DIR> Music
2023-11-24 오후 03:10<DIR> OneDrive
2023-12-27 오후 05:46643 p.sql
2023-12-29 오전 10:11687 pack01.body.sql
2023-12-29 오전 10:12137 pack01.sql
2023-12-29 오전 10:22687 pack01_body.sql
2023-12-18 오후 05:44<DIR> Pictures
2023-11-24 오후 03:08<DIR> Saved Games
2023-12-14 오전 09:50<DIR> Searches
2023-12-11 오전 09:32<DIR> Videos
2023-12-13 오후 06:147,261 window_21c.sql
2023-12-19 오후 06:134,297 window_21c1.sql
2023-12-20 오후 06:191,332 window_21c2.sql
2023-12-21 오후 06:195,011 window_21c3.sql
2023-12-22 오후 06:19792 window_21c4.sql
2023-12-26 오후 06:182,043 window_21c5.sql
2023-12-28 오후 06:202,447 window_21c6.sql
2023-12-13 오후 06:141,441 window_21c~1.sql17개 파일 38,387 바이트
14개 디렉터리 181,773,611,008 바이트 남음
============================================================// 잘 가지고 있기 //2023-12-29 오전 10:12137 pack01.sql
2023-12-29 오전 10:22687 pack01_body.sql
예제5) 명령 프롬프트 창에서 아래의 명령어를 수행하기
< 영어 포함되었을 때 발생하는 오류 >
C:\Users\itwill>wrap iname=pack01_body.sql oname=pack01.plb
PL/SQL Wrapper: Release21.0.0.0.0- Production on 금 12월 2910:23:112023
Version 21.3.0.0.0
Copyright (c) 1982, 2021, Oracle and/or its affiliates. All rights reserved.
Processing pack01_body.sql to pack01.plb
PL/SQL Wrapper error at line 33
Outputting source and continuing.
PL/SQL Wrapper error at line 33
Outputting source and continuing.
------------------------------------------------------------< 영어 없을 때 제대로 암호화 될 때 >
C:\Users\itwill>wrap iname=pack01_body.sql oname=pack01.plb
PL/SQL Wrapper: Release21.0.0.0.0- Production on 금 12월 2910:26:202023
Version 21.3.0.0.0
Copyright (c) 1982, 2021, Oracle and/or its affiliates. All rights reserved.
Processing pack01_body.sql to pack01.plb
============================================================
wrap : 암호화 하겠다
iname : input name
oname : output name
즉 pack01_body.sql 파일을 pck01.plb라는 이름으로 암호화해서 출력해
예제6)
pack01.plb 파일을 메모장으로 열어보면 암호화 되어있음
그 상태 그대로 코드 사용 가능
< 암호화 된 코드 >CREATEOR REPLACE PACKAGE BODY pcg_rank wrapped
a000000
34e
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
26a 1a5
Uw5hqgb+uzAN28BNft+K2ePec2swgzIJLq5GfC+BR2QwHx0U1H1gC47+aXBvhebeo924ZtRq
4rNNCAaaxGJPmAp2gyk0eCCSQJ5veZPm59lTlbHZ4vg/+TDHpujS4X4nHFyT+i64EL8orvXb
kzUO5nGDfTQnRDpLfqFcYq2rG4yJ+o3XMWxex3686LdfIPuHH6ihuzwob2lr0wMoZhn8lw5v
KD76MHJuzhjAGMVaYpwI5XYaSVS6yAH7IPdXTQMc0S+26pHVACwBhNMc8ogeAH5rVOK9xawg
g24WsmLSokqoqvcw8qEJmsv2d+YRmmp8H7kXDzHJbiXvSGlj25ALl3riBypocx5IuzlJFJeU
YtX5Jj+15FLWJELbCxBYiBOsCCD98N1SYEi1CXSabqKJVB+vMZYvSBK3
/
예제7 )
앞으로 패키지 명세 코드와 암호화된 바디 코드를 가지고 다니면 됨
< 실행 순서 >/* 패키지 명세 코드 */CREATEOR REPLACE PACKAGE pcg_rank
ASFUNCTION fun179(p_ename emp.ename%TYPE)
RETURN VARCHAR2;
END pcg_rank;
//* 암호화된 패키지 바디 코드 */CREATEOR REPLACE PACKAGE BODY pcg_rank wrapped
a000000
34e
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
26a 1a5
Uw5hqgb+uzAN28BNft+K2ePec2swgzIJLq5GfC+BR2QwHx0U1H1gC47+aXBvhebeo924ZtRq
4rNNCAaaxGJPmAp2gyk0eCCSQJ5veZPm59lTlbHZ4vg/+TDHpujS4X4nHFyT+i64EL8orvXb
kzUO5nGDfTQnRDpLfqFcYq2rG4yJ+o3XMWxex3686LdfIPuHH6ihuzwob2lr0wMoZhn8lw5v
KD76MHJuzhjAGMVaYpwI5XYaSVS6yAH7IPdXTQMc0S+26pHVACwBhNMc8ogeAH5rVOK9xawg
g24WsmLSokqoqvcw8qEJmsv2d+YRmmp8H7kXDzHJbiXvSGlj25ALl3riBypocx5IuzlJFJeU
YtX5Jj+15FLWJELbCxBYiBOsCCD98N1SYEi1CXSabqKJVB+vMZYvSBK3
//* 패키지 동작 확인 */SELECT ename,
sal,
pcg_rank.fun179(ename) rnk
FROM emp;