테마
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행 · 통계는 지우기 전에 모은 것CF02 카테고리: 18행 · 4블록 · CF01.CTG_NO가 가리키는 중분류(머그 310 · 텀블러 312 …)CF11에서 CF01로 가는 FK는 없다(레거시)PKG_ITM: 상품 공통 패키지 · 매장 POS와 ERP 화면 열 곳 남짓이 같은 함수를 쓴다-- 공통 패키지 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;
[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