Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

Everything has an expiration date

[Oracle - PL/SQL] 20231107 [프로그램 소스] 본문

[Oracle]/PL·SQL (Program source)

[Oracle - PL/SQL] 20231107 [프로그램 소스]

Jelly-fish 2023. 11. 7. 17:48
  • PROCEDURE(출고 업데이트, 입고수량 업데이트, 입고수량 삭제, 출고수량 삭제)
  • CURSOR
  • TRIGGER

 

[처음 배운 개념] - `:OLD` , `:NEW`

 

Ⅰ [UPDATE의 의미]

--※ UPDATE 
내부적으로 DELETE 그리고 INSERT 가 결합된 형태
UPDATE 하기 이전의 데이터는   『:OLD』
UPDATE 수행한 이후의 데이터는 『:NEW』

 

 

Ⅱ [`:OLD`, `:NEW`]

 

 

 

20231107_01_SCOTT(PLSQL).SQL
SELECT USER
FROM DUAL;
--==>> SCOTT


--○ TBL_출고 테이블에서 출고수량을 수정(변경)하는 프로시저를 작성한다.
--   프로시저명 : PRC_출고_UPDATE()
/*
실행 예)
EXEC PRC_출고_UPDATE(출고번호, 변경할수량);
*/

-- 잘못 들어갔던 출고수량을 다시 더해주고, 그 결과값에서 변경할 수량을 빼 준다.
-- 20 -> 50 가능
-- 20 -> 60 불가능(재고수량초과)
--********************************************************************
-- ① 잘못 들어갔던 출고 수량을 상품의 재고 수량에 더해준다.
-- ② 그 수량이 현재의 변경할 수량보다 작거나 같으면 : 변경 가능하다.
--                                            크면 : 변경이 불가능하다!!!!
--********************************************************************


--[선생님 프로시저~!!!]

CREATE OR REPLACE PROCEDURE PRC_출고_UPDATE2
( 
    -- ① 매개변수 구성    
  V_출고번호        IN TBL_출고.출고번호%TYPE
, V_출고수량      IN TBL_출고.출고수량%TYPE

)
IS

    -- ③ 필요한 변수 선언
    V_상품코드          TBL_상품.상품코드%TYPE;
    V_이전출고수량      TBL_출고.출고수량%TYPE;
    V_재고수량          TBL_상품.재고수량%TYPE;
    USER_DEFINE_ERROR   EXCEPTION;

BEGIN

    -- ④ 선언한 변수에 값 담아내기
    -- ★ [INTO] : 한꺼번에 두 개의 변수에 값을 담을 수 있다는것을 항상 기억하자! ★
    SELECT 상품코드, 출고수량 INTO V_상품코드, V_이전출고수량
    FROM TBL_출고
    WHERE 출고번호 = V_출고번호;
    


    -- ⑧ 출고 정상수행여부 판단 필요
    -- 변경 이전의 출고수량 및 현재의 재고수량 확인
    
    SELECT 재고수량 INTO V_재고수량
    FROM TBL_상품
    WHERE 상품코드 = V_상품코드;



    -- ⑨ 파악한 재고수량에 따라 데이터 변경 실시 여부 판단
    --    (『재고수량 + 이전출고수량 < 현재출고수량』인 상황이라면... 사용자정의 예외 발생)
    


    IF (V_재고수량 + V_이전출고수량 < V_출고수량)
        THEN RAISE USER_DEFINE_ERROR;
        
    END IF;
    




    -- ② 수행될 쿼리문 체크(UPDATE→TBL_출고 / UPDATE→TBL_상품)
    UPDATE TBL_출고
    SET 출고수량 = V_출고수량
    WHERE 출고번호 = V_출고번호;
    
    
    
    
    
    -- ⑤
    -- 『상품 재고수량 : 재고수량 + 이전 출고수량 - 변경할 출고수량』
    
    UPDATE TBL_상품
    SET 재고수량 = 재고수량 + V_이전출고수량 - V_출고수량
    WHERE 상품코드 = V_상품코드;
    
    
    
    -- ⑩ 예외 처리
    
    EXCEPTION
        WHEN USER_DEFINE_ERROR
            THEN RAISE_APPLICATION_ERROR(-20002, '재고 부족~!!!');
                 ROLLBACK;
        
        WHEN OTHERS
            THEN ROLLBACK;

    
    -- ⑦ 커밋
    
    COMMIT;

    
END;
--==>> Procedure PRC_출고_UPDATE이(가) 컴파일되었습니다.




-- [변수선언]
-- USER_DEFINE_ERROR EXCEPTION

-- IF (재고수량 + V_이전출고수량 - V_출고수량)
--    THEN
--    
-- END IF;
/*
    EXCEPTION
        WHEN USER_DEFINE_ERROR
             THEN RAISE_APPLICATION_ERROR(20002, '재고부족!!');
                  ROLLBACK;
        
        WHEN OTHERS
             THEN ROLLBACK;


*/





/*
1. PRC_입고_UPDATE(입고번호, 입고수량)
2. PRC_입고_DELETE(입고번호)
3. PRC_출고_DELETE(출고번호)
*/


-- 원래들어있는 상품의 입고내역이 10개이다
-- 그것으로 인해서 현재 재고수량이 0이다.
-- 입고가 10개 됐는데 어떻게 0?
-- 10개였던 수량을 5개로 바꾼다? 불가능!
-- 출고가 10개라면... 불가!







-- 1. 입고를 업데이트 한다는 건 어떤 의미일까...
--    #1 : 입고 번호에 해당하는 상품코드의 상품 재고 수량을 증가시키는 것이다.
--    #2 : 입고 수량이 출고 테이블에 있는 레코드의 총 수량만큼은 커야한다.
--    #3 : 변경된 입고 수량으로 인해 재고 수량이 출고수량보다 크거나 같다면 (○)
--       : 변경된 입고 수량으로 인해 재고 수량이 출고수량보다 작다면 (Ⅹ)

SELECT *
FROM TBL_입고;

SELECT *
FROM TBL_상품;

SELECT *
FROM TBL_출고;


-- 1. PRC_입고_UPDATE(입고번호, 입고수량)
CREATE OR REPLACE PROCEDURE PRC_입고_UPDATE
( V_입고번호    IN TBL_입고.입고번호%TYPE
, V_입고수량    IN TBL_입고.입고수량%TYPE
)
IS
    V_상품코드      TBL_상품.상품코드%TYPE;
    V_재고수량      TBL_상품.재고수량%TYPE;
    V_이전입고수량    TBL_입고.입고수량%TYPE;
    V_총출고수량     TBL_출고.출고수량%TYPE;
    V_총입고수량     TBL_입고.입고수량%TYPE;
    V_최종재고수량    TBL_상품.재고수량%TYPE;
    
    USER_DEFINE_ERROR   EXCEPTION;

