Skip to content
튜닝 연습조인 튜닝
JOIN-049

월 정가 대조 리포트가 157행짜리 가격표를 4만 6천 번 찾아간다

기초SELECT유통199 / 700

상황 ​

영업관리팀의 「월 정가 대조」는 상품마다 그달 판매 수량과 실제 판매 금액, 판매일 당시 정가로 계산한 금액을 나란히 내서 할인이 얼마나 나갔는지 본다. 월초에는 달을 바꿔 가며 하루 수십 번 돌리고, 감사 기간에는 2년 치를 한 달씩 몰아서 돌린다. 2023년 6월 15일에 판매가가 바뀐 상품이 있어서, 정가는 판매일마다 그날 적용되던 값을 써야 한다. 개발자는 "가격표가 5블록이라 몇 번을 읽든 상관없다"라고 한다. 아래는 2023년 6월로 돌린 실행계획이다.

테이블과 인덱스 ​

  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · SLE_NO는 'YYYYMMDD-NNNN' 모양이고 앞 8자리가 판매일자다 · 2023년 6월 45,935줄(CF11_PK 리프 약 181블록, 테이블 약 713블록에 모여 있다) · 그달 팔린 상품 88개
  • CF05 상품가격 (점이력): 157행 · 5블록 · CF05_PK (ITM_NO, UPRC_KN_CD, APL_DT) · 리프 블록 하나 · APL_DT VARCHAR2(8)
  • 이 문제의 CF05: 판매가(UPRC_KN_CD = '10') 127행 (상품 89개의 '20220101' 89행 + 38개 상품의 '20230615' 38행), 나머지 30행은 발주단가('11') · 통계 최신
  • 바인드 :v_from = '20230601' · :v_to = '20230701'

원본 SQL ​

sql
SELECT d.ITM_NO,
       SUM(d.SLE_QTY)              AS sle_qty,
       SUM(d.SLE_QTY * d.SLE_UPRC) AS sle_amt,
       SUM(d.SLE_QTY * p.ITM_UPRC) AS list_amt
  FROM CF11 d, CF05 p
 WHERE d.SLE_NO >= :v_from
   AND d.SLE_NO <  :v_to
   AND 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))
 GROUP BY d.ITM_NO

실행계획 학습용 예시 · 판매상품 행 수는 실데이터 ​

xplan
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |         |      1 |        |     88 |00:00:00.31 |  138699 |
|   1 |  HASH GROUP BY                         |         |      1 |     88 |     88 |00:00:00.31 |  138699 |
|   2 |   NESTED LOOPS                         |         |      1 |  45980 |  45935 |00:00:00.27 |  138699 |
|   3 |    NESTED LOOPS                        |         |      1 |  45980 |  45935 |00:00:00.18 |   92764 |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| CF11    |      1 |  45980 |  45935 |00:00:00.02 |     894 |
|*  5 |      INDEX RANGE SCAN                  | CF11_PK |      1 |  45980 |  45935 |00:00:00.01 |     181 |
|*  6 |     INDEX UNIQUE SCAN                  | CF05_PK |  45935 |      1 |  45935 |00:00:00.12 |   91870 |
|   7 |      SORT AGGREGATE                    |         |  45935 |      1 |  45935 |00:00:00.05 |   45935 |
|   8 |       FIRST ROW                        |         |  45935 |      1 |  45935 |00:00:00.04 |   45935 |
|*  9 |        INDEX RANGE SCAN (MIN/MAX)      | CF05_PK |  45935 |      1 |  45935 |00:00:00.03 |   45935 |
|  10 |    TABLE ACCESS BY INDEX ROWID         | CF05    |  45935 |      1 |  45935 |00:00:00.06 |   45935 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("D"."SLE_NO">=:V_FROM AND "D"."SLE_NO"<:V_TO)
   6 - access("P"."ITM_NO"="D"."ITM_NO" AND "P"."UPRC_KN_CD"='10' AND "P"."APL_DT"=(SELECT MAX("X"."APL_DT") FROM "CF05" "X" ...))
   9 - access("X"."ITM_NO"=:B1 AND "X"."UPRC_KN_CD"='10' AND "X"."APL_DT"<=SUBSTR(:B2,1,8))

문제 ​

  1. 5블록짜리 CF05에서 13만 7천 블록 넘게 읽은 까닭을 6번 ~ 10번 줄의 Starts로 설명하시오.
  2. 판매일 당시 정가라는 결과는 그대로 두고, CF05를 한 번만 읽어 판매상품과 조인하도록 SQL을 고치시오. 경계(가격이 바뀐 날)를 어떻게 처리했는지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자