Oracle 56일차

[PL/SQL]

절차적 프로그래밍(이전 명령어의 결과를 다음 명령어에 전달)을 지원하는 SQL 확장 언어.
변수 사용 가능.
반복문/조건문 정의 가능.
블럭 단위 구조(선언부/실행부/예외처리부로 구성)
블럭 이름 유무에 따라 익명블럭, 저장블럭으로 나뉜다.
– 블럭에 이름 부여 -> 객체 생성

객체 : 프로시저, 함수, 트리거, 패키지

1. 구조

1) 선언부(declare)
– 변수 선언 (default value 지정 가능)
– 데이터 타입 정의
– IN/OUT 정의
– 생략 가능

2) 실행부(begin)
– 프로그램 동작 명령
– 변수의 값 정의
– 조건문/반복문 처리

3) 예외처리부(exception)
– 예외를 발생시키는 로직 정의
– 에러 발생(ORA 코드와 함께 발생시킬 수 있다)
– 생략 가능

2. 변수 선언

var1 := 1 형태로 선언.
그냥 = 은 ‘same’의 의미로 쓰이기 때문에 := 기호를 써야 변수 선언 가능하다.

3. 변수 종류

1) 스칼라 변수
– 값을 직접 상수로 정의
ex) var1 := 1

2) 참조 변수
– 변수의 값을 특정 대상을 참조하면서 정의
ex) var1 emp.empno%type

[변수 선언]

select문의 결과를 변수에 저장, 저장된 변수의 값을 활용한 문장 출력.

** plsql 출력
plsql 블럭에 대한 출력은 기본적으로 생략됨.
방법 :
– sqlplus) set serveroutput on
– orange) result 창의 server output 탭에서 출력 가능

** 사전 설정

create table scott.EMP
as
select * from scott.emp@dblink_orcl;

select * from scott.emp;

create table scott.dept
as
select * from scott.dept@dblink_orcl;

select * from scott.dept;

예제1.

emp 테이블에서 사원번호가 7369직원의 이름, 부서번호, 직업을 아래와 같이 출력.
SMITH-20-CLERK

declare
    vename  varchar2(10);
    vdeptno number;
    vjob    varchar2(10);
begin
    select ename, deptno, job into vename, vdeptno, vjob
    from scott.EMP
    where empno=7369;
    
    dbms_output.put_line(vename||'-'||vdeptno||'-'||vjob);
end;
/

예제2.

위와 비슷한데 empno만 입력 받음.
prompt에서만 수행 가능한 듯?

declare
    vename  varchar2(10);
    vdeptno number;
    vjob    varchar2(10);
begin
    select ename, deptno, job into vename, vdeptno, vjob
    from scott.EMP
    where empno = &vempno;
    
    dbms_output.put_line(vename||'-'||vdeptno||'-'||vjob);
end;
/

-- prompt에서 실행시
Enter value for vempno: 7369
old   8:     where empno = &vempno;
new   8:     where empno = 7369;
SMITH-20-CLERK

PL/SQL procedure successfully completed.
-- 이런 식으로 출력됨.

> set verify off 하면

Enter value for vempno: 7369
SMITH-20-CLERK

PL/SQL procedure successfully completed.
-- 이런 식으로 출력됨.

> set feedback off 하면
Enter value for vempno: 7369
SMITH-20-CLERK

예제3.

위와 비슷한데 empno를 “사원번호를 입력하세요”와 함께 입력 받음.
prompt에서만 가능한 듯?

!vi ex1.sql
-- 아래 plsql을 저장해둠.
declare
    vename  varchar2(10);
    vdeptno number;
    vjob    varchar2(10);
begin
    select ename, deptno, job into vename, vdeptno, vjob
    from scott.EMP
    where empno = &vempno;
    
    dbms_output.put_line(vename||'-'||vdeptno||'-'||vjob);
end;
/

-- vempno 설정하기 전에 sql 실행 시
SQL> @ex1.sql
Enter value for vempno: 7369
SMITH-20-CLERK

SQL> accept vempno prompt '사번을 입력하세요 : '
사번을 입력하세요 : 7369  -- vempno값이 저렇게 설정된 거임.
SQL> @ex1.sql
SMITH-20-CLERK  -- 이미 vempno값이 설정되어서 실행시 자동 출력됨.

** 한글 안 깨지려면
export LANG=ko_KR.UTF-8
export NLS_LANG=KOREAN_KOREA.AL32UTF8