BEGIN
 
    -- ⓐ 입고 번호에 해당하는 상품 알아내기 [V_상품코드]
    
    SELECT 상품코드 INTO V_상품코드
    FROM TBL_입고
    WHERE 입고번호 = V_입고번호;
    
    
    -- ⓑ 상품의 재고수량 확인하기 (TBL_상품) [V_재고수량]
    
    SELECT 재고수량 INTO V_재고수량
    FROM TBL_상품
    WHERE 상품코드 = V_상품코드;
    
    
    -- ⓒ 상품의 총 출고량 확인하기 (TBL_출고) [V_총출고수량]
    
    SELECT SUM(출고수량) INTO V_총출고수량
    FROM TBL_출고
    WHERE 상품코드 = V_상품코드
    GROUP BY 상품코드;
    
    
    -- ⓓ 상품코드에 맞는 입고 총 입고수량 확인하기 [V_총입고수량]
    
    SELECT SUM(입고수량) INTO V_총입고수량
    FROM TBL_입고
    WHERE 상품코드 = V_상품코드
    GROUP BY 상품코드;
    
    
    
    -- ⓔ 입고번호에 맞는 이전 입고수량 확인하기 [V_이전입고수량]
    
    SELECT 입고수량 INTO V_이전입고수량
    FROM TBL_입고
    WHERE 입고번호 = V_입고번호;
    

    --[V_최종재고수량]
    -- 이전 입고수량을 초기화 하고
    -- 변경된 입고수량을 더한 최종 재고수량 구하기 (이전입고수량 취소)
    -- [V_총입고수량 - V_이전입고수량 + V_입고수량(변경후 입고수량) - V_총출고수량]
    
    V_최종재고수량  := V_총입고수량 - V_이전입고수량 + V_입고수량 - V_총출고수량;
    
    
    
    
     -- ⓔ 변경 가능한지 확인.
    --    #3 : 변경된 입고 수량으로 인해 재고 수량이 출고수량보다 크거나 같다면 (○)
    --       : 변경된 입고 수량으로 인해 재고 수량이 출고수량보다 작다면 (Ⅹ)
    
    
    -- 최종재고수량이 음수가 됐다면, 변경이 불가능한 입고 업데이트 수량이므로
    -- 사용자 정의 예외를 발생시켜야 한다.
    
    IF (V_최종재고수량 < 0)
        THEN RAISE USER_DEFINE_ERROR;
        
    END IF;
    
    
    
    -- 업데이트 실행
    -- (TBL_입고 테이블의 입고번호에 맞는 입고 수량을 V_입고수량으로 변경)
    
    UPDATE TBL_입고
    SET 입고수량 = V_입고수량
    WHERE 입고번호 = V_입고번호;
    
    
    -- (TBL_상품 테이블의 상품코드에 맞는 상품의 재고를 최종재고수량으로 변경)
    
    
    UPDATE TBL_상품
    SET 재고수량 = V_최종재고수량
    WHERE 상품코드 = V_상품코드;
    
    
    -- 예외가 발생했을 때 처리
    
    EXCEPTION
        WHEN USER_DEFINE_ERROR
            THEN RAISE_APPLICATION_ERROR(-20004, '입고수량 잘못됨!');
                 ROLLBACK;
        
        WHEN OTHERS
             THEN ROLLBACK;
             
    
    COMMIT;



END;
--==>> Procedure PRC_입고_UPDATE이(가) 컴파일되었습니다.








/*

            입고
            
1            10
2            10
3            10
4            10
5            10
6            10
7            10
8            10
9            10
10            10
11            10
12            10
    

모든 시간대의 입고 재고 출고를 고려해야한다.
입고 테이블 업데이트 시점, 호출시점
입고가 일어난 시간 출고가 일어난시간 재고가 변경된 시간을 다 따져야 한다.
그렇게까지 접근하진 않는다.
현재 재고상황하고 트랜 잭션 처리를 해라.
미래의 것, 이전의 과거것 까지 모두 고려해서 만드는 것은 상당히 어렵고 힘들다.
결과적으로 재고가 '-'가 되지 않게끔만 구성하면 된다.


*/





/*
1. PRC_입고_UPDATE(입고번호, 입고수량)
2. PRC_입고_DELETE(입고번호)
3. PRC_출고_DELETE(출고번호)
*/



--2. PRC_입고_DELETE(입고번호)




CREATE OR REPLACE PROCEDURE PRC_입고_DELETE
( V_입고번호    IN TBL_입고.입고번호%TYPE
)
IS
    
    V_상품코드        TBL_상품.상품코드%TYPE;
    V_재고수량        TBL_상품.재고수량%TYPE;
    V_최종재고수량    TBL_상품.재고수량%TYPE;
    V_이전입고수량    TBL_입고.입고수량%TYPE;
    
    
    USER_DEFINE_ERROR   EXCEPTION;
    
BEGIN

-- #1 입고번호에 해당하는 상품코드, 이전입고수량 확인
    
    SELECT 상품코드, 입고수량   INTO V_상품코드, V_이전입고수량
    FROM TBL_입고
    WHERE 입고번호 = V_입고번호;
    
    
-- #2 상품코드의 재고 수량 확인
    
    SELECT 재고수량 INTO V_재고수량
    FROM TBL_상품
    WHERE 상품코드 = V_상품코드;





-- #3 최종 재고수량 계산 (재고수량 - 이전입고수량)

    V_최종재고수량 := V_재고수량 - V_이전입고수량;

-- #4 재고수량 - 이전입고수량 < 0 이 되선 안 된다.
    
    IF (V_최종재고수량 < 0)
        THEN RAISE USER_DEFINE_ERROR;
        
    END IF;
    
    
-- #5 DELETE, UPDATE
--   1 (TBL_입고 = 입고번호에 맞는 레코드 제거)
--   2 (TBL_상품 = 재고수량 - 이전입고수량)


    DELETE
    FROM TBL_입고
    WHERE 입고번호 = V_입고번호;
    
    
    UPDATE TBL_상품
    SET 재고수량 = 재고수량 - V_이전입고수량
    WHERE 상품코드 = V_상품코드;
    

-- #6 예외처리

    EXCEPTION
        WHEN USER_DEFINE_ERROR
            THEN RAISE_APPLICATION_ERROR(-20005, '입고취소불가능!');
                 ROLLBACK;
                 
        WHEN OTHERS
            THEN ROLLBACK;
    
    
-- #7 커밋

    COMMIT;

END;




--3. PRC_출고_DELETE(출고번호)

-- 출고번호에 맞는 상품코드 확인
-- 상품코드에 해당하는 상품의 재고수량 + 취소된 출고수량


CREATE OR REPLACE PROCEDURE PRC_출고_DELETE
( V_출고번호    IN TBL_출고.출고번호%TYPE
)
IS
    V_상품코드        TBL_상품.상품코드%TYPE;
    V_출고수량        TBL_출고.출고수량%TYPE;
    V_재고수량        TBL_상품.재고수량%TYPE;
    V_최종재고수량    TBL_상품.재고수량%TYPE;

    
BEGIN
    
    -- #1 출고번호에 맞는 상품코드 확인, 출고수량 확인
    
    SELECT 상품코드, 출고수량 INTO V_상품코드, V_출고수량
    FROM TBL_출고
    WHERE 출고번호 = V_출고번호;
    
    
    -- #2 상품코드에 맞는 재고수량 확인
    
    SELECT 재고수량 INTO V_재고수량
    FROM TBL_상품
    WHERE 상품코드 = V_상품코드;
    
    
    -- #3 최종 재고수량 구하기
    
    V_최종재고수량 := V_재고수량 + V_출고수량;
    

    -- 최종 재고수량으로 업데이트
    
    
    -- ⓐ TBL_출고 출고번호에 해당하는 레코드 제거
 
    
    DELETE
    FROM TBL_출고
    WHERE 출고번호 = V_출고번호;
    
    
    -- ⓑ TBL_상품 재고수량 = V_최종재고수량으로 변경.
       
    UPDATE TBL_상품
    SET 재고수량 = V_최종재고수량
    WHERE 상품코드 = V_상품코드;
    
    
    
    -- 예외가 발생할 경우, ROLLBACK을 처리하도록 구성.
    -- 출고수량 NULL로 처리했을 때, 한번 지우고 나서 다시 실행했을 경우
    -- 지웠던 항목이 다시 생겨나는 오류가 발생했었음...
    -- 이유는 뭔지 모르겠음...ㅠㅠ
    EXCEPTION     
        WHEN OTHERS
             THEN ROLLBACK;
        
    
    
    -- 커밋
    COMMIT;

