Skip to content
튜닝 연습PL/SQL과 사용자 정의 함수
PLSQL-027

연간 상품별 판매 맨 아래에 상품명도 분류도 빈 네 줄이 있는데, 리포트는 에러 없이 끝났다

실전SELECT유통677 / 700

상황 ​

1월 둘째 주, 본사 회계팀이 결산용 「2023년 상품별 판매」를 내려받아 중분류로 묶어 보니 머그와 텀블러가 매장 장부보다 합계 310,472,000원 적었다. 리포트 맨 아래에는 상품명도 중분류도 비어 있는 네 줄이 있었고, 네 줄의 금액을 더하면 정확히 그 차이였다. 리포트 도구의 로그는 정상 종료였다. 알고 보니 1월 2일 상품 담당이 라이선스 계약이 끝난 굿즈 4종을 POS에서 내리면서, 판매 중지(SLE_YN = 'N')로 바꾸는 대신 상품 마스터에서 행을 지웠다. 개발자는 함수를 빼고 조인하는 B 판을 냈다. "빈칸이 깨끗이 없어져요." 오과장은 결산 리포트가 무엇을 보여 줘야 하는지 정하고, 다시는 이런 일이 조용히 지나가지 않게 해 달라고 했다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · 2023년 249,971건 · SLE_DT VARCHAR2(8)
  • CF11 판매상품: 1,096,708행 · 8,204블록 · 2023년분 548,401줄 · 그해 팔린 상품 88종 · 2023년 판매액 4,506,252,000원(매출 원장과 같다)
  • CF01 상품: 원래 109행 · 지운 4행(1073 아이언맨머그, 1074 캡틴아메리카머그, 1086 아이언맨텀블러, 1087 캡틴아메리카텀블러) 때문에 지금 105행 · 통계는 지우기 전에 모은 것
  • 지운 4종의 2023년 판매: 12,464줄 · 수량 31,033 · 금액 310,472,000원
  • CF02 카테고리: 18행 · 4블록 · CF01.CTG_NO가 가리키는 중분류(머그 310 · 텀블러 312 …)
  • CF11에서 CF01로 가는 FK는 없다(레거시)
  • PKG_ITM: 상품 공통 패키지 · 매장 POS와 ERP 화면 열 곳 남짓이 같은 함수를 쓴다

원본 SQL ​

sql
-- 공통 패키지 PKG_ITM 안
FUNCTION ITM_NM(p_itm_no NUMBER) RETURN VARCHAR2 IS
  v_nm CF01.ITM_NM%TYPE;
BEGIN
  SELECT ITM_NM INTO v_nm FROM CF01 WHERE ITM_NO = p_itm_no;
  RETURN v_nm;
END;

FUNCTION CTG_NM(p_itm_no NUMBER) RETURN VARCHAR2 IS
  v_nm CF02.CTG_NM%TYPE;
BEGIN
  SELECT c.CTG_NM INTO v_nm
    FROM CF01 i, CF02 c
   WHERE i.ITM_NO = p_itm_no
     AND c.CTG_NO = i.CTG_NO;
  RETURN v_nm;
END;

-- [A] 회계팀 「연간 상품별 판매」 (:yyyy = '2023')
SELECT PKG_ITM.CTG_NM(g.ITM_NO) AS ctg_nm,
       PKG_ITM.ITM_NM(g.ITM_NO) AS itm_nm,
       g.qty, g.amt
  FROM (SELECT d.ITM_NO, SUM(d.SLE_QTY) AS qty, SUM(d.SLE_QTY * d.SLE_UPRC) AS amt
          FROM CF10 h, CF11 d
         WHERE h.SLE_DT BETWEEN :yyyy || '0101' AND :yyyy || '1231'
           AND d.SLE_NO = h.SLE_NO
         GROUP BY d.ITM_NO) g
 ORDER BY 1, 2;