예제4.

emp 테이블을 사용하여, 사번을 입력받고, 해당 사원의 퇴직금을 아래와 같이 출력
SMITH의 퇴직금 : 9999
– 퇴직금 = 근속연수 * SAL * 0.1 (정수로 반올림)

declare
    vename  varchar2(10);
    vsal    number;
    vhiredate   date;
    vretire number;
begin
    select ename, sal, hiredate into vename, vsal, vhiredate
    from scott.EMP
    where empno = &vempno;
    vretire := round(sal*0.1*trunc(months_between(sysdate,hiredate)/12));
    dbms_output.put_line(vename||'의 퇴직금 : '||vretire);
end;
/

-- 강사님 방법
!vi ex2.sql
-- 아래 모든 거 입력 후 prompt에서 ex2.sql 실행 --
accept vempno prompt '퇴직금을 조회할 사번을 입력하세요 : '

declare
    vename  varchar2(10);
    vretire number;
    vempno  number := &vempno; 
    -- 입력받은 값을 재사용할 경우 반드시 변수 선언 필요(변수 선언 시 값의 입력 유도 가능)
begin
    select ename, round(sal*0.1*trunc(months_between(sysdate,hiredate)/12)) into vename, vretire
    from scott.EMP
    where empno = vempno;
    
    dbms_output.put_line(vename||'의 퇴직금 : '||vretire);
end;
/

-- 그러면
SQL> @ex2.sql
퇴직금을 조회할 사번을 입력하세요 : 7369
SMITH의 퇴직금 : 3520

[UPDATE]

예제1.

사번을 입력받고 해당 직원의 급여를 10% 증가 급여로 update
출력은 기존 급여와 인상된 급여를 같이 출력

!vi ex3.sql
---------------------------------
accept vempno prompt '당첨된 사원의 사원번호를 입력하세요 : '

declare
    vempno  number := &vempno;
    vsal    number;
begin
    select sal into vsal
    from scott.EMP
    where empno = vempno;
    
    update scott.EMP
    set sal = vsal * 1.1
    where empno = vempno;
    
    commit;
    
    dbms_output.put_line(vempno||'사원의 급여가 '||vsal||'에서 '||vsal*1.1||'로 변경되었습니다.');
end;
/

-- 그러면
SQL> @ex3.sql
당첨된 사원의 사원번호를 입력하세요 : 7369
7369사원의 급여가 800에서 880로 변경되었습니다.

-- 그리고
SQL> select sal from scott.emp where empno=7369;
       SAL
----------
       880

예제2.

이미 입력된 학생의 시험성적을 변경(학번과 변경 후 시험성적 입력받음). 시험성적 변경에 따른 학점 변경.

** 사전 설정

select * from SCOTT.EXAM_01;
select * from SCOTT.HAKJUM;

create table scott.stu_hakjum
as
select e.studno, h.grade
from SCOTT.EXAM_01 e, SCOTT.HAKJUM h
where e.total between h.min_point and h.max_point;

select * from SCOTT.stu_hakjum;

** plsql

> !vi ex4.sql
------------------------------------------------------
accept vstudno prompt '변경을 원하는 학생의 학번을 입력하세요 : '
accept vtotal prompt '해당 학생의 점수를 입력하세요 : '

declare
    vstudno  number := &vstudno;
    vtotal   number := &vtotal;
    vgrade   varchar2(5);
    extotal  number;
    exgrade  varchar2(5);
begin
    select total into extotal
    from SCOTT.EXAM_01
    where studno = vstudno;
    
    select grade into exgrade
    from SCOTT.stu_hakjum
    where studno = vstudno;
    
    dbms_output.put_line(vstudno||' ex total : '||extotal||', grade : '||exgrade);
    
    update scott.EXAM_01
    set total = vtotal
    where studno = vstudno;
    
    select grade into vgrade
    from SCOTT.HAKJUM
    where vtotal between min_point and max_point;
    
    update SCOTT.stu_hakjum
    set grade = vgrade
    where studno = vstudno;
    
    commit;
    
    dbms_output.put_line(vstudno||' new total : '||vtotal||', grade : '||vgrade);
end;
/

-- 그러면
SQL> @ex4.sql
변경을 원하는 학생의 학번을 입력하세요 : 9414
해당 학생의 점수를 입력하세요 : 90
9414 ex total : 90, grade : A0
9414 new total : 90, grade : A0