END;



--------------------------------------------------------------------------------


--■■■ CURSOR(커서) ■■■--



--1. 오라클에서는 하나의 레코드가 아닌 여러 레코드로 구성된
--   작업 영역에서 SQL 문을 시행하고 그 과정에서 발생한 정보를
--   저장하기 위해 커서(CURSOR)를 사용하며
--   커서에는 암시적인 커서와 명시적인 커서가 있다.


--2. 암시적 커서는 모든 SQL 구문에 존재하며
--   SQL 문 실행 후 오직 하나의 행(ROW)만 출력하게 된다.
--   그러나 SQL 문을 실행한 결과물(RESULT SET)이 
--   여러 행(ROW)으로 구성된 경우
--   커서(CURSOR)를 명시적으로 선언해야 여러 행(ROW)을 다룰 수 있다.



--커서 - [깜짝 상자]와도 같은 느낌...
--커서 = 모든 SQL 구문에 존재...
--커서 = 상자처럼 생김... 내용물을압축해서 그 상자 안에 넣고 뚜껑을 닫으면...
--뚜껑을 여는 순간 튀어나온다... 한꺼번에 왁 하고 쏟아져 나온다...?


--○ 커서 이용 전 상황(단일 행 접근 시)

-- 서버의 아웃풋을 ON한다. 출력을 할 수 있도록. 세션변경.
SET SERVEROUTPUT ON;


DECLARE
    V_NAME  TBL_INSA.NAME%TYPE;
    V_TEL   TBL_INSA.TEL%TYPE;
BEGIN
    SELECT NAME, TEL INTO V_NAME, V_TEL
    FROM TBL_INSA
    WHERE NUM = 1001;
    
    DBMS_OUTPUT.PUT_LINE(V_NAME || ' -- ' || V_TEL);
END;
--==>>
/*
홍길동 -- 011-2356-4528


PL/SQL 프로시저가 성공적으로 완료되었습니다.

*/


--○ 커서 이용 전 상황(다중 행 접근 시)

-- 다중행을 처리하려면, WHERE 조건절(입사번호 비교)만 삭제하면 된다.
-- 그러면 전체 사원을 조회하므로...


DECLARE
    V_NAME  TBL_INSA.NAME%TYPE;
    V_TEL   TBL_INSA.TEL%TYPE;
BEGIN
    SELECT NAME, TEL INTO V_NAME, V_TEL
    FROM TBL_INSA;
    
    DBMS_OUTPUT.PUT_LINE(V_NAME || ' -- ' || V_TEL);
END;
--==>> 에러 발생
--     (ORA-01422: exact fetch returns more than requested number of rows)
-- 결과물이 하나의 행이 아니므로 오류가 발생한다.



SELECT *
FROM TBL_INSA;



-- WHILE LOOP를 할 때, 괄호를 씌우면 오류가 발생한다.
-- JAVA할 때 습관 때문에...

DECLARE
    V_NAME  TBL_INSA.NAME%TYPE;
    V_TEL   TBL_INSA.TEL%TYPE;
    N       NUMBER;
BEGIN
    N := 1001;
    
    WHILE (N <= 1061) LOOP
    
        SELECT NAME, TEL INTO V_NAME, V_TEL
        FROM TBL_INSA
        WHERE NUM = N;
        
        DBMS_OUTPUT.PUT_LINE(V_NAME || ' -- ' || V_TEL);
        
        N := N + 1;
        
    
    END LOOP;
   
    
   
END;


--[선생님 풀이!!!]

DECLARE
    V_NAME  TBL_INSA.NAME%TYPE;
    V_TEL   TBL_INSA.TEL%TYPE;
    V_NUM   TBL_INSA.NUM%TYPE := 1001;
BEGIN
 
    LOOP
    
        SELECT NAME, TEL INTO V_NAME, V_TEL
        FROM TBL_INSA
        WHERE NUM = V_NUM;
        
        DBMS_OUTPUT.PUT_LINE(V_NAME || ' -- ' || V_TEL);
        
        V_NUM := V_NUM + 1;
        
        EXIT WHEN V_NUM >= 1062;
        
        
    END LOOP;
        
        
END;
--==>>
/*
홍길동 -- 011-2356-4528
이순신 -- 010-4758-6532
이순애 -- 010-4231-1236
김정훈 -- 019-5236-4221
한석봉 -- 018-5211-3542
이기자 -- 010-3214-5357
장인철 -- 011-2345-2525
김영년 -- 016-2222-4444
나윤균 -- 019-1111-2222
김종서 -- 011-3214-5555
유관순 -- 010-8888-4422
정한국 -- 018-2222-4242
조미숙 -- 019-6666-4444
황진이 -- 010-3214-5467
이현숙 -- 016-2548-3365
이상헌 -- 010-4526-1234
엄용수 -- 010-3254-2542
이성길 -- 018-1333-3333
박문수 -- 017-4747-4848
유영희 -- 011-9595-8585
홍길남 -- 011-9999-7575
이영숙 -- 017-5214-5282
김인수 -- 
김말자 -- 011-5248-7789
우재옥 -- 010-4563-2587
김숙남 -- 010-2112-5225
김영길 -- 019-8523-1478
이남신 -- 016-1818-4848
김말숙 -- 016-3535-3636
정정해 -- 019-6564-6752
지재환 -- 019-5552-7511
심심해 -- 016-8888-7474
김미나 -- 011-2444-4444
이정석 -- 011-3697-7412
정영희 -- 
이재영 -- 011-9999-9999
최석규 -- 011-7777-7777
손인수 -- 010-6542-7412
고순정 -- 010-2587-7895
박세열 -- 016-4444-7777
문길수 -- 016-4444-5555
채정희 -- 011-5125-5511
양미옥 -- 016-8548-6547
지수환 -- 011-5555-7548
홍원신 -- 011-7777-7777
허경운 -- 017-3333-3333
산마루 -- 018-0505-0505
이기상 -- 
이미성 -- 010-6654-8854
이미인 -- 011-8585-5252
권영미 -- 011-5555-7548
권옥경 -- 010-3644-5577
김싱식 -- 011-7585-7474
정상호 -- 016-1919-4242
정한나 -- 016-2424-4242
전용재 -- 010-7549-8654
이미경 -- 016-6542-7546
김신제 -- 010-2415-5444
임수봉 -- 011-4151-4154
김신애 -- 011-4151-4444
최혜인 -- 010-2509-1783


PL/SQL 프로시저가 성공적으로 완료되었습니다.
*/


/*
일반변수 → 선언
예외변수 → 선언

변수명 자료형;
V_NUM   NUMBER;
U_ERR   EXCEPTION;
커서명  CURSOR;        --(Ⅹ)
*/

