생각하는 감쟈

[Oracle] PL/SQL 본문

Language/Oracle

[Oracle] PL/SQL

생각하는 감쟈🥔 2024. 4. 4. 14:08

 PL/SQL 

 - Procedure Language - SQL
 - 구조적 언어의 SQL의 제한적 성질( 분기문, 반복븜, 변수의 부재)
 - block 구조로 여러 ISQL문을 한번에 실행 가능
 - 모듈화/캡슐화가 가능
 - 서버에 저장디어 빠른 실행과 반복사용 가능
 - 표준 문법이 없음( 각 DBMS에 종속적)
 - 익명블록(Anonymous Block), User Defined Function, Stored PRocedure, Trigger, Package 등 제공

 

1. 익명 블록


   - 이름이 없는 블록
   - PL/SQL의 기본 구조 제공
   - 선언부/실행부로 구성
   
 (구성요소)

  DECLARE
    선언부 : 변수/상수/커서 선언
  BEGIN 
    실행부 : 처리항 비지니스 로직을 구현한 SQL 문
      :
    [EXCEPTION
        예외처리;
    ]
 END;


 
 사용예) 202SUS 5월 가장 많이 구매한 고객의; 회운번호, 이름 주소 구매액을 출력하는 익명 블록
     

	DECLARE
            L_MID MEMBER.MEM_ID%TYPE; --회원번호
            L_MNAME VARCHAR2(100);  --회원명
            L_ADDR VARCHAR2(255);    --주소
            L_AMT NUMBER:=0;    --구매금액
        BEGIN
            --2020 5월 가장 많이 구매한 고객
            SELECT TA.CID, TB.MEM_NAME, TB.MEM_ADD1 || ' ' || MEM_ADD2, TA.CSUM
              INTO L_MID,L_MNAME,L_ADDR,L_AMT
              FROM (SELECT A.CART_MEMBER AS CID,
                           SUM(A.CART_QTY*B.PROD_PRICE) AS CSUM
                      FROM CART A, PROD B
                     WHERE A.CART_PROD=B.PROD_ID
                       AND A.CART_NO LIKE'202005%'
                     GROUP BY A.CART_MEMBER
                     ORDER BY 2 DESC) TA, MEMBER TB
             WHERE TA.CID = TB.MEM_ID
               AND ROWNUM=1; --ROWNUM은 GROUP/ORDER 보다 더 빨리 실행되어져서 서브쿼리로 넣어 줌
               
          DBMS_OUTPUT.PUT_LINE('회원번호 : ' || L_MID);
          DBMS_OUTPUT.PUT_LINE('회원명 : ' || L_MNAME);
          DBMS_OUTPUT.PUT_LINE('회원주소 : ' || L_ADDR);
          DBMS_OUTPUT.PUT_LINE('구매금액 : ' || L_AMT);
          
        EXCEPTION
         WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('오류발생 : ' || SQLERRM);
        END;
        
-- := 하나의 연산자에 할당연산자 오른쪽에 있는걸 왼쪽에 있는걸로 넣으세요


    

    
    

 1. 변수와 상수

  (사용형식)
   식별자 [CONSTANT] 데이터타입 [:=초기값]
    . CONSTANT : 식별자를 상수로 선언할때 사용
    . 데이터타입 : 오라클에서 사용하는 모든 타입 사용 가능
    . := 초기값 을 생략하면 '식별자'에는 NULL값을 초기화 됨
    
 **오라클 변수의 종류와 선언 형식
  (1) SCLAR 변수
    . 일반적인 변수로 하나의 값만 저장하는 변수
    . 선언은 위의 사용형식에 따란 선언
    . EX) L_AMT NUMBER(5) :=0;
 
  (2) REFERENCE 변수
    . 해당 테이블의 행이나 컬럼타입을 참조라는 변수
    . (사용형식)
    . 식별자 [CONSTANT] 테이블명.컬럼명 %TYPE [:=초기값] -- COLNUM참조
    . 식별자 [CONSTANT] 테이블명%ROWTYPE [:=초기값]   -- ROW(행) 참조
    
    . EX)

	L_MILEAGE MKEMBER.MEM_MILEAGE%TYPE 
        --  M_MILEAGE변수의 타입이 MEMBER테이블의 MEM)MILEAGE컬럼의 타입과 크기가 같게 선언
        L_CART_REC CART%ROWTYPE; 
        -- L_CART_REC 변수는 CART 테이블 한 행과 같은 타입으로 선언됨. 
        -- 따라서 L_CART_REC로 CART테이블의 각 행을 참조 시킬수 있는 값의 형태는
        -- CART_MEMBER에 저장된 자료는 L_CART_REC.CART_MEMBER로 
           CART_NO L_CART_REC.CART_NO로
           CART_PROD는 L_CART_REC.CART_NO로 각각 참조 가능함


           
  (3) BIND 변수
    . 프로시저와 함수에서 사요하여 매개변수의 전달을 담당하는 변수
    . EX) 