-- [B] 개발자 안: 함수를 빼고 상품, 카테고리와 조인
SELECT c.CTG_NM AS ctg_nm, i.ITM_NM AS itm_nm, g.qty, g.amt
  FROM (SELECT d.ITM_NO, SUM(d.SLE_QTY) AS qty, SUM(d.SLE_QTY * d.SLE_UPRC) AS amt
          FROM CF10 h, CF11 d
         WHERE h.SLE_DT BETWEEN :yyyy || '0101' AND :yyyy || '1231'
           AND d.SLE_NO = h.SLE_NO
         GROUP BY d.ITM_NO) g,
       CF01 i, CF02 c
 WHERE i.ITM_NO = g.ITM_NO
   AND c.CTG_NO = i.CTG_NO
 ORDER BY 1, 2;

실행계획 학습용 예시 · 건수와 금액은 원본 데이터로 셈 ​

xplan
[A: DISPLAY_CURSOR (ALLSTATS LAST)]
-----------------------------------------------------------------------------------------------------
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |      1 |        |     88 |00:00:00.45 |   12787 |          |
|   1 |  SORT ORDER BY        |      |      1 |     88 |     88 |00:00:00.45 |   12787 |13312  (0)|
|   2 |   VIEW                |      |      1 |     88 |     88 |00:00:00.44 |   12787 |          |
|   3 |    HASH GROUP BY      |      |      1 |     88 |     88 |00:00:00.44 |   12787 | 1150K (0)|
|*  4 |     HASH JOIN         |      |      1 |    548K|    548K|00:00:00.36 |   12787 |   14M (0)|
|*  5 |      TABLE ACCESS FULL| CF10 |      1 |    250K|    249K|00:00:00.04 |    4583 |          |
|   6 |      TABLE ACCESS FULL| CF11 |      1 |   1096K|   1096K|00:00:00.09 |    8204 |          |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("D"."SLE_NO"="H"."SLE_NO")
   5 - filter(("H"."SLE_DT">=:YYYY||'0101' AND "H"."SLE_DT"<=:YYYY||'1231'))

[V$SQL: A를 한 번 돌린 뒤 함수 안 문장]
  SQL_TEXT                                                         EXECUTIONS  ROWS_PROCESSED  BUFFER_GETS
  SELECT ITM_NM FROM CF01 WHERE ITM_NO = :B1                               88              84          172
  SELECT C.CTG_NM FROM CF01 I, CF02 C WHERE I.ITM_NO = :B1 AND …           88              84          340

[A의 결과 맨 아래]
  중분류     상품명                 수량            금액
  텀블러     화이트텀블러          3,762      37,620,000
                                   7,772      62,176,000
                                   7,709      61,672,000
                                   7,914      94,968,000
                                   7,638      91,656,000
  88행 · 수량 1,371,547 · 금액 4,506,252,000

[B의 결과]  84행 · 빈칸 없음 · 수량 1,340,514 · 금액 4,195,780,000

[개발자의 시험: 빈 줄 하나의 상품 번호 1073]
  SQL> SELECT PKG_ITM.ITM_NM(1073) AS nm FROM DUAL;
  NM
  ----------
                              ← 빈칸 (오류 없음)
  SQL> EXEC DBMS_OUTPUT.PUT_LINE(PKG_ITM.ITM_NM(1073));
  ORA-01403: no data found
  ORA-06512: at "PUP.PKG_ITM", line 5

문제 ​

  1. 빈 네 줄이 생겼는데도 리포트가 에러 없이 끝난 이유를 위 자료로 설명하시오. 같은 함수를 PL/SQL에서 부르면 ORA-01403이 나는데 SQL에서는 빈칸이 되는 까닭도 쓰시오.
  2. B 판과 아우터 조인 판이 지금 리포트와 무엇이 다른지 행 수와 합계로 견주고, 결산 리포트가 무엇을 보여 줘야 하는지 정해 SQL을 다시 작성하시오.
  3. 같은 일이 다시 조용히 지나가지 않도록 데이터와 함수 쪽에서 할 일을 정하고, 그 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자