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

목표 대비 실적 화면이 판매상품을 열두 번 읽고, 빨라진 새 판은 목표를 잡지 않은 달의 실적을 빠뜨린다

중급SELECT유통369 / 700

상황 ​

본사 영업기획팀의 「상품별 월 목표 대비 실적」(2023년, 상품 109개 × 12개월 = 1,308줄)은 열 때마다 1.9초가 걸린다. 영업기획팀 20명이 하루 약 400번 연다. 목표는 상품마다 한 줄에 열두 달 칸이 옆으로 늘어선 표 CF_ITM_GOAL에 들어 있어서, 2019년 작성자는 달마다 한 덩어리씩 열두 덩어리를 UNION ALL로 이어 붙였다. 조민우가 UNPIVOT으로 목표를 세로로 세우고 실적을 한 번에 묶는 판을 만들었다. 0.3초로 빨라졌지만 1,261줄만 나왔다. 영업기획팀은 "목표를 잡지 않은 달도 실적은 보여야 한다. 시범 판매나 시즌 연장 실적이 거기 있다"라고 한다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록(전체 스캔 4,510) · SLE_DT VARCHAR2(8) · 2023년 250,120건 · 한 달 약 20,800건
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · SLE_QTY · 2023년 줄 약 548,000
  • CF_ITM_GOAL 상품 월 목표(이 문제에서 쓰는 표): GOAL_YY · ITM_NO · M01_QTY ~ M12_QTY · 연도마다 상품 109줄(2년 218행) · 4블록 · PK (GOAL_YY, ITM_NO)
  • 목표를 잡지 않은 달은 NULL이다. 2023년에는 상품 11개에 NULL 칸이 47개 있고, 그 가운데 19칸에 판매 실적이 있다
  • 인덱스 IX_CF10_SLE_DT (SLE_DT): BLEVEL 2 · 리프 1,393 · CF 9,921 · 통계 최신

원본 SQL ​

sql
-- 지금 SQL (2019년) : 달마다 한 덩어리 · 모두 12 덩어리
SELECT g.ITM_NO, '01' AS MM, g.M01_QTY AS GOAL_QTY, a.ACT_QTY
  FROM CF_ITM_GOAL g,
       (SELECT d.ITM_NO, SUM(d.SLE_QTY) AS ACT_QTY
          FROM CF10 h, CF11 d
         WHERE h.SLE_DT BETWEEN '20230101' AND '20230131'
           AND d.SLE_NO = h.SLE_NO
         GROUP BY d.ITM_NO) a
 WHERE g.GOAL_YY = '2023'
   AND a.ITM_NO(+) = g.ITM_NO
UNION ALL
SELECT g.ITM_NO, '02', g.M02_QTY, a.ACT_QTY
  FROM CF_ITM_GOAL g,
       (SELECT d.ITM_NO, SUM(d.SLE_QTY) AS ACT_QTY
          FROM CF10 h, CF11 d
         WHERE h.SLE_DT BETWEEN '20230201' AND '20230228'
           AND d.SLE_NO = h.SLE_NO
         GROUP BY d.ITM_NO) a
 WHERE g.GOAL_YY = '2023'
   AND a.ITM_NO(+) = g.ITM_NO
UNION ALL
-- … 3월 ~ 12월도 같은 모양
ORDER BY 1, 2;

-- 조민우 판 (0.3초 · 1,261줄)
SELECT g.ITM_NO, g.MM, g.GOAL_QTY, a.ACT_QTY
  FROM (SELECT ITM_NO, MM, GOAL_QTY
          FROM CF_ITM_GOAL
       UNPIVOT (GOAL_QTY FOR MM IN (M01_QTY AS '01', M02_QTY AS '02', M03_QTY AS '03', M04_QTY AS '04',
                                    M05_QTY AS '05', M06_QTY AS '06', M07_QTY AS '07', M08_QTY AS '08',
                                    M09_QTY AS '09', M10_QTY AS '10', M11_QTY AS '11', M12_QTY AS '12'))
         WHERE GOAL_YY = '2023') g,
       (SELECT d.ITM_NO, SUBSTR(h.SLE_DT, 5, 2) AS MM, SUM(d.SLE_QTY) AS ACT_QTY
          FROM CF10 h, CF11 d
         WHERE h.SLE_DT BETWEEN '20230101' AND '20231231'
           AND d.SLE_NO = h.SLE_NO
         GROUP BY d.ITM_NO, SUBSTR(h.SLE_DT, 5, 2)) a
 WHERE a.ITM_NO(+) = g.ITM_NO
   AND a.MM(+)     = g.MM
 ORDER BY 1, 2;

