mjeongriver
Published 2022. 11. 25. 15:52
day38-DB: sql TIL/SQL

1. 프로시저

-- PLSQL(프로그래밍 SQL)
/*
오라클에서 제공되는 프로그래밍 기능이다.
일반적인 프로그래밍과는 차이가 있지만, 오라클 내부에서 아주 적절한 방식을 적용해준다.
쿼리문의 집합으로 어떠한 동작을 일괄처리 하기 위한 용도로 사용됩니다.

절차형 SQL(PLSQL)은 코드 부분만 선택하여 f5로 컴파일 하여 실행합니다.
*/
-- 오라클에서는 출력 구문으로 나올 수 있는 것을 선언해줘야 함. (출력을 허용함)
set serveroutput on;

DECLARE  -- 실행시키고 싶은 부분만 선택 후 f5로 실행
    vi_num NUMBER; -- 변수 선언(변수명, 타입)
BEGIN
    vi_num := 100; -- 대입(:= - 대입 연산자)
    DBMS_OUTPUT.PUT_LINE(vi_num); -- 출력(sysout)
END;

/*
-- 연산자
일반 SQL문의 모든 연산자의 사용이 가능하고 특별하게 **는 제곱을 의미합니다 
*/
DECLARE
 a number := 2**2*3**2; --4 * 9
BEGIN
 DBMS_OUTPUT.PUT_LINE('a = ' || TO_CHAR(a));
END;

-- DML문
-- DDL문은 사용이 불가능하고, 일반적인 SQL문의 select절을 사용하는데
-- 특이한 점은 select절 아래에 into 절을 사용해 변수에 할당합니다.
DECLARE 
 --v_emp_name VARCHAR2(50); -- 사원명 변수(문자열 변수는 길이제약이 필요)
 --v_dep_name VARCHAR2(50); -- 부서명 변수
 --해당 테이블과 같은 타입의 컬럼 변수를 선언하려면 테이블명, 컬럼명%type을 사용합니다. (오류)
 v_emp_name employees.first_name%TYPE;
 v_dep_name departments.department_name%TYPE;
BEGIN
 SELECT a.first_name, b.department_name
 INTO v_emp_name, v_dep_name -- 대입을 의미
 FROM employees a
 LEFT OUTER JOIN departments b
 ON a.department_id = b.department_id
 WHERE employee_id = 100;
 
 DBMS_OUTPUT.PUT_LINE(v_emp_name || ' - ' || v_dep_name);
end;

-- select문과 insert문 dml문을 같이 사용할 수 있습니다.
create table emp_sal( -- 드래그 하고 f9
    emp_years varchar2(50),
    emp_salary number(10)
);

-- 년도별 사원의 급여합을 구해서 새로운 테이블 insert
declare
    emp_sum employees.salary%type;
    emp_years emp_sal.emp_years%type := 2008;
begin
    -- select
    select sum(salary)
    into emp_sum
    from employees
    where to_char(hire_date, 'yyyy') = emp_years;
    
    -- insert
    insert into emp_sal values(emp_years, emp_sum);

    -- commit
    commit;
    dbms_output.put_line(emp_sum);
end;

select * from emp_sal; -- 이거만 드래그해서 선택

 

1. 1) 프로시저 예제

-- 1. 구구단 중 3단을 출력하는 익명 블록을 만들어보자.
DECLARE
     
BEGIN
 DBMS_OUTPUT.PUT_LINE('3 * 1 = ' || 3*1);
 DBMS_OUTPUT.PUT_LINE('3 * 2 = ' || 3*2);
 DBMS_OUTPUT.PUT_LINE('3 * 3 = ' || 3*3);
 DBMS_OUTPUT.PUT_LINE('3 * 4 = ' || 3*4);
 DBMS_OUTPUT.PUT_LINE('3 * 5 = ' || 3*5);
 DBMS_OUTPUT.PUT_LINE('3 * 6 = ' || 3*6);
 DBMS_OUTPUT.PUT_LINE('3 * 7 = ' || 3*7);
 DBMS_OUTPUT.PUT_LINE('3 * 8 = ' || 3*8);
 DBMS_OUTPUT.PUT_LINE('3 * 9 = ' || 3*9);
END;