/*
커서 → 정의
(선언과는 달리)
(자료형 변수명 으로 구성이 된다.)

TABLE 테이블명
INDEX 인덱스명
USER  유저명

CURSOR 커서명        --(○)

*/

--[전체적인 커서 사용의 흐름 재정리]==============================================

-- 커서를 정의한다고 한 이유 : 데이터타입을 먼저 적은 후 변수명(커서명)을 적으므로
-- 커서 정의 : VIEW를 정의하는 것과 유사하다.
-- 만들어진 커서는 뚜껑을 열어서 오픈해야 쓸 수 있다.
-- 커서를 오픈해야 한다. -> 오픈 커서
-- 커서를 오픈하면 데이터가 와~하고 쏟아져 나오기 때문에
-- 배추, 연탄을 나르듯이 한 행 한 행 커서를 가져오는 형태로 담는다.
-- 한 행 한 행 변수에 담았으면 그것을 출력한다.
-- 담고 쓰고 담고 쓰고... 이런 반복을 멈추는 조건은
-- 커서를 참조할 때 그 상태가 NOTFOUND 상태이면 반복을 멈추면 된다.
-- 모든 데이터를 출력하고 나면 커서를 닫아준다.
-- 클로즈 커서.
-- 오라클에서 자동으로 닫아주는 커서들은 오히려 클로즈 구문 때문에 에러가 나는 것도 있다.
-- 그런 커서의 경우에는 클로즈 커서 구문을 작성하지 않으면 오류가 해결된다.
--==============================================================================


--○ 커서 이용 후 상황

DECLARE
    -- 주요 변수 선언
    V_NAME  TBL_INSA.NAME%TYPE;
    V_TEL   TBL_INSA.TEL%TYPE;
    
    -- 커서 이용을 위한 커서 정의 CHECK~!!!
    --【 앞 TYPE, 뒤 변수명 】
    -- 이 커서는, TBL_INSA 테이블의 NAME 컬럼과 TEL 컬럼을 조회하는 커서이다.
    -- 깜짝 상자 안에, TBL_INSA의 NAME, TEL 정보를 꾹 눌러담아서 뚜껑을 덮어둔 상태!!
    CURSOR  CUR_INSA_SELECT
    IS
    SELECT NAME, TEL
    FROM TBL_INSA;
    
BEGIN
    -- *(커서는 열어야 쓸 수 있다!)
    -- 오픈 커서
    OPEN CUR_INSA_SELECT;
    -- 안에 꾹 눌러담아 놨던, TBL_INSA의 NAME, TEL 정보가 와악!! 하고 나온다...
    -- 커서 뚜껑을 연 상황 → 데이터가 하나씩 계속 날라오는 상황!!
    -- 연탄을 하나씩 넘겨넘겨 넘겨서 받어 넘겨받어!!! 
    
    
    -- 커서 오픈 시 (와~!!!)  쏟아져나오는 데이터를 처리(반복문을 활용하여 처리)
    LOOP
    
        --# [FETCH] : 가져오다.
         
        -- 한 행 한 행 받아다가 처리하는 행위 → 가져오다(데려오다) → 『FETCH』
        -- 패치하겠다. 커서명...
        
        -- CUR_INSA_SELECT 내부에... 아래의 쿼리문이 있는 것.
        -- [SELECT NAME, TEL
        --  FROM TBL_INSA;]
        
        -- 이것을 변수에 저장하듯이 담아낼 수 있다 하나씩...
        
        FETCH CUR_INSA_SELECT INTO V_NAME, V_TEL;
        
       -- 언제까지??? (EXIT WHEN)
       -- → 커서에서 더 이상 데이터가 쏟아져 나오지 않는 상태... → 『NOTFOUND』
        
        -- [%] : 참조하기 위한 기호
        -- 참조하려고 봤더니... NOTFOUND야...
        -- 더 이상 쏟아져 나올 데이터가 없어. 바닥이 보여. 이때까지 반복해라...
        EXIT WHEN CUR_INSA_SELECT%NOTFOUND;
        
        -- 출력
         DBMS_OUTPUT.PUT_LINE(V_NAME || ' -- ' || V_TEL);
        
        
    END LOOP;
    
    -- 커서를 사용했으면 닫아 주어야 한다.


    -- 클로즈 커서 
    CLOSE CUR_INSA_SELECT;
    
    
END;


--------------------------------------------------------------------------------


--■■■ TRIGGER(트리거) ■■■--

-- 부비트랩 : 계속 저걸 지켜보고 있다가... 내가 그 자리에 없더라도
--            뭔가를 설치해놓고 자리를 비웠어. 그렇더라도... 내가 원하던 그 일이
--            벌어지게끔 만든다!!

-- ▶  설치만 해두고... 나는 그 자리를 떠난다!
--     거기를 지나가는 당사자는 자기가 그랬다는 걸 모르고... 폭탄에 당해버려...

-- 사전적인 의미 : 방아쇠, 촉발시키다, 야기하다, 유발하다.


-- 1. TRIGGER(트리거)란 DML 작업 즉, INSERT, UPDATE, DELETE 작업이 일어날 때
--    자동적으로 실행되는(유발되는, 촉발되는) 객체로
--    이와 같은 특징을 강조하여 DML TRIGGER 라고 부르기도 한다.
--    TRIGGER 는 무결성 뿐 아니라
--    다음과 같은 작업에도 널리 사용된다.


-- 자동으로 파생된 열 값 생성
--==>> 내가 자리를 비웠을 떄 떠들면... 떠든 사람 : 김뫄뫄 반장이나 부반장이 작성...
--     노트를 열어두고, 자동으로 이름을 적게끔 만든다.


-- 잘못된 트랜잭션 방지
--==>> 입고가 하나 했으면, 재고가 하나 늘고... 출고가 하나 되면 재고가 하나 줄어들도록.
--     모두 성공했을 때만 커밋. 하나라도 실패하면 ROLLBACK


-- 복잡한 보안 권한 강제 수행
--==>> 복잡한 업무 규칙 강요. 업무 시간에는 주식 사이트에 접속 못하도록 막아놨어.
--     업무시간에는 메시지 시스템이 안 돌도록 그룹 웨어 안에다가 그런 기능을 삽입해놨어.
--     그렇게 되면 업무시간이 지난 후에만 사용가능한 것. 이것과 비슷한 개념.

-- 분산 데이터베이스 노드 상에서 참조 무결성 강제 수행

-- [참조 무결성] : 저 쪽에 있는걸. 사원을 등록하는 과정에서 부서번호 10, 20 30만 가능하도록 하는것이 참조무결성
-- 서버가 나눠져있는 것. 서로 다른 테이블들끼리 제약조건을 둬야하는데 그럴 수 없는 상황이다. (분산되어 있으므로)
-- DEPT → EMP 같은 테이블스페이스이면 제약조건 설정 가능
-- 10 20 30 번 외에 40번 부서번호를 갖는 사원을 EMP 테이블에 입력 못하게끔 하는것이 참조무결성.
-- 가시거리 안에 있기 때문에 제약조건 설정이 가능한 것이다. 
-- EMP (하나의 테이블스페이스(서버)) : [서울]에 있어
-- DEPT : [대전]에 있어
-- 같은 서버에 존재하지 않으므로 하나의 테이블스페이스 안에 테이블이 존재하지 않으므로(DEPT가 서울에 없음)
-- 제약조건을 설정하기가 힘들지 않을까... → 가능하게끔 하려면?
-- 40번 입력이 불가능하도록 할 때 트리거를 사용할 수 있다는 의미이다.