참조 변수 선언

다른 변수나 컬럼을 참조하여 새로운 변수를 정의.

ex)
declare
    vempno scott.emp.empno&type;

예제) 학번을 입력받고 해당 학생의 시험성적, 학점 출력

> !vi ex5.sql
------------------------------------------------------
accept vstudno prompt '학번을 입력하세요 : '

declare
    vstudno scott.student.studno%type := &vstudno;
    vtotal scott.exam_01.total%type;
    vgrade scott.hakjum.grade%type;
begin
    select e.total, h.grade into vtotal, vgrade
    from scott.exam_01 e, scott.hakjum h
    where e.total between h.min_point and h.max_point
    and studno = vstudno;
    
    dbms_output.put_line(vstudno||' 학생의 시험점수 : '||vtotal||' , 학점 : '||vgrade);
end;
/

-- 그러면
SQL> @ex5.sql
학번을 입력하세요 : 9411
9411 ????????? ???????????? : 97 , ?????? : A+

외부 변수 insert

외부 변수의 값을 특정 테이블에 insert 시 입력 변수 사용
** 문자는 ‘로 감싸줘야 한다, number는 안 그래도 됨.

-- 사전 설정
create table scott.table1

-- plsql
> !vi ex7.sql
accept vno prompt '사원번호를 입력하세요 : '
accept vname prompt '이름을 입력하세요 : '
accept vaddr prompt '주소를 입력하세요 : '

declare
    vno     number := &vno;
    vname   varchar2(10) := '&vname';
    vaddr   varchar2(50) := '&vaddr';
begin
    insert into scott.table1 values(vno, vname, vaddr);
    commit;
    dbms_output.put_line('no : '||vno||', name : '||vname||', addr : '||vaddr||' 값이 입력되었습니다.');
end;
/

[if문]

SQL문에는 사용 불가, PL/SQL문에 가능

** 문법1
begin
    if 조건1 then 리턴1;
    end if;
    if 조건2 then 리턴2;
    end if;
end;
/
** 문법2
begin
    if (조건1)  -- 괄호 생략 가능
        then
            명령어1;
            명령어2;
        else
            명령어1;
            명령어2;
    end if;
end;
/
** 문법3
begin
    if (조건1)  -- 괄호 생략 가능
        then
            명령어1;
            명령어2;
    elsif (조건2)
        then
            명령어1;
            명령어2;
        else
            명령어1;
            명령어2;
    end if;
end;
/

예제1

emp 테이블에서 사번을 입력받고 해당 사원의 부서명을 아래와 같이 출력.
– 7369의 소속 부서명 : 인사부
deptno 10=인사부/20=총무부/30=재무부

> !vi ex8.sql
--------------------------
accept vempno prompt '사번을 입력하세요 : '

declare
    vempno  number := &vempno;
    vdeptno number;
    vdname  varchar2(10);
begin
    select deptno into vdeptno
    from scott.emp
    where empno = vempno;
    
    if (vdeptno = 10) then
        vdname := '인사부';
    end if;
    if (vdeptno = 20) then
        vdname := '총무부';
    end if;
    if (vdeptno = 30) then
        vdname := '재무부';
    end if;
    
    dbms_output.put_line(vempno||'의 소속 부서명 : '||vdname);
end;
/

-- 그러면
SQL> @ex8.sql
사번을 입력하세요 : 7369
7369의 소속 부서명 : 총무부

예제2

student 테이블에서 학번을 입력받고 해당 학생의 키, 몸무게, 비만 여부를 출력.
** 표준체중 = (키 – 100) * 0.9
표준체중 < 체중 : 과체중
표준체중 > 체중 : 저체중
표준체중 = 체중 : 표준

> !vi ex9.sql
----------------------------------------------
accept vstudno prompt '학번을 입력하세요 : '

declare
    vstudno number := &vstudno;
    vhei    number;
    vwei    number;
    vfat    varchar2(10);
begin
    select height, weight into vhei, vwei
    from scott.student
    where studno = vstudno;
    
    if (0.9*(vhei-100) < vwei) then
        vfat := 'over';
    elsif (0.9*(vhei-100) = vwei) then
        vfat := 'avg';
    else
        vfat := 'less';
    end if;
    
    dbms_output.put_line('height : '||vhei||', weight : '||vwei||', fat : '||vfat);
