1. PL/SQL
Procedural Language extension to SQL의 약자
오라클 자체 내장된 절차적 언어
SQL단점을 보완하여 문장 내에서 변수 정의, 조건 처리, 반복 처리 등을 지원함
- 대입연산 기호 (:=) 바뀌므로 주의
- 레코드 타입 변수: 다수의 자료형을 저장할 수 있는 일종의 묶음 자료형
- 🚨 END; 아래 (/)슬래시를 입력해줘야 구문 전체를 실행시킬 수 있다.
사전세팅 :: 출력용 환경변수 켜기
- 프로시저를 사용하여 출력하는 내용을 화면에 띄워주는 환경변수를 ON처리 해줘야 변수를 볼 수 있음
- SET SERVEROUTPUT ON; → OFF가 디폴트
- 접속할때마다 세팅해줘야함
SET SERVEROUTPUT ON;
begin
dbms_output.put_line('안녕 PL/SQL'); -- on으로 해줘야 뜸(접속마다 설정)
end;
구조
구조 | 시작 | 내용 |
DECLARE SECTION 선언부 |
DECLARE | 변수나 상수를 선언하는 부분 - 변수명 자료형(크기) |
EXECUTABLE SECTION 실행부 |
BEGIN | 로직 기술부, ; 삽입 필요 (SELECT : 반드시 특정 변수에 필요 데이터를 저장하고 활용해야 한다.(INTO 활용)) - 레퍼런스 변수, rowtype변수, 테이블 타입 변수, 레코드 타입 변수 이용가능 - 선택문, 반복문 사용가능 |
EXCEPTION SECTION 예외처리부 |
EXCEPTION | 예외 발생시 해결 로직 작성 - 실행부에 포함된다 - WHEN ~ THEN ~ |
END | 실행부 끝나면 명시 필요 |
DECLARE 변수 활용하기
변수는 DECLARE 부분에 변수명 자료형 형식으로 선언
변수 종류
- 기본 자료형: 오라클이 제공하는 타입: 문자, 숫자, 날짜의 형식(1개)
- 기본자료형 예시
DECLARE
V_EMPNO VARCHAR2(20);
V_EMPNAME VARCHAR2(10);
V_AGE NUMBER;
V_DATE DATE;
BEGIN
-- 변수에 값 대입: 대입연산자(:=) 이용
V_EMPNO := '200';
V_EMPNAME := 'JOHN';
V_AGE := 29;
V_DATE := TO_DATE('24/02/13','YY/MM/DD');
DBMS_OUTPUT.PUT_LINE(V_EMPNO);
DBMS_OUTPUT.PUT_LINE(V_EMPNAME);
DBMS_OUTPUT.PUT_LINE(V_AGE);
DBMS_OUTPUT.PUT_LINE(V_DATE);
-- SELECT : 반드시 특정 변수에 필요 데이터를 저장하고 활용해야 한다.(INTO 활용)
SELECT EMP_NO, EMP_NAME
INTO V_EMPNO, V_EMPNAME
FROM EMPLOYEE WHERE EMP_ID = 200;
DBMS_OUTPUT.PUT_LINE(V_EMPNO);
DBMS_OUTPUT.PUT_LINE(V_EMPNAME);
END;
/
-
- 기본형은 계속 맞춰서 형변환을 해줘야하는데 참조 자료형은 테이블의 데이터 타입이 바뀌면 알아서 같이 바뀌기 때문에 그보다 유동적 대처가 가능하다.참조 자료형: 테이블 컬럼에 선언되어있는 타입을 참조하는 것(1개)
DECLARE
V_EMPID EMPLOYEE.EMP_ID%TYPE; -- EMPLOYEE 테이블의 emp_id의 타입을 가져온다는 의미
V_SALARY EMPLOYEE.SALARY%TYPE;
BEGIN
V_EMPID := 'CH';
V_SALARY := 7000;
DBMS_OUTPUT.PUT_LINE(V_EMPID || ' ' || V_SALARY);
SELECT EMP_ID, SALARY
INTO V_EMPID, V_SALARY
FROM EMPLOYEE
WHERE EMP_ID = '&사원번호'; -- &사용시 입력창이 뜬다.
DBMS_OUTPUT.PUT_LINE(V_EMPID || ' ' || V_SALARY);
END;
/
ROWTYPE(여러개)
- 테이블에 선언된 전체 컬럼 타입을 가져와 적용하는 것(참조)
- 컬럼명으로 가져올 수 있다.
- 즉, 1개 row 저장 가능
DECLARE
V_EMP EMPLOYEE%ROWTYPE;
V_DEPT DEPARTMENT%ROWTYPE;
BEGIN
SELECT *
INTO V_EMP
FROM EMPLOYEE
WHERE EMP_ID = '&EMP_ID';
--ROW타입에 저장된 데이터는 컬럼명으로 가져올 수 있다.
DBMS_OUTPUT.PUT_LINE(V_EMP.EMP_ID || V_EMP.EMP_NAME || V_EMP.EMP_NO);
SELECT * --DEPT_ID AS DEPTID, DEPT_TITLE AS DEPTTITLE
INTO V_DEPT
FROM DEPARTMENT
WHERE DEPT_ID = '&부서코드';
DBMS_OUTPUT.PUT_LINE(V_DEPT.DEPT_ID || V_DEPT.DEPT_TITLE || V_DEPT.LOCATION_ID);
END;
/
아래는 타입을 생성해서 선언하는 변수
TABLE(1개)
- 자바의 배열과 유사한 자료형
- 한개 타입의 여러 값을 저장
- 사용시 인덱스 번호로 접근
-- table type
DECLARE
TYPE EMPNAME_TABLE IS TABLE OF EMPLOYEE.EMP_NAME%TYPE
INDEX BY BINARY_INTEGER;
EMPNAMES EMPNAME_TABLE; --하나의 자료형이므로 바로 삽입 가능
I BINARY_INTEGER := 1;
BEGIN
EMPNAMES(1) := 'SAM';
EMPNAMES(2) := 'JOHN';
EMPNAMES(3) := 'STEVE';
EMPNAMES(4) := 'SOPIA';
EMPNAMES(5) := 'JULIA';
--DBMS_OUTPUT.PUT_LINE(EMPNAMES(1) || EMPNAMES(2) || EMPNAMES(3) || EMPNAMES(4) || EMPNAMES(5));
--반복문 사용이 더 용이한 경우
FOR K IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(EMPNAMES(K));
END LOOP;
--이름들을 가져와서 저장해볼까요
FOR E IN(SELECT * FROM EMPLOYEE) LOOP
EMPNAMES(I) := E.EMP_NAME;
I := I+1;
END LOOP;
FOR K IN 1..I-1 LOOP --I가 1부터 시작이기 때문에 I-1까지 루프
DBMS_OUTPUT.PUT_LINE(EMPNAMES(K));
END LOOP;
END;
/
- RECORD(여러개)
- 자바의 클래스와 유사
- 다수 타입의 여러 값을 저장
- 변수 명으로 접근해서 사용
DECLARE
TYPE MYRECORD IS RECORD(
EMPID EMPLOYEE.EMP_ID%TYPE,
EMNAME EMPLOYEE.EMP_NAME%TYPE,
DEPTITLE DEPARTMENT.DEPT_TITLE%TYPE,
JOBNAME JOB.JOB_NAME%TYPE
);
MYDATA MYRECORD;
BEGIN
SELECT EMP_ID, EMP_NAME, DEPT_TITLE, JOB_NAME
INTO MYDATA
FROM EMPLOYEE
LEFT JOIN DEPARTMENT ON DEPT_CODE = DEPT_ID
JOIN JOB USING(JOB_CODE)
WHERE EMP_NAME = '&이름';
DBMS_OUTPUT.PUT_LINE(MYDATA.EMPID || MYDATA.EMNAME || MYDATA.JOBNAME);
END;
/
사용방법
(1) 익명 블록 이용(기본적인 방법)
많이 안씀
[DECLARE
필요한 변수 선언]
BEGIN
실행할 로직 작성;
END
(2) OBJECT(PROCEDURE, FUNCTION, TRIGGER) 이용
아래 참조
조건문 종류
(1) IF
@ 작성방법
IF 조건문
THEN true일때 실행할 내용
END IF;
@ 예시
-- 월급 200이상인 사원 이름 검색
DECLARE
V_SALARY EMPLOYEE.SALARY%TYPE;
BEGIN
SELECT SALARY
INTO V_SALARY
FROM EMPLOYEE
WHERE EMP_NAME = '%사원명';
IF V_SALARY > 2000000
THEN --DBMS_OUTPUT.PUT_LINE('우와 200만원 보다 많이 받는다!!')
INSERT INTO HISTORY VALUES(V_SALARY,SYSDATE); -- 저장까지해보기.
COMMIT; -- 저장을 했다면 커밋까지 써줘야한다.
END IF;
END;
/
CREATE TABLE HISTORY(
SALARY NUMBER,
TODAY DATE
);
(2) IF ~ ELSE
@ 작성방법
IF 조건문
THEN 실행문
ELSE 실행문
END IF;
@ 예시
--월급 400이상인 사원 이름 검색
DECLARE
V_SALARY EMPLOYEE.SALARY%TYPE;
BEGIN
SELECT SALARY
INTO V_SALARY
FROM EMPLOYEE
WHERE EMP_NAME = '&이름';
IF V_SALARY > 4000000
THEN DBMS_OUTPUT.PUT_LINE('WOW');
ELSE DBMS_OUTPUT.PUT_LINE('GOGO');
END IF;
END;
/
(3) IF ~ ELSIF ~ ELSE
조건이 많을 때 사용
@ 작성방법
IF 조건문
THEN 실행문;
ELSIF 실행문
THEN 실행문;
.
.
ELSE 실행문;
END IF;
@ 예시
-- JOB_TITLE출력 로직
DECLARE
V_EMP_ID EMPLOYEE.EMP_ID%TYPE;
V_JOB_CODE EMPLOYEE.JOB_CODE%TYPE;
MSG VARCHAR2(10);
BEGIN
SELECT EMP_ID, JOB_CODE
INTO V_EMP_ID, V_JOB_CODE
FROM EMPLOYEE
WHERE EMP_ID = '&EMP_ID';
IF V_JOB_CODE = 'J1'
THEN MSG := 'CEO';
ELSIF V_JOB_CODE IN ('J2', 'J3', 'J4')
THEN MSG := '간부';
ELSIF V_JOB_CODE IN ('J5', 'J6')
THEN MSG := '사원';
ELSE MSG := '인턴';
END IF;
DBMS_OUTPUT.PUT_LINE(MSG);
END;
/
(4) CASE문
이미 이전에 작성법은 함
DECLARE
NUM NUMBER;
BEGIN
NUM := '&NUM';
CASE
WHEN NUM > 10 THEN DBMS_OUTPUT.PUT_LINE(Num || '10초과');
WHEN NUM > 5 THEN DBMS_OUTPUT.PUT_LINE(Num || '5초과');
ELSE DBMS_OUTPUT.PUT_LINE(Num || '5이하값 입니다');
END CASE;
END;
/
반복문
LOOP예약어만 이용해서 사용가능
반복문안에 조건을 입력하지 않으면 무한루프가 되므로 조심할 것
(1) 기본 반복문
-- 1~ 100까지 출력
DECLARE
NUM NUMBER := 1;
RNDNUM NUMBER;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE(NUM);
IF NUM = 100
THEN EXIT;
END IF;
NUM := NUM + 1;
END LOOP;
END;
/
- 활용 예시
-- 무작위 데이터 100개 게시글 저장하기
CREATE TABLE FK_BOARD2(
BOARD_NO NUMBER PRIMARY KEY,
BOARD_TITLE VARCHAR2(200),
BOARD_DATE DATE
);
CREATE SEQUENCE SEQ_BOARDNO;
DECLARE
NUM NUMBER := 1;
RNDNUM NUMBER;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE(NUM);
RNDNUM := FLOOR(DBMS_RANDOM.VALUE(1,200));
INSERT INTO FK_BOARD2 VALUES(SEQ_BOARDNO.NEXTVAL, 'TEST TITLE' || RNDNUM, SYSDATE);
IF NUM = 100
THEN EXIT;
END IF;
NUM := NUM + 1;
END LOOP;
END;
/
SELECT * FROM FK_BOARD2;
(2) while 반복문
@ 작성법
WHILE
WHILE 조건문 LOOP
실행구문
END LOOP;
-- 1~10까지 반복 출력
DECLARE
NUM NUMBER := 1;
BEGIN
WHILE NUM <= 10 LOOP
DBMS_OUTPUT.PUT_LINE(NUM);
NUM := NUM +1;
END LOOP;
END;
/
(3) 기본 for문
@ 작성법
FOR 변수 IN 범위(시작..끝)LOOP -- 점 두개
실행구문
END LOOP;
-- 기본 for
BEGIN
FOR I IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(I);
END LOOP;
END;
/
(4) select문으로 for문
조건별 분기넣어서 저장 또는 삭제 등으로 연결 가능
@ 작성법
FOR 변수 IN (SELECT문) LOOP
실행구문
END LOOP;
-- select문으로 for실행해보기:
BEGIN
FOR E IN (SELECT * FROM EMPLOYEE) LOOP
DBMS_OUTPUT.PUT_LINE(E.EMP_NAME || E.SALARY || E.BONUS || E.EMAIL);
IF E.SALARY > 3500000
THEN INSERT INTO HIGH_SAL VALUES(E.EMP_ID, E.SALARY, E.HIRE_DATE);
ELSE INSERT INTO LOW_SAL VALUES(E.EMP_ID, E.SALARY, E.HIRE_DATE);
END IF;
END LOOP;
COMMIT;
END;
/
SELECT * FROM HIGH_SAL;
SELECT * FROM LOW_SAL;
2. PROCEDURE & FUNCTION
PL/SQL문을 저장해서 쓸 수 있게 해주는 구문으로 간단히 호출해서 실행 결과를 얻을 수 있음
1) PROCEDURE
하나의 객체이기 때문에 create 구문을 사용한다.
@ 생성 방법
CREATE [OR REPLACE] PROCEDURE NAME
IS
[필요한 변수 선언]
BEGIN
실행할 구문 작성 -> 저장되는 구문
END;
/
@ 실행방법
exec 프로시저명;
또한, 매개변수를 넣어서 생성할 수도 있다. → 유동적으로 돌아가기 위함
자바의 return과 비슷하지만, 받고 주는 것이 모두 매개변수로 이뤄진다는 차이가 있다.
매개변수 있는 :: IN/OUT procedure
(1) IN 매개변수
필요 데이터를 외부에서 받는 매개변수 : 일반적인 매개변수
- 외부 변수는 오라클에서 전역변수 타입인 VAR 를 활용
- var test varchar2(20);
create or replace procedure pro_select_emp(v_emp in employee.emp_id%type)
is
name varchar2(20);
begin
select emp_name
into name
from employee
where emp_id = v_emp;
dbms_output.put_line(name);
end;
/
set serveroutput on;
exec pro_select_emp('200');
(2) OUT 매개변수
호출한 곳에서 지정한 변수에 프로시저 실행 결과를 저장받는 매개변수
--이름을 전달받으면 이름을 출력하는 프로시저
create or replace procedure pro_select_emp2(v_emp in employee.emp_id%type,
v_name out employee.emp_name%type)
is
name varchar2(20);
begin
select emp_name
into name
from employee
where emp_id = v_emp;
dbms_output.put_line(name);
v_name := name;
end;
/
set serveroutput on;
var test varchar2(20);
print test;
-- :연산자를 통해 test 변수에 저장
exec pro_select_emp2('&emp_id', :test);
2) FUNCTION
단일함수(각 row마다 적용되는)를 직접 만드는 것
procedure와 비슷하지만 실행결과를 되돌려 받을 수 있다는 점에서 차이가 있음
함수형이기 때문에 반환형, 매개변수 있음
select, insert, update문에서 사용할 수 있다.
create [or replace] function 함수명(매개변수, 매개변수,...)
return 리턴type
is
[변수 선언]
begin
코드
end;
/
@ 생성 예
-- 문자를 넣으면 길이를 세어주는 함수
create or replace function myfunc(v_str varchar2)
return number
is
v_result number;
begin
select length(v_str)
into v_result
from dual;
return v_result;
end;
/
--실행하기
select myfunc('화이팅') From dual;
--emp_id를 전달받아 연봉을 반환해주는 함수만들기
create or replace function salary(v_id employee.emp_id%type) -- 참조타입으로 받기
return number
is
result_salary number;
begin
select (salary + (salary * nvl(bonus, 0))) * 12
into result_salary
from employee
where emp_id = v_id;
return result_salary;
end;
/
-- 전체 사원의 연봉 테이블 출력
select e.*, salary(e.emp_id) from employee e;
-- 입력받아서 한명 사원의 연봉 출력
select salary('&emp_id') from dual;
3. TRIGGER
테이블이나 뷰가 DML문(insert, update, delete)에 의해 변경될 경우, 자동으로 실행될 로직을 정의하여 저장, 실행하는 객체
입력된 데이터를 바로 저장, 주문량에 따라 재고량 update하는 등의 이벤트 생성시 사용됨
단, 에러 발생시 원인 찾기가 어려워서 안쓰기도 함
생성
- 이전값은 :OLD.컬럼명으로 접근, 새로운 값은 :NEW.컬럼명으로 접근
- insert: 새로운 값을 받아서 처리함
- update: 이전값과 새로운 값을 모두 받아서 처리함
- delete: 이전값을 처리함
CREATE [OR REPLACE] TRIGGER 트리거명
AFTER/BEFOR DML(INSERT, UPDATE, DELETE) ON 테이블명
FOR EACH ROW --행 추가마다 실행
BEGIN
LOGIC
END;
/
-- EMPLOYEE테이블에 신입사원이 추가되면 신입사원이 입사했습니다 출력
CREATE OR REPLACE TRIGGER TRG_01
AFTER INSERT ON EMPLOYEE
FOR EACH ROW -- 다른것도 있지만 이거로 제일 많이 사용함
BEGIN
DBMS_OUTPUT.PUT_LINE('신입사원 ' || :NEW.emp_name ||'이(가) 입사했습니다');
END;
/
--트리거 발동
insert into employee values('991', '전우치', '941111-123456', 'joen@j.com',
'01033333333', 'D3', 'J3', 'S1',30, 0.5, 200, sysdate, null, default);
--출력: 신입사원 전우치이(가) 입사했습니다.
- (예) 트리거를 이용하여 상품 재고 관리 및 수정 히스토리 저장하기
--재고 관리 트리거 만들어보기
create table product(
pcode number primary key,
pname varchar2(20) not null,
brand varchar2(20),
price number check(price > 0),
stock number default 0
);
create sequence seq_productno;
--입출력용 테이블
create table product_io(
iocode number primary key,
pcode number references product(pcode) not null, -- 필수관계
iodate date default sysdate,
amount number check(amount > 0),
status varchar2(10) check(status in ('입고', '출고')) -- 숫자가 더 연산이 빠르긴 하지만 헷갈릴 수있음
);
create sequence seq_iono;
-- 제품등록
insert into product values(seq_productno.nextval, '맥북', '애플', 6000000, default);
insert into product values(seq_productno.nextval, '아이폰16', '애플', 2000000, default);
insert into product values(seq_productno.nextval, '반팔티', '스투시', 130000, default);
insert into product values(seq_productno.nextval, '샤넬백', '샤넬', 12000000, default);
select * from product;
--트리거 생성
create or replace trigger trg_productio
after insert on product_io
for each row -- 각 row 삽입시마다 실행
begin
if :new.status = '입고' --상태가 입고면 재고를 수량만큼 더한다
then update product set stock = stock + :new.amount
where pcode = :new.pcode;
elsif :new.status = '출고'
then update product set stock = stock - :new.amount
where pcode = :new.pcode;
end if;
end;
/
insert into product_io values(seq_iono.nextval,1,default, 10, '입고');
insert into product_io values(seq_iono.nextval,1,default, 3, '출고');
select * from product; --맥북 재고 변동 확인 10->7
--변경 이력용 테이블
create taBle history_table(
old_pname varchar2(20),
new_pname varchar2(20),
old_price number,
new_price number,
update_date date default sysdate
);
--수정 진행시 이력 테이블에 데이터를 저장하는 트리거 생성
create or replace trigger trg_productupdate
after update on product
for each row
begin
insert into history_table values(:old.pname, :new.pname, :old.price, :new.price, default);
end;
/
select * from history_table;
select * from product;
update product set pname = '갤럭시s24', price = 1700000 where pcode = 2;
4. CURSOR
결과가 여러 개의 행으로 구해지는 SELECT문을 처리하기 위해 실행 결과를 저장해놓은 객체
- 명시적 커서라면 CURSOR ~ OPEN ~FETCH ~ CLOSE 단계로 진행된다.
💡 fetch ?
: 커서에서 원하는 값을 추출하는 것
1) 커서의 종류
- 명시적(Explicit) 커서 : 사용자가 선언해서 생성하여 사용하는 커서로 주로 여러개 행을 처리하고자 할 경우에 사용한다.
- 묵시적(Inplicit) 커서 : 오라클에서 자동으로 선언해주기 때문에 사용자는 생성 유무를 알 수 없다.
2) 커서의 속성
명시적 커서
커서명+속성 | 설명 |
%NOTFOUND | 커서 영역 자료가 모두 fetch되어 다음 영역이 존재하지 않을 때 true 반환 |
%FOUND | 커서 영역에 아직 fetch되지 않은 자료가 있을 때 true 반환 |
%ISOPEN | 커서가 OPNE된 상태면 TRUE |
%ROWCOUNT | 커서가 얻어온 레코드의 개수로 일종의 카운터 역할을 한다. - 커서가 오픈되면 0, 패치가 발생할 때마다 1씩 증가한다. |
묵시적 커서
- 위 속성에서 커서명 대신 SQL을 사용한다
3) 커서 생성
CUROSR 커서명
IS
SELECT 저장할_컬럼들
FROM 컬럼이_위치하는_테이블
WHERE 저장조건
BEGIN
OPEN 오픈할_커서명;
LOOP
FETCH 커서명 INTO 꺼내올_칼럼들;
EXIT WHEN 커서명+속성;
[출력문 등 반복 실행할 로직 작성]
END LOOP;
CLOSE 커서명; --커서 닫기
END/
5. PACKAGE
프로시저와 함수를 보다 효율적으로 관리하기 위해 패키지로 묶어서 관리를 한다.
@ 패키지 생성
CREATE OR REPLACE PACKAGE KH_PACK
IS
PROCEDURE CUSOR_DEPT;
FUNCTION BONUS_CALC(V_EMPID EMPLOYEE.EMP_ID%TYPE)
RETURN NUMBER;
END;
/
@ 패키지 사용
EXEC KH_PACK.CUSOR_DEPT;
'Oracle DataBase' 카테고리의 다른 글
12. DB :: DB모델링 정리 (0) | 2024.06.21 |
---|---|
10. ORACLE :: oracle 내 객체 활용 정리(object) (0) | 2024.06.12 |
9. ORACLE :: DCL 구문(사용 권한 구문) 정리, 요약 (2) | 2024.06.12 |
8. ORACLE :: DML구문(insert, upadate, delete) 정리 (0) | 2024.06.12 |
7. ORACLE 정리 :: DDL구문(create, alter, drop) (2) | 2024.06.11 |