-- 복잡한 업무 규칙 강제 적용


-- 투명한 이벤트 로깅 제공
-- 누가 무언가를 INSERT했다...와 같은 LOG들을..
-- LOG (요새로 접근하는 나무다리. 성 안에 들어가는 역할.)
-- 그렇기 때문에 LOG IN(성안에 들어감)
-- 다리 위에 있는상태 LOG ON!!
-- 다리를 통과해서 들어오는 사람마다 기록을 남긴다 -> 로그기록
-- 그런 로그를 남기는 행위 : 로깅.
-- 이벤트가 발생했다는 기록을 남기는 것을 로그기록.



-- 복잡한 감사 제공
-- 지켜보고 감시한다. 

-- 동기 테이블 복제 유지관리
-- 동기 / 비동기... 나중에 쌤이 정리해 줄게요
-- 동기화 : 한 쪽에서 어떤 일이 일어났을 때, 다른 쪽에서도 그 일이 발생하도록 처리하는 것.
-- INSERT를 프로시저를 통하지 않고 한 쪽 테이블에서의 인서트 밸류가 다른 쪽으로도 자동으로 저장되는 것.


-- 테이블 엑세스 통계 수집
-- 이 테이블에 누가 접근했는지의 통계.
-- 경태가 이 테이블에 3번 INSERT했네, 민지가 3번 DELETE했네 그것을 적어놔야
-- 통계를 낼 수 있으니까. 그걸 도와주는 것이 트리거.


-- 사람이 그걸 직접 일일이 감시하면서 진행하기에는 어려움이 있다는 특징이 있다.
-- 점심시간 아니면 웹 브라우저 금지 : 사람이 일일이 점검하기에는 매우 어렵다.
-- 이런걸 도와주는 것이 트리거. 



--(프로시저 내부에서는 가능했지만... 트리거는...?)
-- 호출하는형식으로 쓰는것이 아니라, 사용자가 트리거가 있는지도 모르고 쓰게 된다는 것이다.
-- INSERT나 DELETE를 했다면 사용자가 COMMIT을 해야한다. 그렇기에 트리거 내부에 적지 않는 것이다.


-- 2. TRIGGER(트리거) 내에서는 COMMIT, ROLLBACK 문을 사용할 수 없다.



-- 일과시간에만 이용할 수 있는 테이블 (이 시간 동안에는 못하거나, 이 시간에만 하게끔 조건.)
-- 접근 못하게끔 막는다 : 사후에 막으면 돼요? 안 돼요!
-- 맞고나서 막으면... 아파..... BEFORE에 막아야 돼...
-- 술취한 사람에게 잡히지 말자...



-- 3. 특징 및 종류
--    - BEFORE STATEMENT
--    - BEFORE ROW
--      (앞에서 동작하는 트리거)




--    - AFTER STATEMENT
--    - AFTER ROW
--      (사후에 동작해야 하는 트리거)


-- 떠든사람 노트에 이름을 적는다. ...주형이가 5분 뒤에 떠들 것 같은데? 적어...안돼!
-- 떠들것 같다고해서 적으면 안 되고, 진짜 떠들었을 때만 이름을 적어야지!
-- AFTER TRIGGER!!


--[단일값]
-- STATE : 문장    - 하나의 케이스에 대해서 동작
--        (떠든 사람의 이름 하나만 적어야 한다. 이 레코드가 있는지 없는지 전부확인할 필요X)


--[전체 행 모두!]
-- ROW   : 모든 행 - 행을 전부 스캔해서 확인해 보면서 동작해야 돼
--        (바밤바 하나 입고 - 어떤 레코드가 바밤바 레코드인지 확인해야 한다. 각각의 행들을 살펴봐야 하는것!)



-- 이벤트 : INSERTING, UPDATING, ...
-- 모든 이벤트 : 전부 기술

-- ON [테이블명] : 부비트랩을 어디에다가 장착할래?? 어떤 테이블에다가 장착해서
--                 누가 건드리면 폭발하게끔 할래??


-- 트리거가 각각의 행 모두 스캔해야 하는 ROW타입 트리거 일 때는 FOR EACH ROW 조건을 작성한다.
-- STATE의 경우에는 하나의 행에 대해서만 처리하므로 이 조건이 필요 없다.


-- FOR EACH ROW : FOR (무엇을 위해)
--                EACH (각각의 행에 대해서)
--




-- 4. 형식 및 구조
/*
CREATE [OR REPLACE] TRIGGER 트리거명
    [BEFORE | AFTER]
    이벤트 1 [OR 이벤트 2 [OR 이벤트3]] ON 테이블명
    [FOR EACH ROW [WHEN TRIGGER 조건]]

[DECLARE]
    -- 선언 구문;
BEGIN
    -- 실행 구문;
END;
*/





--■■■ AFTER STATEMENT TRIGGER 상황 실습 ■■■--
-- ※ DML 작업에 대한 이벤트 기록 (DATA MANIPULATION LANGUAGE)



--○ TRIGGER(트리거) 생성
CREATE OR REPLACE TRIGGER TRG_EVENTLOG
        AFTER --> 입력되거나 삭제되거나 업데이트된 '후'에 진행하므로 AFTER.
        
        INSERT OR UPDATE OR DELETE ON TBL_TEST1 --> 어느 테이블을 기준으로 감시하는지를 작성.
        -- 부비트랩을 설치한 테이블
        -- 아무런 제약조건을 걸지 않을 경우 ON TBL_TEST1
        -- 업데이트만 처리 UPDATE ON TBL_TEST1
BEGIN 
    -- 이벤트 종류 구분(조건문을 통한 분기)
    IF (INSERTING)  -- ★ ING~잉
        THEN INSERT INTO TBL_EVENTLOG(MEMO)
        VALUES('INSERT 쿼리가 실행되었습니다.');
    ELSIF (UPDATING)-- ★ ING~잉
        THEN INSERT INTO TBL_EVENTLOG(MEMO)
        VALUES('UPDATE 쿼리가 실행되었습니다.');
    ELSIF (DELETING)-- ★ ING~잉
        THEN INSERT INTO TBL_EVENTLOG(MEMO)
        VALUES('DELETE 쿼리가 실행되었습니다.');
    END IF;
    
    --COMMIT;
    -- ※ TRIGGER 내에서는 COMMIT / ROLLBACK 구문 사용 불가~!!!    CHECK~!!!

END;
--==>> Trigger TRG_EVENTLOG이(가) 컴파일되었습니다.




-- (DML : DATA MANIPULATION LANGUAGE : INSERT, UPDATE, DELETE, ...)



--■■■ BEFORE STATEMENT TRIGGER 상황 실습 ■■■--
-- ※ DML 작업 수행 전에 작업에 대한 가능여부 확인

-- 오전 9시 이전이거나 오후 6시 이후이면 작업 수행 불가능하도록.

-- 트리거에서는 웬만하면 DECLARE로 내부 변수를 선언하지 않는다.
-- 트리거는 DECLARE로 구성하는데... 이 마저도 일반적으로 생략한다.
-- 프로시저는 사용자가 쓰고 싶을 때 쓰지만
-- 트리거는 사용자 의지로 사용할 수 있는 것이 아니므로
-- 트리거 내부에서는 변수를 선언하고 그 변수를 통한 처리를 하지 않는다.
-- 따라서, 예외를 나타내는 변수 또한 생성하지 않는다.