-- 2. 사원 테이블에서 201번 사원의 이름과 이메일 주소를 출력하는 익명 블록을 만들어보자.
declare
   -- v_emp_name VARCHAR2(50); -- 사원명 변수(문자열 변수는 길이제약이 필요)
   -- v_emp_email VARCHAR2(50);
    
   -- 해당 테이블과 같은 타입의 컬럼 변수를 선언하려면 테이블명, 컬럼명%type을 사용합니다. 
 v_emp_name employees.first_name%TYPE;
 v_emp_email employees.email%TYPE;
begin
 select first_name, email
 into v_emp_name, v_emp_email
 from employees
 where employee_id = 201;
 
DBMS_OUTPUT.PUT_LINE(v_emp_name || ' - ' || v_emp_email);

end;

-- 3. 사원 테이블에서 사원번호가 제일 큰 사원을 찾아낸 뒤
-- 이 번호 +1번으로 아래의 사원을 emps에 
-- employee_id, last_name, email, hire_date, job_id를 신규 입력하는 익명 블록을 만들어 보자.
-- hint: select 절 이후에 insert문의 사용이 가능합니다.
-- <사원명> : steven
-- <이메일> : stevenjobs
-- <입사일자> : 오늘 날짜(sysdate)
-- <job_id> : CEO
create table emps as (select * from employees where 1 = 2);

declare
    vn_max_empno employees.employee_id%type; -- 최대값 저장
    vs_email     employees.email%type; -- 이메일 저장
    
begin
    -- select
    select max(employee_id)
    into vn_max_empno
    from employees;
    
    -- insert
    insert into emps (employee_id, last_name, email, hire_date, job_id) 
    values(vn_max_empno + 1, 'steven', 'stevenjobs', sysdate, 'CEO');

    -- commit
    commit;
end;

select * from emps;

 

2. PLSQL 제어문

--PLSQL 제어문
--랜덤 수 출력
declare
    v_num number := round(dbms_random.value(0, 10) ); -- 0~10미만, round 반올림
begin
    dbms_output.put_line(v_num);
end;

-- if 문장
-- if문 (if(조건) then else end if);
declare
 num1 number := 5;
 num2 number := trunc(dbms_random.value(1, 11)); -- 랜덤수 저장
begin
    dbms_output.put_line('랜덤수' || num2);
    
 if(num1 >= num2) then 
    dbms_output.put_line(num1 || '이 큰수 입니다.'); 
 else
    dbms_output.put_line(num2 || '이 큰수 입니다.'); 
 end if;
end;

-- elseif문

declare
 ran_num number := trunc(dbms_random.value(1,101) );
begin

    if ran_num >= 90 then
        dbms_output.put_line('a학점 입니다');
    elsif ran_num >= 80 then
        dbms_output.put_line('b학점 입니다');
    elsif ran_num >= 70 then
        dbms_output.put_line('c학점 입니다');
    else
        dbms_output.put_line('d학점 입니다');
    end if; 
    
end;

------------------실습-------------------------
--첫번째 값은 rownum을 이용하면 됩니다.
--10~120 사이의 10단위 랜덤한 번호를 이용해서 department_id의 첫번째 행만 select합니다.
--뽑은 사람의 salary가 9000이상이면 높음, 5000이상이면 중간, 나머지는 낮음으로 출력

declare
 sal employees.salary%type;
 ran number := round(dbms_random.value(10, 120), -1); -- -1은 정수 첫번째 자리에서 반올림이 올라감
 
begin

    select salary
    into sal
    from employees
    where department_id = ran and rownum = 1;
    
    dbms_output.put_line('급여:' || sal);
    
    if sal >= 9000 then
    dbms_output.put_line('높음');
    elsif sal >= 5000 then
    dbms_output.put_line('중간');
    else
    dbms_output.put_line('낮음');
    end if;
    
end;

-- case when then
declare
 sal employees.salary%type;
 ran number := round(dbms_random.value(10, 120), -1); -- -1은 정수 첫번째 자리에서 반올림이 올라감
 
begin

    select salary
    into sal
    from employees
    where department_id = ran and rownum = 1;
    
    dbms_output.put_line('급여:' || sal);
    
    case when sal >= 9000 then
    dbms_output.put_line('높음');
    when sal >= 5000 then
    dbms_output.put_line('중간');
    else
    dbms_output.put_line('낮음');
    end case;
    
end;
 
-- 반복문 while, for
declare 
 a number := 3;
 b number := 1;
begin

    while b <= 9
    loop 
        dbms_output.put_line(a || ' x ' || b || ' = ' || a*b);
        b := b + 1;
    end loop;
    
end;