CREATE OR REPLACR FUNCTION IN_CREATE_CAET_NO (
            P_DATE IN DATE,
            P_MEM_ID IN MEMBER.MEM_ID%TYPE)
                :
        END;
        
        CREATE OR REPLACE PROCEDURE proc_sales_report(
            P_PERIOD IN VARCHAR2,
            P_RESULT OUT VARCHAR2)
                :
            END;
       BOOLEAN : T/F/N 값을 담음


            
   
   

 2) 분기문 


   - IF문이 제공됨

(사용형식-1)
    IF 조건 THEN
        명령문1;
    [ELSE
        명령문2;]
    END IF;
    
(사용형식-2)
     IF 조건 THEN
        명령문1;
     [ELSIF 조건 THEN   -- ELSE IF 쓸때 ELSIF E 가 빠짐
        명령문2;]
          :
     END IF;
     
(사용형식-3)
     IF 조건 THEN
        IF 조건 THEN
            명령문1;
        ELSE 명령문2;
        END IF;
    [ELSE 명령문3;]
    END IF;


         
 사용예) 회원들의 마일리지를 조회하여 평균값보다 큰 마일리지를 보유한 회원은 '우수회원 입니다'라믐 메시지를, 
        평균보다 적으면 '분발하세요'라는 메시지를 출력하시오
        출력은                 평균 마일리지 :
        --------------------------------------------
            회원번호    회원명   보유말일리지    비고
        --------------------------------------------
        
        
        --------------------------------------------
        
        처리인원 :  명

        DECLARE
          L_AMILE NUMBER:=0;    -- 평균마일리지
          L_MID MEMBER.MEM_ID%TYPE;     -- 회원번호
          L_MNAME MEMBER.MEM_NAME%TYPE; -- 회원명
          L_MILE NUMBER:=0;     -- 보유마일리지
          L_REMAEKS VARCHAR2(100); -- 결과 메시지
        
          CURSOR cur_member01 IS 
           SELECT MEM_ID, MEM_NAME, MEM_MILEAGE
             FROM MEMBER;
             
        BEGIN
         OPEN cur_mrmber01;
         
         SELECT AVG(MEM_MILEAGE) INTO L_AMILE
           FROM MEMBER;
           
        DBMS_OUTPUT.PUT_LINE(LPAD('평균마일리지 :',42,' ') || L_AMILE);
        DBMS_OUTPUT.PUT_LINE(' ------------------------------------');
        DBMS_OUTPUT.PUT_LINE('회원번호   회원명    마일리지   비고');
        DBMS_OUTPUT.PUT_LINE(' ------------------------------------');

         LOOP
          FETCH cur_mrmber01 INTO L_MID, L_MNAME, L_MILE;
          EXIT WHEN cur_member01%NOTFOIND;
          IF L_MILE>LAMLIE THEN
             L_REMARKS := '우수회원 입니다';
          ELSE
            L_REMARKS := '분발하세요'
          END IF;
          DBMS_OUTPUT.PUT_LINE(LPAD(L_MID,7,' '));
          DBMS_OUTPUT.PUT('     ');
          DBMS_OUTPUT.PUT(PRAD(L_MID));
          DBMS_OUTPUT.PUT(LPAD(L_MNAME,8,' '));
          DBMS_OUTPUT.PUT(TO_CHAR(L_MILE,'999,999'));
          DBMS_OUTPUT.PUT('     ');
          DBMS_OUTPUT.PUT_LINE(L_REMARKS);
          DBMS_OUTPUT.PUT_LINE('     ');
         END LOOP;
         
        DBMS_OUTPUT.PUT_LINE('처리인원 : '||cur_member01.ROWCOUNT || '명')
          CLOSE cur_member01;
            
        
        END;

 


 사용예) 2020년 7월 28일이라고 가저하고 cart_no를 생성하는 함수를 작성하시오

	CREATE OR REPLACE FUNCTION fn_create_cartno(
            P_DATE IN DATE)
            RETURN VARCHAR2
        IS 
            L_FLAG NUMBER(2):=0;
            L_TMP_CARTNO CHAR(20);
        BEGIN
            SELECT COUNT(*) INTO L_FLAG
              FROM CART
             WHERE TO_DATE(SUBSTR(CART_NO,1,8))=P_DATE;
             
            IF L_FLAG=0 THEN
               L_TMP_CARTNO:=TO_CHAT(P_DATE,'YYYYMMDD') || TRIM('00001') ;
            ELSE
               SELECT TO_CHAR(TO_NUMBER(A.CART_NO)+1) INTO L_TMP_CARTNO
                 FROM (SELECT CART_NO
                         FROM CART
                        WHERE TO_DATE(SUBSTR(CART_NO,1,8))=TO_CHAR(P_DATE,'YYYYMMDD')
                        ORDER BY 1 DESC) A
                  AND ROWNUM=1;
            END IF;
            RETURN L_TMP_CARTNO;
        END;

 

  CREATE OR REPLACE FUNCTION fn_create_cartno(
        P_DATE IN DATE)
        RETURN VARCHAR2
      IS 
        L_FLAG NUMBER(2) := 0;
        L_TMP_CARTNO VARCHAR2(20);
      BEGIN
        SELECT COUNT(*) INTO L_FLAG
          FROM CART
        WHERE SUBSTR(CART_NO, 1, 8)=TO_CHAR(P_DATE,'YYYYMMDD');
        
        IF L_FLAG = 0 THEN 
           L_TMP_CARTNO :=TO_CHAR(P_DATE, 'YYYYMMDD') || TRIM('00001');
        ELSE
           SELECT TO_CHAR(TO_NUMBER(A.CART_NO)+1) INTO L_TMP_CARTNO
                 FROM (SELECT CART_NO
                       FROM CART
                       WHERE SUBSTR(CART_NO, 1, 8)=TO_CHAR(P_DATE, 'YYYYMMDD')
                       ORDER BY 1 DESC) A
            WHERE ROWNUM=1;
      END IF;
      RETURN L_TMP_CARTNO;
  END;
  