--○ TRIGGER(트리거) 생성 → TRG_TEST1_DML
CREATE OR REPLACE TRIGGER TRG_TEST1_DML
        BEFORE
        INSERT OR UPDATE OR DELETE ON TBL_TEST1
        
BEGIN
    

    --IF (작업시간이 오전 9시 이전이거나... 오후 6시 이후라면...)
    --    THEN 작업을 수행하지 못하도록 처리하겠다
    
    -- IF (TO_CHAR(SYSDATE, 'HH24') NOT IN (BETWEEN 9 AND 18))
    
    
    IF (TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) < 9 OR TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) > 17)
        --THEN 예외를 발생시키도록 처리하겠다.
        THEN RAISE_APPLICATION_ERROR(-20007, '작업 시간은 09:00 ~ 18:00 까지만 가능합니다.');
        
    END IF;
END;
--==>> Trigger TRG_TEST1_DML이(가) 컴파일되었습니다.


-- 자식의 테이블을 삭제하기 위해 삭제하려는 시점 NOW 이전에
-- 과거 시점으로 돌아가서 부모의 PRIMARY KEY를 삭제하려고 할 때 이 트리거를 많이 한다.



--■■■ BEFORE ROW TRIGGER 상황 실습 ■■■--
-- ※ 참조 관계가 설정된 데이터(자식) 삭제를 먼저 수행하는 모델

-- 부모 테이블의 텔레비전 데이터를 제거하려했더니 오류가 나면서 삭제가 안됐다.
-- 자식 테이블에 코드 1(텔레비전)을 참고하는 데이터가 있어서 제거가 안 됐던 것.
-- TBL_TEST3에 있는 텔레비전을 참고하고 있는 모든 데이터들을 삭제하면
-- 부모테이블의 텔레비전 항목이 지워진다.
-- 즉, 부모 테이블에서 텔레비전을 지우려고 할 때 지워지도록 처리하는것.
-- 제거시점(NOW)보다 이전(과거 : BEFORE)으로 돌아가서 자식 테이블의 데이터 제거.


-- 사용자가 부모 테이블의 데이터를 제거하려고 할 때 움직이는 것.
-- 사용자가 건드리는 테이블은 부모 테이블인 TBL_TEST2이므로 ON TBL_TEST2이다!!!!!!


-- 사용자가 삭제하려는 TBL_TEST2의 데이터를 지칭하는 쿼리문 내의 코드.
--==>> :OLD.CODE;

--     :OLD   ←    →    :NEW
-----------------|------------------->

-- 과거의 코드를 삭제한다는 의미로(자식 테이블 내에 존재하는 코드...) [:OLD.CODE]...


--『UPDATE』 : 오라클 내부적으로 들어가면 존재하지 않는다.
--            --==>> DELETE + INSERT 이다.

-- 내가 수정하려는 데이터를 삭제한 다음에 바꾸려는 데이터로 INSERT되는 것이다.
-- UPDATE : DELETE + INSERT (더 비효율적) -- 한 번 실행할 때마다 두 개의 명령 실행.
-- INSERT : INSERT (효율적 : 명령 단일.)




--○ TRIGGER(트리거) 생성 → TRG_TEST2_DELETE

-- 행 트리거. 부모가 하나 없어질 떄, 자식을 하나씩 찾아봐야 하는 트리거.
-- FOR EACH ROW 구문이 빠졌다!★
-- 확인을 하려면, 저장 공간이 필요하다. 그것이 바로 :OLD!!

CREATE OR REPLACE TRIGGER TRG_TEST2_DELETE
        BEFORE
        DELETE ON TBL_TEST2
        FOR EACH ROW
        
BEGIN
    
    DELETE 
    FROM TBL_TEST3
    WHERE CODE = :OLD.CODE;
END;
--==>> Trigger TRG_TEST2_DELETE이(가) 컴파일되었습니다.

        

--※ 『:OLD』
--    참조 전 열의 값
--   - INSERT  : 입력하기 이전 자료 즉, 입력할 자료 
--   - DELETE :  삭제하기 이전 자료 즉, 삭제할 자료



--※ UPDATE 
--   내부적으로 DELETE 그리고 INSERT 가 결합된 형태
--   UPDATE 하기 이전의 데이터는   『:OLD』
--   UPDATE 수행한 이후의 데이터는 『:NEW』



-- 입고, 상품, 출고 : 프로시저를 통해서 했지만...
-- 이를 트리거로 처리해 주면... INSERT만 하더라도 관련 재고가 늘어난다!!!
-- 출고 테이블에 한 건을 입력했을 뿐인데 재고가 줄어든다!!! 와우

-- ROW 트리거일 경우에는... 여러 개가 하나를 참조하는 경우
-- 하나의 파장이 자식에 반영되어야 하는 경우는 대부분 ROW TRIGGER이다.



--■■■ AFTER ROW TRIGGER 상황 실습 ■■■--
--※ 참조 테이블 관련 트랜잭션 처리

 


 

20231107_02_scott.sql

SELECT USER
FROM DUAL;
--==>> SCOTT



--------------------------------------------------------------------------------

SELECT *
FROM TBL_상품;

--==>>
/*
상품코드	상품명	소비자가격	재고수량
H001	    바밤바	600	        30
H002	    죠스바	500	        30
H003	    보석바	500	        20 (35 → -15 → 20)
H004    	누가바	600	        70
H005    	쌍쌍바	700	        0
H006	    수박바	500	        0
H007	    알껌바	500	        0
C001	빵빠레	1600	    20
C002	    월드콘	1500	    20
C003	메타콘	1500	    20
C004	구구콘	1600	    20
C005	슈퍼콘	1700	    0
E001	빵또아	2600        	0
E002	    투게더	2500        	0
E003	팥빙수	2500	        0
E004	셀렉션	2600	        0
E005	설레임	2700	        0
*/

SELECT *
FROM TBL_입고;
--==>>
/*
입고번호	상품코드	입고일자	입고수량	입고단가
3	    H001    	2023-11-06	50	500
4	    H002    	2023-11-06	30	400
1	    H001    	2023-11-06	30	500
2	    H002    	2023-11-06	20	400
5	    H003    	2023-11-06	35	400 -- [H003 - 보석바]
6	    H004    	2023-11-06	70	500
7	    C001	2023-11-06	26	1400
8	    C001	2023-11-06	24	1400
9	    C001	2023-11-06	20	1500
10	    C002    	2023-11-06	20	1000
11	    C003	2023-11-06	20	1000
12	    C004	2023-11-06	20	1000
*/



SELECT *
FROM TBL_출고;
--==>>
/*
출고번호	상품코드	    출고일자	    출고수량    	출고단가
2	    H001        	2023-11-06	10	        600
1	    H001	        2023-11-06	10	        600
3	    C001	    2023-11-06	20	        1500
4	    C001	    2023-11-06	20	        1500
5	    C001	    2023-11-06	10	        1500
6	    H001	        2023-11-06	10	        500
7	    H001        	2023-11-06	10	        500
8	    H001        	2023-11-06	10	        500
9	    H002        	2023-11-06	20	        500
10	    H003        	2023-11-06	15	        500 -->[보석바 15개 출고] (40개 업데이트 불가)
*/



--○ 생성한 프로시저가 제대로 작동하는지의 여부 확인 → 프로시저 호출