실행계획 학습용 예시 ​

xplan
[지금 SQL · 덩어리 12개 가운데 1월과 12월만 펼쳐 적는다]
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |                |      1 |        |   1308 |00:00:01.91 |     104K|
|   1 |  SORT ORDER BY                            |                |      1 |   1308 |   1308 |00:00:01.91 |     104K|
|   2 |   UNION-ALL                               |                |      1 |        |   1308 |00:00:01.90 |     104K|
|*  3 |    HASH JOIN OUTER                        |                |      1 |    109 |    109 |00:00:00.16 |    8677 |
|*  4 |     TABLE ACCESS FULL                     | CF_ITM_GOAL    |      1 |    109 |    109 |00:00:00.01 |       4 |
|   5 |     VIEW                                  |                |      1 |     88 |     84 |00:00:00.16 |    8673 |
|   6 |      HASH GROUP BY                        |                |      1 |     88 |     84 |00:00:00.16 |    8673 |
|*  7 |       HASH JOIN                           |                |      1 |  45900 |  45188 |00:00:00.14 |    8673 |
|   8 |        TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |  20850 |  20611 |00:00:00.01 |     469 |
|*  9 |         INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |  20850 |  20611 |00:00:00.01 |      60 |
|  10 |        TABLE ACCESS FULL                  | CF11           |      1 |   1096K|   1096K|00:00:00.11 |    8204 |
|     |    (2월 ~ 11월 덩어리 10개 · 모양이 같고 덩어리마다 CF11 FULL 8,204)                                             |
|* 91 |    HASH JOIN OUTER                        |                |      1 |    109 |    109 |00:00:00.16 |    8692 |
|* 92 |     TABLE ACCESS FULL                     | CF_ITM_GOAL    |      1 |    109 |    109 |00:00:00.01 |       4 |
|  93 |     VIEW                                  |                |      1 |     88 |     88 |00:00:00.16 |    8688 |
|  94 |      HASH GROUP BY                        |                |      1 |     88 |     88 |00:00:00.16 |    8688 |
|* 95 |       HASH JOIN                           |                |      1 |  46600 |  46640 |00:00:00.14 |    8688 |
|  96 |        TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |  21250 |  21264 |00:00:00.01 |     484 |
|* 97 |         INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |  21250 |  21264 |00:00:00.01 |      62 |
|  98 |        TABLE ACCESS FULL                  | CF11           |      1 |   1096K|   1096K|00:00:00.11 |    8204 |
------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("A"."ITM_NO"="G"."ITM_NO")
   4 - filter("G"."GOAL_YY"='2023')
   7 - access("D"."SLE_NO"="H"."SLE_NO")
   9 - access("H"."SLE_DT">='20230101' AND "H"."SLE_DT"<='20230131')
  … (덩어리마다 같은 모양 · 97 - access("H"."SLE_DT">='20231201' AND "H"."SLE_DT"<='20231231'))

문제 ​

  1. 지금 SQL이 판매상품 테이블을 몇 번 읽는지 실행계획으로 세고, 조민우 판에서 줄이 사라진 까닭을 UNPIVOT의 기본 동작으로 설명하시오.
  2. 판매 테이블을 한 번씩만 읽으면서 지금 SQL과 같은 1,308줄을 돌려주도록 SQL을 고치시오.
  3. 고친 SQL이 지금 SQL과 같은 답인지 확인하는 방법을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자