실행)
    INSERT INTO CART VALUES('m001', fn_create_cartno(SYSDATE),'P302000005',5);


          

  3) 반복문


  - 주로 커서 처리를 위한 목적으로 사용
  - LOOP, WHILE, FOR문이 제공 됨
  
  1) LOOP 문
    . 반복문의 기본 구조를 제공
    . 무한루프 제공

 (사용형식)
    LOOP
         반복 수행 명령문
           :
        [EXIT WHEN 조건;]
    END LOOP;
    . 'EXIT WHEN 조건' : 조건이 참이면 반복을 벗아님 (END LOOP 다음 명령 수행)


    
** 커서
    - 커서는 오라클 SQL명령으로 영향 받은 결과 집합(넓은 의미의 커서)
    - 협의의 커서는 SELECT문의 결과 집합
    - 개발자가 결과 집합내부의 자료를 접근할 수 있는 방법을 제공
    - 커서 속성
    --------------------------------------------
        커서속성              내용
    --------------------------------------------
     SQL%ISOPEN     커서가 OPEN 된 상태이면 TRUE 반환
     SQL%NOTFOUND   결과 집합에 자료가 없음면 TRUE
     SQL%FOUND      결과 집합에 자료가 있면 TRUE
     SQL%ROWCOUNT   커서내의 행의 갯수
    --------------------------------------------

    가) 익명커서
        . 일반적인 SELECT문의 결과로 이름을 부여하지 않은 커서
        . 결과가 출력될때 OPEN 되엇다가 출력이 종료되면 즉시 CLOSE 됨
        . 개발자가 커서 내부에 접근할 수 없음 (SQL%ISOPEN 값이 늘 FALSE)
    나) 명시적 커서
        . 이름을 부여한 커서
        .개발자가 커서 내부에 접근할 수 이씀
      (사용형식)
        CURSOR 커서명 [(변수명list)] IS
        . '변수list' : 커서 OPEN 문에서 전달되는 값을 보관하는 바인딩 변수로 크기를 선언하지 않음
        . 커서의 사용 절차
            커서선언(선언부) → 커서 OPEN → 커서 FETCH → 커서 종료(CLOSE)
            
        . 커서 OPEN
          - 사용할 커서를 OPEN
          - (기술형식)
          OPEN 커서명 [(변수lsit)}
          
        . 커서 FETCH
          - 커서 집합의 자료를 행단위로 읽어올때 사용
          - (기술 형식)
          FETCH 커서명 INTO 변수 list;
            . 커서문의 SELECT정의 컬럼 값을 INTO 다음 변수에 저장
            . 보통 반복문 안에 기술

 

        . 커서 종료(CLOSE)
          - OPEN 된 커서를 닫음
          - CLOSE 되지 않은 커서는 재 OPEN
          
            
            
  사용예) 키보드로 10-110 사이의 부서번호를 입력 받아 해당 부서에 근무하는 사원 정보를 출력하시오
         (커서 사용) 사원번호, 사워명, 입사일, 급여

 ACCEPT P_DEPT PROMPT '부서번호 입력(10~110) : '