-- 탈출문 exit when 조건
-- conitnue when 조건
declare 
 a number := 3;
 b number := 1;
begin

    while b <= 9
    loop 
        dbms_output.put_line(a || ' x ' || b || ' = ' || a*b);
        exit when b = 5; -- 탈출
        
        b := b + 1;
    end loop;
    
end;

--실습
--1. 모든 구구단을 출력하는 익명블록을 만드세요
DECLARE
   
BEGIN   
    FOR i IN 2..9
    LOOP
        DBMS_OUTPUT.PUT_LINE ('구구단:' || i || '단');
        FOR j in 1..9
        LOOP
              DBMS_OUTPUT.PUT_LINE (i || ' x ' || j || ' = ' || i*j);      
        END LOOP;
        
        DBMS_OUTPUT.PUT_LINE ('-------------------');
    END LOOP;
END;

--2. INFO테이블(시퀀스사용)에 insert를 300번 실행하는 익명블록을 처리하세요.
DECLARE
    I NUMBER := 1;
BEGIN
    WHILE I <= 300
    LOOP
        INSERT INTO INFO(ID, TITLE, CONTENT, REGDATE) VALUES(SEQ_INFO.NEXTVAL, 'TEST'|| I, 'TEST'|| I, SYSDATE);  
        INSERT INTO INFO(ID, TITLE, CONTENT, REGDATE) VALUES(SEQ_INFO.NEXTVAL, 'ADMIN'|| I, 'ADMIN'|| I, SYSDATE); 
        
        I := I + 1;
    END LOOP;
    COMMIT;
END;

 

3. 저장 프로시저

/*
저장 프로시저 - 하나의 함수처럼 실행하기 위한 쿼리의 집합.
만드는 과정과, 실행하는 구문이 나누어 작성합니다.
*/

--프로시저 생성
/*
CREATE OR REPLACE PROCEDURE NEW_JOB_PROC --매개변수
IS --변수의 선언 영역
BEGIN --실행 영역
    DBMS_OUTPUT.PUT_LINE('HELLO WORLD!');    
END;
*/
--프로시저 실행
EXECUTE NEW_JOB_PROC;

--프로시저 매개변수 IN
/*
CREATE OR REPLACE PROCEDURE NEW_JOB_PROC
    (P_JOB_ID IN JOBS.JOB_ID%TYPE,
     P_JOB_TITLE IN JOBS.JOB_TITLE%TYPE,
     P_MIN_SAL IN JOBS.MIN_SALARY%TYPE,
     P_MAX_SAL IN JOBS.MAX_SALARY%TYPE
    )
IS
BEGIN
    INSERT INTO JOBS 
    VALUES(P_JOB_ID, P_JOB_TITLE, P_MIN_SAL, P_MAX_SAL);
    
END;
*/
EXECUTE NEW_JOB_PROC('SM_MAN1', 'sample test', 1000, 5000);

--프로시저 응용
EXECUTE NEW_JOB_PROC('SM_MAN1', 'sample test222', 1000, 5000);

/*
CREATE OR REPLACE PROCEDURE NEW_JOB_PROC
    (P_JOB_ID IN JOBS.JOB_ID%TYPE,
     P_JOB_TITLE IN JOBS.JOB_TITLE%TYPE,
     P_MIN_SAL IN JOBS.MIN_SALARY%TYPE,
     P_MAX_SAL IN JOBS.MAX_SALARY%TYPE
    )
IS
    V_COUNT NUMBER := 0; --지역변수
BEGIN

    SELECT COUNT(*)
    INTO V_COUNT
    FROM JOBS
    WHERE JOB_ID = P_JOB_ID; -- V_COUNT의 데이터개수 할당
    -- V_COUNT가 없다면 INSERT 있다면 UPDATE
    IF V_COUNT = 0 THEN
        INSERT INTO JOBS 
        VALUES(P_JOB_ID, P_JOB_TITLE, P_MIN_SAL, P_MAX_SAL);    
    ELSE 
        UPDATE JOBS 
        SET JOB_TITLE = P_JOB_TITLE,
            MIN_SALARY = P_MIN_SAL,
            MAX_SALARY = P_MAX_SAL
        WHERE JOB_ID = P_JOB_ID;
    END IF;
END;
*/

--프로시저의 디폴트 매개변수
EXECUTE NEW_JOB_PROC('SM_MAN1', 'sample test222'); --매개변수가 일치 하지 않기때문에 에러

