Oracle
[211006] PROCEDURE 연습문제
감자탈출기
2021. 10. 8. 02:12
PROCEDURE
PL/SQL문을 저장하는 객체
필요할 때마다 복잡한 구문을 다시 입력할 필요 없이 간단하게 호출해서 실행 결과를 얻을 수 있음
/* 1. 재고관리를 위한 테이블 생성 테이블 명 : PRODUCTS 컬럼명 데이터타입 제약조건 비고 PID VARCHAR2(6) Primary Key 문자와 숫자의 조합으로 만들어진 코드를 사용한다. PNAME VARCHAR2(30) NOT NULL 제품 이름 AMOUNT NUMBER NOT NULL 제품의 단가 CNT NUMBER NOT NULL 제품의 수량 테이블 명 : PROD_INOUT 컬럼명 데이터타입 제약조건 비고 ID NUMBER Primary Key 제품 입고 출고내역을 식별하기 위한 번호, 자동증가 기능을 활용한다. IN_OUT CHAR(1) CHECK I:입고, O:출고 임을 구분하는 컬럼 PID VARCHAR2(6) Foreign Key PRODUCTS 테이블을 참조하는 외래키로 입출고된 제품을 구분한다. AMOUNT NUMBER NOT NULL 입출고 시점의 제품 단가 CNT NUMBER NOT NULL 입출고된 제품의 수량, DEFAULT(1) PRICE NUMBER NOT NULL 입출고 시점의 제품 전체 금액(단가 x 수량) 입고 제품에는 10% 할인된 금액으로 기록되어야 한다. 출고 제품에는 10% 부가세가 포함된 금액으로 기록되어야 한다. 소수점은 절삭한다. INOUT_DATE DATE 입출고 날짜, DEFAULT(SYSDATE) */ SELECT * FROM PRODUCTS; SELECT * FROM PROD_INOUT; DROP TABLE PROD_INOUT; DROP TABLE PRODUCTS CASCADE CONSTRAINTS; ; CREATE TABLE PRODUCTS ( PID VARCHAR2(6) PRIMARY KEY , PNAME VARCHAR2(30) NOT NULL , AMOUNT NUMBER NOT NULL , CNT NUMBER NOT NULL ); COMMENT ON COLUMN PRODUCTS.PID IS '문자와 숫자의 조합으로 만들어진 코드를 사용한다.'; COMMENT ON COLUMN PRODUCTS.PNAME IS '제품 이름'; COMMENT ON COLUMN PRODUCTS.AMOUNT IS '제품의 단가'; COMMENT ON COLUMN PRODUCTS.AMOUNT IS '제품의 수량'; CREATE TABLE PROD_INOUT ( ID NUMBER PRIMARY KEY , IN_OUT CHAR(1) CHECK(IN_OUT IN('O','I')) , PID VARCHAR2(6) REFERENCES PRODUCTS(PID) , AMOUNT NUMBER NOT NULL , CNT NUMBER NOT NULL --디폴트 (아래에서 따로 넣어 주었지만 컬럼레벨도 가능하다.) , PRICE NUMBER NOT NULL , INOUT_DATE DATE --디폴트 ); COMMENT ON COLUMN PROD_INOUT.ID IS '제품 입고 출고내역을 식별하기 위한 번호, 자동증가 기능을 활용한다.'; COMMENT ON COLUMN PROD_INOUT.IN_OUT IS 'I:입고, O:출고 임을 구분하는 컬럼'; COMMENT ON COLUMN PROD_INOUT.PID IS 'PRODUCTS 테이블을 참조하는 외래키로 입출고된 제품을 구분한다.'; COMMENT ON COLUMN PROD_INOUT.AMOUNT IS '입출고 시점의 제품 단가'; COMMENT ON COLUMN PROD_INOUT.CNT IS '입출고된 제품의 수량'; COMMENT ON COLUMN PROD_INOUT.PRICE IS '입출고 시점의 제품 전체 금액(단가 x 수량) 입고 제품에는 10% 할인된 금액으로 기록되어야 한다. 출고 제품에는 10% 부가세가 포함된 금액으로 기록되어야한다. 소수점은 절삭한다.'; COMMENT ON COLUMN PROD_INOUT.INOUT_DATE IS '입출고 날짜'; ALTER TABLE PROD_INOUT MODIFY CNT DEFAULT(1); ALTER TABLE PROD_INOUT MODIFY INOUT_DATE DEFAULT(SYSDATE); /* 2. 1번에서 만든 테이블의 기본 데이터를 추가한다. - PRODUCTS 테이블에는 다음의 데이터를 추가한다. PID:A00001 PNAME:투명박스 AMOUNT:15,000 CNT:10 PID:A00002 PNAME:텀블러 AMOUNT:10,000 CNT:15 PID:A00003 PNAME:마스크(10개입) AMOUNT:5,000 CNT:20 PID:A00004 PNAME:투명테이프 AMOUNT:1,000 CNT:30 PID:A00005 PNAME:종이컵(355ml) AMOUNT:2,000 CNT:40 - PROD_INOUT과 PRODUCTS 테이블에는 다음 설명에 맞게 데이터가 추가 및 변경될 수 있게 한다. 투명박스 2개를 2021년 01월 02일에 출고하였다. 텀블러 1개를 2021년 01월 03일에 출고하였다. 마스크 10개를 2021년 01월 05일에 출고하였다. 종이컵 5개를 2021년 01월 12일에 출고하였다. 마스크 20개를 2021년 01월 15일에 입고하였다. 투명테이프 2개를 2021년 01월 17일에 출고하였다. 종이컵 10개를 2021년 01월 21일에 출고하였다. 투명박스 7개를 2021년 01월 25일에 입고하였다. */ -- PRODUCTS 테이블에는 다음의 데이터를 추가한다. INSERT INTO PRODUCTS VALUES('A00001', '투명박스', 15000, 10); INSERT INTO PRODUCTS VALUES('A00002', '텀블러', 10000, 15); INSERT INTO PRODUCTS VALUES('A00003', '마스크(10개입)', 5000, 20); INSERT INTO PRODUCTS VALUES('A00004', '투명테이프', 1000, 30); INSERT INTO PRODUCTS VALUES('A00005', '종이컵(355ml)', 2000, 40); SELECT*FROM PRODUCTS; --참조되는자식테이블 (PID, PNAME, AMOUNT, CNT) SELECT*FROM PROD_INOUT; --(ID, IN_OUT, PID, AMOUNT, CNT, PRICE, INOUT_DATE) DESC PROD_INOUT; DROP SEQUENCE PROD_INOUT_SEQ; CREATE SEQUENCE PROD_INOUT_SEQ; INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00001', 15000, 2, 0, TO_DATE('20210102', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00002', 10000, 1, 0, TO_DATE('20210103', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00003', 5000, 10, 0, TO_DATE('20210105', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00005', 2000, 5, 0, TO_DATE('20210112', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'I', 'A00003', 5000, 20, 0, TO_DATE('20210115', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00004', 1000, 2, 0, TO_DATE('20210117', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00005', 2000, 10, 0, TO_DATE('20210121', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES (PROD_INOUT_SEQ.NEXTVAL, 'I', 'A00001', 15000, 7, 0, TO_DATE('20210125', 'yyyymmdd')); -- PRICE 계산식에 부가세 작업까지 한번에 (사용자 정의 함수 sample5 line 51) UPDATE PROD_INOUT SET PRICE = AMOUNT * CNT * 1.1 WHERE IN_OUT = 'O'; UPDATE PROD_INOUT SET PRICE = AMOUNT * CNT * 0.9 WHERE IN_OUT = 'I'; /* 3. 1월 1달간 출고 내역을 조회하시오. 제품코드 제품명 단가 수량 금액 A00001 투명박스 15,000 2 33,000 A00002 텀블러 10,000 1 11,000 A00003 마스크 5,000 10 55,000 .... */ SELECT*FROM PROD_INOUT; --(ID, IN_OUT, PID, AMOUNT, CNT, PRICE, INOUT_DATE) SELECT*FROM PRODUCTS; SELECT A.PID AS 제품코드 , B.PNAME AS 제품명 , TO_CHAR(MAX(A.AMOUNT), '999,999,999') AS 단가 , TO_CHAR(SUM(A.CNT), '999,999,999') AS 수량 , TO_CHAR(SUM(A.PRICE), '999,999,999') AS 금액 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID WHERE A.IN_OUT = 'O' AND INOUT_DATE BETWEEN TO_DATE('20210101', 'yyyymmdd') AND TO_DATE('20210131', 'yyyymmdd') GROUP BY A.PID, B.PNAME; /* 4. 1월 1달간의 입/출고 내역을 다음과 같이 나오도록 조회하시오. 제품코드 제품명 입고량 출고량 A00001 투명박스 7 2 A00002 텀블러 0 1 A00003 마스크 20 10 ... 총합 xx xx */ SELECT*FROM PROD_INOUT; --(ID, IN_OUT, PID, AMOUNT, CNT, PRICE, INOUT_DATE) SELECT*FROM PRODUCTS; -- UNION 안 쓰는 방법 SELECT DECODE(A.PID, NULL, '총합', A.PID) AS 제품코드 --와.. , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID WHERE INOUT_DATE BETWEEN TO_DATE('20210101', 'yyyymmdd') AND TO_DATE('20210131', 'yyyymmdd') GROUP BY ROLLUP((A.PID, B.PNAME)); -- 묶음을 하나로 보기 때문에 묶음별 값이 하나랑, 전체 계 한개 총 2개 기준으로 GROUP BY 됨 -- 전체 계는 집계값 제외한 나머지 열 NULL로 나오기 때문에 DECODE 써서 '총합' 넣을 수 있는 것 -- DECODE(A.IN_OUT, 'I', A.CNT, 0) -- A.IN_OUT이 'I'면 A.CNT 값을 반환, 아니면 0 반환. -- A.IN_OUT 값을 A.CNT로 변경하는 게 아닌 단순히 해당 값으로 SELECT 컬럼을 반환하는 것) -- UNION SELECT A.PID AS 제품코드 , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID WHERE INOUT_DATE BETWEEN TO_DATE('20210101', 'yyyymmdd') AND TO_DATE('20210131', 'yyyymmdd') GROUP BY A.PID, B.PNAME UNION -- 행결합 SELECT '총합' AS 제품코드 , NULL AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID WHERE INOUT_DATE BETWEEN TO_DATE('20210101', 'yyyymmdd') AND TO_DATE('20210131', 'yyyymmdd'); -- GROUP BY 하지 않고 다 더해야 총합. 행 한줄 따로 생성한 것 SELECT * FROM PROD_INOUT; /* 5. 3번, 4번 조회 쿼리 활용한 VIEW를 생성하여 년/월 조회 조건만으로도 동일한 결과가 조회될 수 있도록 하시오. */ SELECT*FROM PROD_INOUT; --(ID, IN_OUT, PID, AMOUNT, CNT, PRICE, INOUT_DATE) SELECT*FROM PRODUCTS; -- 3번(1월 1달간 출고 내역) 뷰 CREATE OR REPLACE VIEW V_출고내역 AS SELECT EXTRACT(YEAR FROM A.INOUT_DATE) AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) AS 월 , A.PID AS 제품코드 , B.PNAME AS 제품명 , TO_CHAR(MAX(A.AMOUNT), '999,999,999') AS 단가 , TO_CHAR(SUM(A.CNT), '999,999,999') AS 수량 , TO_CHAR(SUM(A.PRICE), '999,999,999') AS 금액 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID WHERE A.IN_OUT = 'O' -- WHERE의 월 조건 필요 없음 삭제. 뷰 조회시 조건으로 넣을 것 GROUP BY EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE), A.PID, B.PNAME WITH READ ONLY; SELECT 제품코드, 제품명, 단가, 수량, 금액 FROM V_출고내역 WHERE 년도 = 2021 AND 월 = 2; -- 테스트를 위한 2월달 추가 데이터 입력 INSERT INTO PROD_INOUT VALUES(PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00002', 10000, 3 , 10000 * 3 * 1.1, TO_DATE('20210205', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES(PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00002', 10000, 2 , 10000 * 2 * 1.1, TO_DATE('20210209', 'yyyymmdd')); INSERT INTO PROD_INOUT VALUES(PROD_INOUT_SEQ.NEXTVAL, 'O', 'A00003', 5000, 5 , 5000 * 5 * 1.1, TO_DATE('20210212', 'yyyymmdd')); -- 4번(1월 1달간의 입/출고 내역) 뷰 SELECT*FROM PROD_INOUT; --(ID, IN_OUT, PID, AMOUNT, CNT, PRICE, INOUT_DATE) SELECT*FROM PRODUCTS; CREATE OR REPLACE VIEW V_입출고내역(년도, 월, 제품코드, 제품명, 입고량, 출고량) -- 별칭 일괄 부여 AS-- 서브쿼리의 SELECT절에 함수 사용했을 경우 반드시 별칭 지정하여야 한다. SELECT EXTRACT(YEAR FROM A.INOUT_DATE) --AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) --AS 월 , DECODE(A.PID, NULL, '총합', A.PID) --AS 제품코드 , B.PNAME --AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) --AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) --AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID GROUP BY ROLLUP( (EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE), A.PID, B.PNAME) ); WITH READ ONLY; -- 롤업 방식때문에 년월별 분류되는 총합 사라짐 (잘못된 구문) SELECT 제품코드, 제품명, 입고량, 출고량 FROM V_입출고내역 WHERE 년도 = 2021 AND 월 = 1; -- 최종 수정 CREATE OR REPLACE VIEW V_입출고내역 AS SELECT EXTRACT(YEAR FROM A.INOUT_DATE) AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) AS 월 , DECODE(A.PID, NULL, '총합', A.PID) AS 제품코드 , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID GROUP BY ROLLUP( (EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE)) , (A.PID, B.PNAME) ) WITH READ ONLY; SELECT 제품코드, 제품명, 입고량, 출고량 FROM V_입출고내역 WHERE 년도 = 2021 AND 월 = 1; /* ROLLUP : 인자로 전달받은 그룹중 가장먼저 전달된 그룹을 기준으로 추가적 집계 결과 반환 년월 기준으로 추가 집계결과 반환. (년+월)+(디코드+네임), (년+월), (전체 집계)반환 */ -- DECODE(A.PID ~ 구문 그대로 그룹바이에 사용하면 안 되는 이유 /* 실행 순서 1. FROM 절 2. WHERE 절 3. GROUP BY 절 4. HAVING 절 5. SELECT 절 6. ORDER BY 절 */ -- : GROUP BY 실행하면 컬럼의 이름이 DECODE(A.PID ~로 바뀐상태라서 SELECT 실행시 DECODE 할 A.PID 컬럼을 찾지 못하기 때문. -- 이해를 위한 구문 CREATE OR REPLACE VIEW V_입출고내역 AS SELECT EXTRACT(YEAR FROM A.INOUT_DATE) AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) AS 월 , DECODE(DECODE(A.PID, NULL, '총합', A.PID), NULL, '총합', DECODE(A.PID, NULL, '총합', A.PID)) AS 제품코드 -- ㄴ 정상 작동하길 원한다면 이러한 구조가 되어야 함 , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID GROUP BY ROLLUP( (EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE)) , (DECODE(A.PID, NULL, '총합', A.PID), B.PNAME) ) -- ㄴ 그대로 붙여넣기 했을 때 WITH READ ONLY; SELECT 제품코드, 제품명, 입고량, 출고량 FROM V_입출고내역 WHERE 년도 = 2021 AND 월 = 1; -- 뷰 안쪽 점검하기 SELECT EXTRACT(YEAR FROM A.INOUT_DATE) AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) AS 월 , DECODE(A.PID, NULL, '총합', A.PID) AS 제품코드 , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID GROUP BY ROLLUP((EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE), A.PID, B.PNAME)); -- ㄴ롤업때문에 년 월별 총합은 사라짐 -- 수정 SELECT EXTRACT(YEAR FROM A.INOUT_DATE) AS 년도 , EXTRACT(MONTH FROM A.INOUT_DATE) AS 월 , DECODE(A.PID, NULL, '총합', A.PID) AS 제품코드 , B.PNAME AS 제품명 , SUM(DECODE(A.IN_OUT, 'I', A.CNT, 0)) AS 입고량 , SUM(DECODE(A.IN_OUT, 'O', A.CNT, 0)) AS 출고량 FROM PROD_INOUT A JOIN PRODUCTS B ON A.PID = B.PID GROUP BY ROLLUP( (EXTRACT(YEAR FROM A.INOUT_DATE), EXTRACT(MONTH FROM A.INOUT_DATE)) , (A.PID, B.PNAME) ); /* 6. 제품이 출고 또는 입고가 될 때 PRODUCTS에 기록된 수량도 같이 반영될 수 있도록 프로시저를 만들어 동작시키시오. 단, 출고작업 진행시 출고에 필요한 상품의 수량이 부족한 경우에는 출고작업이 이루어지지 않도록 해야 한다. */ SET SERVEROUTPUT ON; DROP SEQUENCE PROD_INOUT_SEQ; -- 입고 예시 CREATE OR REPLACE PROCEDURE PROC_제품입고(i_name IN VARCHAR, i_cnt IN NUMBER, i_date IN DATE) IS p_id PRODUCTS.PID%TYPE; amount PRODUCTS.AMOUNT%TYPE; BEGIN SELECT PID, AMOUNT INTO p_id, amount FROM PRODUCTS WHERE PNAME = i_name; INSERT INTO PROD_INOUT VALUES(PROD_INOUT_SEQ.NEXTVAL, 'I', p_id , amount, i_cnt, (amount * i_cnt * 0.9), i_date); UPDATE PRODUCTS SET CNT = CNT + i_cnt WHERE PID = p_id; COMMIT; --ROLLBACK; -- 구문 검증용. 작동때마다 테이블 데이터 매번 바뀌면 헷갈리니까. -- 참고로 시퀀스는 롤백 안 됨 알아서 DROP 초기화할 것) END; / EXEC PROC_제품입고('텀블러', 5, TO_DATE('20210219', 'yyyymmdd')); -- 출고 예시 CREATE OR REPLACE PROCEDURE PROC_제품출고(i_name IN VARCHAR, i_cnt IN NUMBER, i_date IN DATE) IS p_id PRODUCTS.PID%TYPE; amount PRODUCTS.AMOUNT%TYPE; cnt PRODUCTS.AMOUNT%TYPE; not_enough_count EXCEPTION; BEGIN SELECT PID, amount INTO p_id, amount FROM PRODUCTS WHERE PNAME = i_name; INSERT INTO PROD_INOUT VALUES(PROD_INOUT_SEQ.NEXTVAL, 'O', p_id , amount, i_cnt, amount * i_cnt * 1.1, i_date); UPDATE PRODUCTS SET CNT = CNT - i_cnt WHERE PID = p_id; SELECT CNT INTO cnt FROM PRODUCTS WHERE PNAME = i_name; IF cnt < 0 THEN -- 복습할 때 보니까 cnt < i_cnt 같음 RAISE not_enough_count -- RAISE 예외 발생 시키기 -- ROLLBACK; --예외처리로 아래에서 롤백하고 삭제 할 부분 ELSE COMMIT; END IF; EXCEPTION WHEN not_enough_count THEN DBMS.OUTPUT.PUT_LINE('제품 출고를 위한 수량이 부족합니다'); ROLLBACK; END; / EXEC PROC_제품출고('텀블러', 11, TO_DATE('20210220', 'yyyymmdd')); SELECT * FROM PROD_INOUT; SELECT * FROM PRODUCTS; |