DECLARE
    L_ID HR.EMPLOYEES.EMPLOYEE_ID%TYPE;    -- 사원번호
    L_NAME VARCHAR2(200);   -- 사원명
    L_HDATE DATE;   -- 입사일
    L_SAL NUMBER := 0; -- 급여
    
    CURSOR cur_emp_dept(DID HR.DEPARTMENTS.DEPARTMENT_ID%TYPE) IS
    SELECT EMPLOYEE_ID, EMP_NAME, HIRE_DATE, SALARY
      FROM HR.EMPLOYEES
     WHERE DEPARTMENT_ID = DID;
     
BEGIN
    OPEN cur_emp_dept(TO_NUMBER('&P_DEPT'));
                
    DBMS_OUTPUT.PUT_LINE(' 부서 번호 : ' || '&P_DEPT');
    
    LOOP
        FETCH cur_emp_dept INTO L_ID, L_NAME, L_HDATE, L_SAL;
        EXIT WHEN cur_emp_dept%NOTFOUND;
        
        DBMS_OUTPUT.PUT_LINE('---------------------------');
        DBMS_OUTPUT.PUT_LINE(' 사원번호 : ' || L_ID);
        DBMS_OUTPUT.PUT_LINE(' 사원명 : ' || L_NAME);
        DBMS_OUTPUT.PUT_LINE(' 입사일 : ' || L_HDATE);
        DBMS_OUTPUT.PUT_LINE(' 급여 : ' || L_SAL);
        DBMS_OUTPUT.PUT_LINE('---------------------------');

    END LOOP;
    DBMS_OUTPUT.PUT_LINE('사원수 : ' || cur_emp_dept%ROWCOUNT);
    CLOSE cur_emp_dept; -- CLOSE 문에 세미콜론 추가

END;



    2) WHILE 문
    . JAVA의 WHILE과 비슷한 구조 및 기능 제공

 (사용형식)
    WHILE 조건 LOOP
         반복 수행 명령문
           :
    END LOOP;
      - '조건'이 참이면 반복수행하고 '조건'이 거짓이면 END LOOP 이후의 명령 수행


        
           
  사용예) 거주지가 '충남'인 회원들을 출력하시오. 회원번호 회원명 주소
  

DECLARE
    L_MID MEMBER.MEM_ID%TYPE;
    L_MNAME MEMBER.MEM_NAME%TYPE;
    L_ADDR VARCHAR2(300);
    
    CURSOR cur_member IS
        SELECT MEM_ID, MEM_NAME, MEM_ADD1 || ' ' || MEM_ADD2
          FROM MEMBER
         WHERE MEM_ADD1 LIKE '충남%';
BEGIN
    OPEN cur_member;
    FETCH cur_member INTO L_MID, L_MNAME, L_ADDR;
    WHILE cur_member%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(L_MID || '  ' || L_MNAME || '  ' || L_ADDR);
        FETCH cur_member INTO L_MID, L_MNAME, L_ADDR;
    END LOOP;
    CLOSE cur_member;
