테마
본사 레거시 유통의 정보기술팀이 변경 요청서를 올렸다. "상품가격(CF05)은 적용일자만 있어서 판매일의 가격을 구하는 SQL이 복잡하고 느리다. 종료일(END_DT) 칸을 추가해 선분이력으로 바꾸고, 가격 등록 화면이 새 가격을 넣을 때 앞 구간의 종료일을 닫게 하자." 근거로 든 것은 월 1회 도는 「연간 정가 매출」 배치가 6초 넘게 걸린다는 것이다.
선배는 "CF05를 쓰는 SQL 셋과 등록 화면을 다 보고, 요청서를 받을지 SQL에서 풀지 숫자와 대가로 정리해 와"라고 했다.
CF05 상품가격 (점이력): 157행 · 5블록 · CF05_PK (ITM_NO, UPRC_KN_CD, APL_DT) · 리프 블록 하나 · 판매가(UPRC_KN_CD = '10') 127행 = 상품 89개의 '20220101' + 38개 상품의 '20230615' · 나머지 30행은 발주단가CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · SLE_NO 앞 8자리가 판매일자 · 2023년 548,213줄(테이블의 절반)CF05를 쓰는 곳: [Q1] POS 결제 화면의 현재가(하루 3만 번), [Q2] 연간 정가 매출 배치(월 1회), [Q3] 영수증 재출력의 당시 정가(영수증 1장에 1 ~ 4줄, 하루 500번), [Q4] 가격 등록 화면(가격 개정 때, 1년에 몇 번)-- [Q1] POS 현재가
SELECT ITM_UPRC
FROM CF05
WHERE ITM_NO = :itm AND UPRC_KN_CD = '10'
AND APL_DT = (SELECT MAX(APL_DT) FROM CF05
WHERE ITM_NO = :itm AND UPRC_KN_CD = '10' AND APL_DT <= :today);
-- [Q2] 연간 정가 매출 (월 · 상품별)
SELECT SUBSTR(d.SLE_NO, 1, 6) AS ym, d.ITM_NO,
SUM(d.SLE_QTY * (SELECT p.ITM_UPRC
FROM CF05 p
WHERE p.ITM_NO = d.ITM_NO AND p.UPRC_KN_CD = '10'
AND p.APL_DT = (SELECT MAX(x.APL_DT) FROM CF05 x
WHERE x.ITM_NO = d.ITM_NO AND x.UPRC_KN_CD = '10'
AND x.APL_DT <= SUBSTR(d.SLE_NO, 1, 8)))) AS list_amt
FROM CF11 d
WHERE d.SLE_NO >= '20230101' AND d.SLE_NO < '20240101'
GROUP BY SUBSTR(d.SLE_NO, 1, 6), d.ITM_NO;
-- [Q3] 영수증 재출력: Q2 와 같은 서브쿼리를 영수증 한 장(1 ~ 4줄)에 쓴다
-- [Q4] 가격 등록: INSERT INTO CF05 (ITM_NO, UPRC_KN_CD, APL_DT, ITM_UPRC) VALUES (...);
-- 변경 요청서의 DDL
ALTER TABLE CF05 ADD (END_DT VARCHAR2(8) DEFAULT '99991231');
CREATE INDEX CF05_X01 ON CF05 (ITM_NO, UPRC_KN_CD, END_DT);
[Q1] Buffers 3 (INDEX RANGE SCAN (MIN/MAX) 1 + INDEX UNIQUE SCAN 1 + 테이블 1)
[Q3] 영수증 한 장(4줄) Buffers 12 + 판매상품 4
[Q2]
------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1056 |00:00:06.21 | 1653K|
| 1 | TABLE ACCESS BY INDEX ROWID | CF05 | 548K| 1 | 548K|00:00:03.10 | 1644K|
|* 2 | INDEX UNIQUE SCAN | CF05_PK | 548K| 1 | 548K|00:00:02.31 | 1096K|
| 3 | SORT AGGREGATE | | 548K| 1 | 548K|00:00:01.12 | 548K|
| 4 | FIRST ROW | | 548K| 1 | 548K|00:00:00.93 | 548K|
|* 5 | INDEX RANGE SCAN (MIN/MAX) | CF05_PK | 548K| 1 | 548K|00:00:00.81 | 548K|
| 6 | HASH GROUP BY | | 1 | 1056 | 1056 |00:00:06.21 | 1653K|
|* 7 | TABLE ACCESS FULL | CF11 | 1 | 548K| 548K|00:00:00.09 | 8204 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("P"."ITM_NO"=:B1 AND "P"."UPRC_KN_CD"='10' AND "P"."APL_DT"=(SELECT MAX("X"."APL_DT") FROM "CF05" "X" ...))
5 - access("X"."ITM_NO"=:B1 AND "X"."UPRC_KN_CD"='10' AND "X"."APL_DT"<=SUBSTR(:B2,1,8))
7 - filter(("D"."SLE_NO">='20230101' AND "D"."SLE_NO"<'20240101'))
Starts로 설명하시오. 스칼라 서브쿼리 캐시가 왜 도움이 되지 않았는지, END_DT가 생기면 이 일량이 줄어드는지도 쓰시오.