EXEC PRC_출고_UPDATE2(10, 40);
--==>> 에러 발생
--     (ORA-2002: 재고 부족~!!!)


EXEC PRC_출고_UPDATE2(10, 25);
--==>> PL/SQL 프로시저가 성공적으로 완료되었습니다.


--○ 프로시저 호출 이후 테이블 조회(확인)
SELECT *
FROM TBL_출고;
--==>>
/*
출고번호	상품코드    	출고일자	출고수량	출고단가
2	    H001	        2023-11-06	10	600
1	    H001	        2023-11-06	15	600
3	    C001	    2023-11-06	20	1500
4	    C001	    2023-11-06	20	1500
5	    C001	    2023-11-06	10	1500
6	    H001	        2023-11-06	10	500
7	    H001        	2023-11-06	10	500
8	    H001	        2023-11-06	10	500
9	    H003	        2023-11-06	25	500
*/


SELECT *
FROM TBL_상품;
--==>>
/*
상품코드	상품명	소비자가격	재고수량
H001	    바밤바	600	        25
H002	    죠스바	500	        30
H003	    보석바	500	        10
H004	    누가바	600	        70
H005    	쌍쌍바	700	        0
H006    	수박바	500     	0
H007    	알껌바	500     	0
C001	빵빠레	1600	    20
C002    	월드콘	1500	    20
C003	메타콘	1500	    20
C004	구구콘	1600	    20
C005	슈퍼콘	1700	    0
E001	빵또아	2600	        0
E002	    투게더	2500	        0
E003	팥빙수	2500	        0
E004	셀렉션	2600	        0
E005	설레임	2700	        0
*/

--김지민 어디가쏘~~~ 빨리와 8282

/*

현재 재고수량 = 총입고수량 - 총출고수량

재고수량 = 입고수량 - 출고수량

총 입고수량 = 입고수량 1 + 입고수량 2 + 입고수량 3

변경할 입고수량 = 입고수량 2 → 입고수량 4로 변경하고 싶다

총 입고수량 - 입고수량 2 = 입고수량 1 + 입고수량 3

위의 결과값 + 입고수량 4 를 했을 때의 결과 = 최종 입고 수량(변경후)


최종 입고 수량(변경후) - 총 출고수량 < 0 이 되면 안된다!!

기존 재고가 3이야(출고까지 반영됨)

입고번호가 1인 레코드를 바꿀거야
1 10
1  5

3 - 5  < 0

*/





SELECT *
FROM TBL_상품;

SELECT *
FROM TBL_입고;

SELECT *
FROM TBL_출고;


EXECUTE PRC_입고_UPDATE(3, 50);
EXECUTE PRC_입고_DELETE(6);
EXECUTE PRC_출고_DELETE(1);
ROLLBACK;







--------------------------------------------------------------------------------




--■■■ AFTER STATEMENT TRIGGER 상황 실습 ■■■--
-- ※ DML 작업에 대한 이벤트 기록 (DATA MANIPULATION LANGUAGE)

--○ 실습을 위한 준비 → 테이블 생성(TBL_TEST1)

CREATE TABLE TBL_TEST1
( ID    NUMBER
, NAME  VARCHAR2(30)
, TEL   VARCHAR2(60)
, CONSTRAINT TEST1_ID_PK PRIMARY KEY(ID)
);
--==>> Table TBL_TEST1이(가) 생성되었습니다.

--○ 실습을 위한 준비 → 테이블 생성(TBL_EVENTLOG)

CREATE TABLE TBL_EVENTLOG
( MEMO  VARCHAR2(200)
, ILJA  DATE DEFAULT SYSDATE
);
--==>> Table TBL_EVENTLOG이(가) 생성되었습니다.


--○ 날짜 관련 세션 설정

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
--==>> Session이(가) 변경되었습니다.


--○ 확인

SELECT *
FROM TBL_TEST1;
--==>> 조회 결과 없음

SELECT *
FROM TBL_EVENTLOG;
--==>> 조회 결과 없음


-- 졸리거나 수업이 재미없거나 집에 가고싶거나 하면...
-- 테이블 1에(TBL_TEST1) 이름 전화번호를 적으세요.



--○ 생성한 TRIGGER 작동여부 확인
--   → TBL_TEST1 테이블을 대상으로 INSERT, UPDATE, DELETE 수행


INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(1, '이윤수', '010-1111-1111');
--==>> 1 행 이(가) 삽입되었습니다.

INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(2, '강혜성', '010-2222-2222');
--==>> 1 행 이(가) 삽입되었습니다.

INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(3, '문정환', '010-3333-3333');
--==>> 1 행 이(가) 삽입되었습니다.

UPDATE TBL_TEST1
SET NAME = '박가영', TEL = '010-4444-4444'
WHERE ID = 1;

SELECT *
FROM TBL_TEST1;
-- 1 이윤수 010-1111-1111
-- 2 강혜성 010-2222-2222
-- 3 문정환 010-4444-4444


DELETE
FROM TBL_TEST1
WHERE ID IN (2, 3);
--==>> 2개 행 이(가) 삭제되었습니다.


SELECT *
FROM TBL_TEST1;
--==>> 
/*
ID	NAME	    TEL
1	박가영	010-4444-4444
*/


DELETE
FROM TBL_TEST1
WHERE ID = 1;
--==>> 1 행 이(가) 삭제되었습니다.




SELECT *
FROM TBL_TEST1;
--==>> 조회 결과 없음.


SELECT *
FROM TBL_EVENTLOG;
--==>>
/*
MEMO	                    ILJA
INSERT 쿼리가 실행되었습니다.	2023-11-07 16:14:22
INSERT 쿼리가 실행되었습니다.	2023-11-07 16:14:53
INSERT 쿼리가 실행되었습니다.	2023-11-07 16:17:24
UPDATE 쿼리가 실행되었습니다.	2023-11-07 16:18:32
DELETE 쿼리가 실행되었습니다.	2023-11-07 16:19:25
DELETE 쿼리가 실행되었습니다.	2023-11-07 16:20:20
*/

-- 쓰는 사람 입장에서는 순수하게 INSERT, DELETE, UPDATE를 하는 것일 뿐인데
-- 미리 사전에 걸어둔 트리거 때문에 이런 기능이 실행된다.
--


--------------------------------------------------------------------------------


--■■■ BEFORE STATEMENT TRIGGER 상황 실습 ■■■--
-- ※ DML 작업 수행 전에 작업에 대한 가능여부 확인

SELECT SYSDATE
FROM DUAL;
--==>> 2023-11-07 18:46:49

INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(1, '김다슬', '010-1111-1111');
--==>> 에러 발생
--     (ORA-20007: 작업 시간은 09:00 ~ 18:00 까지만 가능합니다.) ← 사용자 정의 에러



SELECT SYSDATE
FROM DUAL;
--==>> ORA-20007: 작업 시간은 09:00 ~ 18:00 까지만 가능합니다.



INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(1, '김다슬', '010-1111-1111');
--==>> 1 행 이(가) 삽입되었습니다.


SELECT *
FROM TBL_TEST1;
--==>>
/*
ID	NAME	    TEL
1	김다슬	010-1111-1111
*/


COMMIT;
--==>> 커밋 완료.


UPDATE TBL_TEST1
SET NAME = '오수경', TEL = '010-2222-2222'
WHERE ID = 1;
--==>> 1 행 이(가) 업데이트되었습니다.