END;


    3) FOR 문
    . JAVA의 FOR과 비슷한 기능 제공

 (사용형식)
    FOR 제어변수 IN [REVERSE] 초기값 .. 초종값 LOOP
        반복처리문;
    END LOOP;
    
    (커서 처리용 FOR문)
    FOR 레코드명 IN 커서명 | 커서용 SELECT 문 LOOP
     반복처리문
        :
    END LOOP;


     **  FOR 문으로 커서를 사용하는 경우 OPEN/FETCH/CLOSE 문이 생략 됨
         선언부에서 선언된 커서를 사용하거나(커서명) 도는 커서 구성에
         사용되는 SELECT 문을 직접 기술 할 수 업다
         
     ** FOR문을 사용할 경우 커서내의 컬럼 참조는
        레코드명, 컬럼명을 참조함


  사용예) 거주기가 '충남'인 회원들을 출력하시오 회원번호 회원명, 주소

        DECLARE
            CURSOR cur_for_cursor IS
                SELECT MEM_ID, MEM_NAME, MEM_ADD1|| '  ' || MEM_ADD2 AS ADDR
                  FROM MEMBER
                 WHERE MEM_ADD1 LIKE '충남%';
        BEGIN
            FOR MEM_REC IN (SELECT MEM_ID, MEM_NAME,MEM_ADD1|| '  ' || MEM_ADD2 AS ADDR
                              FROM MEMBER
                             WHERE MEM_ADD1 LIKE '충남%') LOOP
                DBMS_OUTPUT.PUT_LINE(MEM_REC.MEM_ID|| '  ' || MEM_REC,MEM_NAME||'  '||
                                     MEM_REC.ADDR);
            END LOOP;
        END;
        
        DECLARE
            CURSOR cur_for_cursor IS
                SELECT MEM_ID, MEM_NAME, MEM_ADD1 || '  ' || MEM_ADD2 AS ADDR
                  FROM MEMBER
                 WHERE MEM_ADD1 LIKE '충남%';
        BEGIN
            FOR MEM_REC IN cur_for_cursor LOOP
            DBMS_OUTPUT.PUT_LINE(MEM_REC.MEM_ID || '  ' || MEM_REC.MEM_NAME || '  ' || MEM_REC.ADDR);
            END LOOP;
        END;


            
            
  사용예) 키보드로 10-110 사이의 부서번호를 입력 받아 해당 부서에 근무하는 사원 정보를 출력하시오
         (커서 사용) 사원번호, 사워명, 입사일, 급여

ACCEPT P_DEPT PROMPT '부서번호 입력(10~110) : '
DECLARE
    L_ID HR.EMPLOYEES.EMPLOYEE_ID%TYPE;    -- 사원번호
    L_NAME VARCHAR2(200);   -- 사원명
    L_HDATE DATE;   -- 입사일
    L_SAL NUMBER := 0; -- 급여
    
    CURSOR cur_emp_dept(DID HR.DEPARTMENTS.DEPARTMENT_ID%TYPE) IS
    SELECT EMPLOYEE_ID, EMP_NAME, HIRE_DATE, SALARY
      FROM HR.EMPLOYEES
     WHERE DEPARTMENT_ID = DID;
     
BEGIN
    OPEN cur_emp_dept(TO_NUMBER('&P_DEPT'));
                
    DBMS_OUTPUT.PUT_LINE(' 부서 번호 : ' || '&P_DEPT');
    
    LOOP
        FETCH cur_emp_dept INTO L_ID, L_NAME, L_HDATE, L_SAL;
        EXIT WHEN cur_emp_dept%NOTFOUND;
        
        DBMS_OUTPUT.PUT_LINE('---------------------------');
        DBMS_OUTPUT.PUT_LINE(' 사원번호 : ' || L_ID);
        DBMS_OUTPUT.PUT_LINE(' 사원명 : ' || L_NAME);
        DBMS_OUTPUT.PUT_LINE(' 입사일 : ' || L_HDATE);
        DBMS_OUTPUT.PUT_LINE(' 급여 : ' || L_SAL);
        DBMS_OUTPUT.PUT_LINE('---------------------------');

    END LOOP;
    DBMS_OUTPUT.PUT_LINE('사원수 : ' || cur_emp_dept%ROWCOUNT);
    CLOSE cur_emp_dept; -- CLOSE 문에 세미콜론 추가

END;

'Language > Oracle' 카테고리의 다른 글

[Oracle] SQL_Trigger  (0) 2024.04.09
[Oracle] SQL_User Defined Function (Function)  (0) 2024.04.08
[Oracle] SQL 객체_04 : INDEX  (0) 2024.04.01
[Oracle] SQL 객체_03 : SYNONYM  (0) 2024.03.29
[Oracle] SQL 객체_02 : SEQUENCE  (2) 2024.03.29
Comments