end;
/

-- 그러면
SQL> @ex9.sql
학번을 입력하세요 : 9411
height : 180, weight : 72, fat : avg
SQL> @ex9.sql
학번을 입력하세요 : 9412
height : 172, weight : 64, fat : less

[case문]

SQL, PL/SQL 모두에서 사용 가능.

예제1)

바로 위 1번에서 한 예제 case문으로 변경

accept vempno prompt '사번을 입력하세요 : '

declare
    vempno  number := &vempno;
    vdeptno number;
    vdname  varchar2(10);
begin
    select deptno into vdeptno
    from scott.emp
    where empno = vempno;
    
    vdname := case when vdeptno = 10 then '인사부'
                          when vdeptno = 20 then '총무부'
                          else '재무부'
                   end;
    
    dbms_output.put_line(vempno||'의 소속 부서명 : '||vdname);
end;
/

예제2)

바로 위 2번에서 한 예제 case문으로 변경

accept vstudno prompt '학번을 입력하세요 : '

declare
    vstudno number := &vstudno;
    vhei    number;
    vwei    number;
    vfat    varchar2(10);
begin
    select height, weight into vhei, vwei
    from scott.student
    where studno = vstudno;
    
    vfat := case when (0.9*(vhei-100) < vwei) then 'over'
                    when (0.9*(vhei-100) = vwei) then 'avg'
                    else 'less'
              end;
    
    dbms_output.put_line('height : '||vhei||', weight : '||vwei||', fat : '||vfat);
end;
/

[실습]

delivery 테이블에서 일자와 업종을 입력 시 해당 업종의 전이랒 대비 통화량 증가율 출력

** 사전 설정

insert into scott.DELIVERY
select * from scott.DELIVERY@dblink_orcl;
commit;

select * from SCOTT.DELIVERY;
> !vi ex12.sql
-----------------------------------------------------
accept vdate prompt '일자를 입력하세요 : '
accept vshop prompt '업종을 입력하세요 : '

declare
    vdate       varchar2(8) := '&vdate';
    vshop       varchar2(100) := '&vshop';
    vyesterday  number;
    vtoday      number;
begin
    select sum(통화건수) into vtoday 
    from SCOTT.DELIVERY
    group by 일자, 업종
    having 일자 = vdate
    and 업종 = vshop;
    
    select sum(통화건수) into vyesterday 
    from SCOTT.DELIVERY
    group by 일자, 업종
    having 일자 = vdate-1
    and 업종 = vshop;   
    
    dbms_output.put_line('yesterday : '||vyesterday||', today : '||vtoday||', increase% : '||(vyesterday-vtoday)/vyesterday*100||'%');
end;
/

-- 그러면
SQL> @ex12.sql
일자를 입력하세요 : 20180202
업종을 입력하세요 : 피자
yesterday : 5733, today : 6411, increase% :
-11.82626896912611198325484039769754055468%
-- 강사님 코드 증가율을 lag과 윈도우 함수를 이용해서 한 쿼리로 구해버림.
-- 한 쿼리로 하는게 데이터베이스에 한 번만 접근하면 돼서 성능이 더 좋음.
select 증가율
from (select 일자, round((sum(통화건수) - lag(sum(통화건수)) over(order by 일자)) / lag(sum(통화건수)) over(order by 일자) * 100, 2) as 증가율
      from SCOTT.DELIVERY
      where 업종 = '음식점-중국음식'
      group by 일자)
where 일자 = '20180205';

accept vdate prompt '일자를 입력하세요 : '
accept vname prompt '업종을 입력하세요 : '

declare
    -- 여기서 ' 안 썼는데 그러면 실행할 때 accept 부분에서 직접 ' 넣어서 입력시켜야됨.
    -- ex) '피자', '20180205'
    vdate   varchar2(8) := &vdate;
    vname   varchar2(100) := &vname;
    vrate   number;
begin
    select 증가율 into vrate
    from (select 일자, round((sum(통화건수) - lag(sum(통화건수)) over(order by 일자)) / lag(sum(통화건수)) over(order by 일자) * 100, 2) as 증가율
          from SCOTT.DELIVERY
          where 업종 = vname
          group by 일자)
    where 일자 = vdate;
    
    dbms_output.put_line(vname||'의 '||vdate||'날의 이전일자 대비 통화건수 증가율 : '||vrate);
end;
/

Leave a Comment