/*
CREATE OR REPLACE PROCEDURE NEW_JOB_PROC
    (P_JOB_ID IN JOBS.JOB_ID%TYPE,
     P_JOB_TITLE IN JOBS.JOB_TITLE%TYPE,
     P_MIN_SAL IN JOBS.MIN_SALARY%TYPE := 0,
     P_MAX_SAL IN JOBS.MAX_SALARY%TYPE := 1000
    )
IS
    V_COUNT NUMBER := 0; --지역변수
BEGIN

    SELECT COUNT(*)
    INTO V_COUNT
    FROM JOBS
    WHERE JOB_ID = P_JOB_ID; -- V_COUNT의 데이터개수 할당
    -- V_COUNT가 없다면 INSERT 있다면 UPDATE
    IF V_COUNT = 0 THEN
        INSERT INTO JOBS 
        VALUES(P_JOB_ID, P_JOB_TITLE, P_MIN_SAL, P_MAX_SAL);    
    ELSE 
        UPDATE JOBS 
        SET JOB_TITLE = P_JOB_TITLE,
            MIN_SALARY = P_MIN_SAL,
            MAX_SALARY = P_MAX_SAL
        WHERE JOB_ID = P_JOB_ID;
    END IF;
END;
*/
--디폴트 매개변수
EXECUTE NEW_JOB_PROC('SA_MAN2', 'sample test222'); 
EXECUTE NEW_JOB_PROC('SA_MAN3', 'sample test222', 1000, 5000); 

----------------------------------------------------------
--매개변수 OUT
CREATE OR REPLACE PROCEDURE NEW_JOB_PROC
    (P_JOB_ID IN JOBS.JOB_ID%TYPE,
     P_JOB_TITLE IN JOBS.JOB_TITLE%TYPE,
     P_MIN_SAL IN JOBS.MIN_SALARY%TYPE := 0,
     P_MAX_SAL IN JOBS.MAX_SALARY%TYPE := 1000,
     P_RESULT OUT VARCHAR2 --OUT
    )
IS
    V_COUNT NUMBER := 0; --지역변수
BEGIN

    SELECT COUNT(*)
    INTO V_COUNT
    FROM JOBS
    WHERE JOB_ID = P_JOB_ID; -- V_COUNT의 데이터개수 할당
    -- V_COUNT가 없다면 INSERT 있다면 UPDATE
    IF V_COUNT = 0 THEN
        INSERT INTO JOBS 
        VALUES(P_JOB_ID, P_JOB_TITLE, P_MIN_SAL, P_MAX_SAL);    
        
        --OUT변수에 '성공' 할당
        P_RESULT := '성공';       
    ELSE 
        UPDATE JOBS 
        SET JOB_TITLE = P_JOB_TITLE,
            MIN_SALARY = P_MIN_SAL,
            MAX_SALARY = P_MAX_SAL
        WHERE JOB_ID = P_JOB_ID;
        
        --OUT변수에 '업데이트' 할당
         P_RESULT := '업데이트';       
        
    END IF;
    
    --예외처리
    EXCEPTION WHEN OTHERS THEN  DBMS_OUTPUT.PUT_LINE('예외가 발생했습니다');
   
END;
-- OUT변수를 활용하려면 익명블록에서 실핼
DECLARE
    STR VARCHAR2(20); -- OUT을 돌려받을 변수
BEGIN
    NEW_JOB_PROC('SA_MAN4', 'SAMPLE', 0, 1000, STR);
    DBMS_OUTPUT.PUT_LINE(STR); --결과
END;

-------------------------------------------------------------------------------
--실습
--EMPLOYEE_ID를 받아서 EMPLOYEES에 존재하면, 근속년수를 출력
--없다면, ID는 없습니다 를 출력하는 프로시저.


CREATE OR REPLACE PROCEDURE EMP_YEAR_PROC
    (P_EMP_ID IN EMPLOYEES.EMPLOYEE_ID%TYPE
    )
IS --프로시저에서 사용할 매개변수
    V_COUNT NUMBER := 0;
    V_YEAR NUMBER := 0;
BEGIN
    SELECT COUNT(*)
    INTO V_COUNT
    FROM EMPLOYEES
    WHERE EMPLOYEE_ID = P_EMP_ID;

    --
    IF V_COUNT = 0 THEN --없는경우
        DBMS_OUTPUT.PUT_LINE(P_EMP_ID || '는 없습니다');
    ELSE --존재하는 경우
        SELECT TRUNC((SYSDATE - HIRE_DATE) / 365)
        INTO V_YEAR
        FROM EMPLOYEES 
        WHERE EMPLOYEE_ID = P_EMP_ID;
        
        DBMS_OUTPUT.PUT_LINE(P_EMP_ID || '의 근속년수:' || V_YEAR);
    END IF;
   