INSERT INTO TBL_TEST1(ID, NAME, TEL)
VALUES(2, '김경태', '010-3333-3333');
--==>> 1 행 이(가) 삽입되었습니다.



SELECT *
FROM TBL_TEST1;
--==>>
/*
ID	NAME    	TEL
2	김경태	010-3333-3333
1	오수경	010-2222-2222
*/


COMMIT;
--==>> 커밋 완료.



SELECT SYSDATE
FROM DUAL;
--==>> 2023-11-07 06:02:05


UPDATE TBL_TEST1
SET NAME = '박범구', TEL = '010-4444-4444'
WHERE ID = 1;
--==>> 에러 발생
--     (ORA-20007: 작업 시간은 09:00 ~ 18:00 까지만 가능합니다.)


DELETE
FROM TBL_TEST1
WHERE ID = 2;
--==>> 에러 발생
--     (ORA-20007: 작업 시간은 09:00 ~ 18:00 까지만 가능합니다.)



--------------------------------------------------------------------------------

--■■■ BEFORE ROW TRIGGER 상황 실습 ■■■--
-- ※ 참조 관계가 설정된 데이터(자식) 삭제를 먼저 수행하는 모델


--○ 실습 환경 구성을 위한 테이블 생성 → TBL_TEST2


CREATE TABLE TBL_TEST2
( CODE  NUMBER
, NAME  VARCHAR2(40)
, CONSTRAINT TEST2_CODE_PK PRIMARY KEY(CODE)
);
--==>> Table TBL_TEST2이(가) 생성되었습니다.



--○ 실습 환경 구성을 위한 테이블 생성 → TBL_TEST3

CREATE TABLE TBL_TEST3
( SID   NUMBER
, CODE  NUMBER
, SU    NUMBER
, CONSTRAINT TEST3_SID_PK PRIMARY KEY (SID)
, CONSTRAINT TEST3_CODE_FK FOREIGN KEY (CODE)
             REFERENCES TBL_TEST2(CODE)
);
--==>> Table TBL_TEST3이(가) 생성되었습니다.



--○ 실습 관련 데이터 입력

-- 부모테이블에 입력.
INSERT INTO TBL_TEST2(CODE, NAME) VALUES(1, '텔레비전');
INSERT INTO TBL_TEST2(CODE, NAME) VALUES(2, '냉장고');
INSERT INTO TBL_TEST2(CODE, NAME) VALUES(3, '세탁기');
INSERT INTO TBL_TEST2(CODE, NAME) VALUES(4, '건조기');
--==>> 1 행 이(가) 삽입되었습니다. * 4



SELECT *
FROM TBL_TEST2;
--==>>
/*
CODE	NAME
1	    텔레비전
2	    냉장고
3	    세탁기
4	    건조기
*/


COMMIT;
--==>> 커밋 완료.


--○ 실습 관련 데이터 입력

INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(1, 1, 30);  -- 텔레비전
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(2, 1, 50);  -- 텔레비전
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(3, 1, 60);  -- 텔레비전

INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(4, 2, 20);  -- 냉장고
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(5, 2, 20);  -- 냉장고
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(6, 3, 40);  -- 세탁기

INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(7, 1, 30);  -- 텔레비전
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(8, 4, 30);  -- 건조기
INSERT INTO TBL_TEST3(SID, CODE, SU) VALUES(9, 3, 10);  -- 세탁기
--==>> 1 행 이(가) 삽입되었습니다. * 9


SELECT *
FROM TBL_TEST3;
--==>>
/*
==================
SID	CODE	SU
==================
1	1	    30
2	1	    50
3	1	    60
4	2	    20
5	2	    20
6	3	    40
7	1	    30
8	4	    30
9	3	    10
==================
*/

COMMIT;
--==>> 커밋 완료.


--○ 부모 테이블(TBL_TEST2)의 데이터 삭제 시도


SELECT *
FROM TBL_TEST2
WHERE CODE=1;
--==>> 1	텔레비전


DELETE
FROM TBL_TEST2
WHERE CODE=1;
--==>> 에러 발생
--     (ORA-02292: integrity constraint (SCOTT.TEST3_CODE_FK) violated - child record found)




--○ TRIGGER 생성 이후 확인


DELETE
FROM TBL_TEST2
WHERE CODE=1;
--==>> 1 행 이(가) 삭제되었습니다.

SELECT *
FROM TBL_TEST2;
--==>>
/*
CODE	NAME
2	    냉장고
3	    세탁기
4	    건조기
*/
-- 부모 테이블의 텔레비전이 날라갔다!!


SELECT *
FROM TBL_TEST3;
--==>>
/*
SID	CODE	SU
4	2	20
5	2	20
6	3	40
8	4	30
9	3	10
*/
-- 자식 테이블에서 부모 테이블의 텔레비전을 참조하는 데이터들이
-- 모두 삭제되었음을 확인!



DELETE 
FROM TBL_TEST2
WHERE CODE=2;
--==>> 1 행 이(가) 삭제되었습니다.


SELECT *
FROM TBL_TEST2;
--==>>
/*
CODE	NAME
3	세탁기
4	건조기
*/



SELECT *
FROM TBL_TEST3;
--==>>
/*
SID	CODE	SU
6	3	    40
8	4	    30
9	3	    10
*/




--------------------------------------------------------------------------------

--■■■ AFTER ROW TRIGGER 상황 실습 ■■■--
--※ 참조 테이블 관련 트랜잭션 처리



--※ 실습을 위한 준비
-- 모든 재고수량 0으로 초기화.
UPDATE TBL_상품
SET 재고수량 = 0;
--==>> 17개 행 이(가) 업데이트되었습니다.


-- 테이블을 초기 상태로 되돌리고, 로그조차 남기지 않는 것.
TRUNCATE TABLE TBL_입고;
--==>> Table TBL_입고이(가) 잘렸습니다.


TRUNCATE TABLE TBL_출고;
--==>> Table TBL_출고이(가) 잘렸습니다.


SELECT *
FROM TBL_상품;
--==>> 모든 상품 재고 0

ROLLBACK;


SELECT *
FROM TBL_입고;
--==>> 조회결과 없음.


SELECT *
FROM TBL_출고;
--==>> 조회결과 없음.

--******************************************************************************
--★ [DDL : DATA DEFINE LANGUAGE 데이터 정의어 이므로, 자동 커밋된다.]
--★ [TRUNCATE] : 테이블을 오려내는 것이다. (잘린 것을 다시 붙여넣을 수 없다.)
--             ROLLBACK 되지 않는다. AUTO-COMMIT!!!!
--******************************************************************************


SELECT *
FROM TBL_상품;
--==>>
/*
상품코드	상품명	소비자가격	재고수량
H001	    바밤바	600         	0
H002	    죠스바	500	            0
H003    	보석바	500	            0
H004	    누가바	600	            0
H005    	쌍쌍바	700         	0
H006	    수박바	500	            0
H007	    알껌바	500	            0
C001	빵빠레	1600	        0
C002	    월드콘	1500	        0
C003	메타콘	1500	        0
C004	구구콘	1600	        0
C005	슈퍼콘	1700	        0
E001	빵또아	2600            	0
E002	    투게더	2500	            0
E003	팥빙수	2500	            0
E004	셀렉션	2600	            0
E005	설레임	2700	            0
*/