Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-051

「품절 의심 구간」이 6초 걸리고, 12월 초부터 안 팔린 상품은 목록에 없다

중급SELECT유통356 / 700

상황 ​

본사 상품팀의 「품절 의심 구간」은 2023년에 상품마다 판매가 7일 넘게 끊긴 구간을 찾는다. 판매가 끊긴 구간은 매장 재고가 바닥났다는 신호여서, 발주 담당이 거래처와 통화할 때 근거로 쓴다. 한 번 여는 데 6초가 걸린다. 발주 담당은 이렇게 말한다. "상품 1083은 12월 4일 뒤로 한 번도 안 팔렸는데 목록에 없어요. 지금 품절인 게 제일 급한데요."

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · SLE_DT VARCHAR2(8). 2023년 판매는 250,025건이다.
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO). 2023년 판매상품은 550,055줄이고 팔린 상품은 88개다.
  • 상품과 판매일의 쌍(그 상품이 팔린 날)은 2023년에 24,156개다. 많이 팔리는 상품은 거의 매일, 드문 상품은 한 해에 수십 일 팔린다.
  • 이 화면에 쓸 인덱스는 PK뿐이다. 통계는 최신이다.

원본 SQL ​

sql
SELECT x.ITM_NO, x.SLE_DT AS from_dt, MIN(y.SLE_DT) AS to_dt,
       TO_DATE(MIN(y.SLE_DT), 'YYYYMMDD') - TO_DATE(x.SLE_DT, 'YYYYMMDD') - 1 AS no_sale_days
  FROM (SELECT DISTINCT b.ITM_NO, a.SLE_DT
          FROM CF10 a, CF11 b
         WHERE a.SLE_DT BETWEEN '20230101' AND '20231231'
           AND b.SLE_NO = a.SLE_NO) x,
       (SELECT DISTINCT b.ITM_NO, a.SLE_DT
          FROM CF10 a, CF11 b
         WHERE a.SLE_DT BETWEEN '20230101' AND '20231231'
           AND b.SLE_NO = a.SLE_NO) y
 WHERE y.ITM_NO = x.ITM_NO
   AND y.SLE_DT > x.SLE_DT
 GROUP BY x.ITM_NO, x.SLE_DT
HAVING TO_DATE(MIN(y.SLE_DT), 'YYYYMMDD') - TO_DATE(x.SLE_DT, 'YYYYMMDD') - 1 >= 7
 ORDER BY x.ITM_NO, x.SLE_DT

실행계획 학습용 예시 ​

xplan
-----------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |      1 |        |     37 |00:00:06.12 |   25574 |          |
|*  1 |  FILTER                  |      |      1 |        |     37 |00:00:06.12 |   25574 |          |
|   2 |   SORT GROUP BY          |      |      1 |  24156 |  24068 |00:00:06.11 |   25574 | 1517K (0)|
|*  3 |    HASH JOIN             |      |      1 |   3313K|   3296K|00:00:04.87 |   25574 | 1430K (0)|
|   4 |     VIEW                 |      |      1 |  24156 |  24156 |00:00:00.79 |   12787 |          |
|   5 |      HASH UNIQUE         |      |      1 |  24156 |  24156 |00:00:00.79 |   12787 | 2271K (0)|
|*  6 |       HASH JOIN          |      |      1 |    550K|    550K|00:00:00.62 |   12787 |   11M (0)|
|*  7 |        TABLE ACCESS FULL | CF10 |      1 |    250K|    250K|00:00:00.04 |    4583 |          |
|   8 |        TABLE ACCESS FULL | CF11 |      1 |   1096K|   1096K|00:00:00.11 |    8204 |          |
|   9 |     VIEW                 |      |      1 |  24156 |  24156 |00:00:00.81 |   12787 |          |
|  10 |      HASH UNIQUE         |      |      1 |  24156 |  24156 |00:00:00.81 |   12787 | 2271K (0)|
|* 11 |       HASH JOIN          |      |      1 |    550K|    550K|00:00:00.63 |   12787 |   11M (0)|
|* 12 |        TABLE ACCESS FULL | CF10 |      1 |    250K|    250K|00:00:00.04 |    4583 |          |
|  13 |        TABLE ACCESS FULL | CF11 |      1 |   1096K|   1096K|00:00:00.11 |    8204 |          |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(TO_DATE(MIN("Y"."SLE_DT"),'YYYYMMDD')-TO_DATE("X"."SLE_DT",'YYYYMMDD')-1>=7)
   3 - access("Y"."ITM_NO"="X"."ITM_NO")
       filter("Y"."SLE_DT">"X"."SLE_DT")
   6 - access("B"."SLE_NO"="A"."SLE_NO")
   7 - filter(("A"."SLE_DT">='20230101' AND "A"."SLE_DT"<='20231231'))
  11 - access("B"."SLE_NO"="A"."SLE_NO")
  12 - filter(("A"."SLE_DT">='20230101' AND "A"."SLE_DT"<='20231231'))

문제 ​

  1. 3번 줄 HASH JOIN이 어떤 짝을 몇 개 만드는지 쓰고, 6초가 어디서 나오는지 설명하시오.
  2. 판매일 쌍을 한 번만 만들어 상품마다 다음 판매일을 구하도록 SQL을 고치시오. 상품 1083처럼 마지막 판매 뒤로 안 팔린 구간도 나오게 하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자