END;

EXECUTE EMP_YEAR_PROC(101);

 

4.  트리거

/*
trigger는 테이블에 부착한 형태로써, insert, update, delete 작업이 수행될 때 
특정 코드가 작동되도록 하는 구문입니다.

트리거는 실행할 때 범위를 지정하고 f5버튼으로 부분 실행 해야 합니다.
그렇지 않으면 하나의 구문으로 인식되어 정상 동작하지 않습니다.
*/

create table tbl_test (
    id number(10),
    text varchar2(20)
);

select * from tbl_test;

-- 
set SERVEROUTPUT on;
/*
CREATE or REPLACE TRIGGER trg_test 
    AFTER update or delete -- 삭제, 수정 이후에 동작
    on tbl_test -- 부착할 테이블
    for each row -- 각 행에 적용
DECLARE -- 지역변수 선언

BEGIN
    dbms_output.put_line('트리거가 동작함');
end;
*/

insert into tbl_test values(1, '홍길동');
insert into tbl_test values(2, '이순신');
update tbl_test set text = '홍길동' where id = 1;
delete from tbl_test where id = 2;

--------------------------------------------------------------------------------
-- before vs after
/*
after 트리거 - insert, update, delete 작업 이후에 동작하는 트리거를 의미합니다.
before 트리거 - insert, update, delete 작업 이전에 동작하는 트리거를 의미합니다.

:OLD = 참조 전 열의 값(insert: 입력 전 자료, update: 수정 전 자료, delete: 삭제할 자료)
:NEW = 참조 후 열의 값(insert: 입력 할 자료, update: 수정 된 자료)
*/

create table tbl_user_backup(
    id varchar2(20),
    name varchar2(20),
    address varchar2(30),
    updatedate date default sysdate,
    m_type char(10), -- 변경 타입
    m_user varchar2(20) -- 변경한 사용자
);
select * from tbl_user_backup;

create table tbl_user( --update나 delete가 실행 되면 기존 데이터는 삭제 됨(기존에 있던 데이터를 backup 테이블에 이전)
    id varchar2(20) primary key,
    name varchar2(20),
    address varchar2(30)
);
select * from tbl_user;

-- update나 delete를 시도하면 수정 또는 삭제된 데이터를 별도의 테이블에 보관(f5 하기)
create or replace trigger trg_user_backup
    after update or delete 
    on tbl_user 
    for each row 
declare -- 변수 선언
    vn_type varchar2(10);

begin

    if updating then -- 업데이트 or 딜리트 
        vn_type := '수정';
    elsif deleting then
        vn_type := '삭제';
    end if;
    
    -- user_backup에 인서트
    insert into tbl_user_backup values (:old.id, :old.name, :old.address, sysdate, vn_type, user() ); -- 마지막, 현재 로그인된 유저 
    
end;

insert into tbl_user values('test01', 'admin', '서울');
insert into tbl_user values('test02', 'admin', '경기');
insert into tbl_user values('test03', 'admin', '부산');
update tbl_user set address = '부산' where id = 'test01'; --동작
delete from tbl_user where id = 'test02'; -- 동작

select * from tbl_user_backup;
select * from tbl_user;

-- before 
create or replace trigger trg_user_insert
    before insert -- tbl_user에 insert되기 이전에, :new-새롭게 들어가는 데이터
    on tbl_user
    for each row
declare

begin
   :new.name := substr(:new.name, 1, 1) || '**'; -- 문자열 자르기: 가장 앞자리를 자름(뒤에 변경된 데이터가 new.name으로 들어옴)/입력 전 데이터 ** 붙임
end;

insert into tbl_user values('test04', 'admin', '부산');
insert into tbl_user values('test05', 'admin', '경기');


select * from tbl_user;

'TIL > SQL' 카테고리의 다른 글

서브쿼리 정리  (0) 2023.05.18
day37-DB: sql  (0) 2022.11.22
day36-DB: sql  (0) 2022.11.21
day35-DB: sql  (0) 2022.11.18
day34-DB: sql  (0) 2022.11.17
profile

mjeongriver

@mjeongriver

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그