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

A 등급 고객 스무 명의 상품평 현황에 일반 고객 1,980명이 0건으로 섞여 나온다

기초SELECT한빛유통185 / 700

상황 ​

CRM팀의 「VIP 상품평 활동」은 A 등급 고객 20명마다 1년 동안 남긴 상품평 건수와 평균 평점을 보여 준다. 평가를 하나도 남기지 않은 VIP도 0건으로 보여야 해서 아우터 조인으로 썼다. 화면을 연 CRM 담당자가 "VIP는 20명인데 목록이 2,000줄이고, 평가가 0건인 VIP가 일반 고객 사이에 묻혀서 안 보인다"라고 했다. 아래는 2023-07-01 ~ 2024-06-30으로 조회한 실행계획이다.

테이블과 인덱스 ​

  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · CUS_GD A 1%(20명) · B 9% · C 90%
  • T_ITM_EVL 상품평가: 5,000행 · 48블록 · PK_T_ITM_EVL (ITM_ID, EVL_LST_NO) · EVL_DT는 2023-01-01 ~ 2024-12-31에 고르게 분포
  • 인덱스 IX_ITM_EVL_CUS (CUS_ID, EVL_DT): 고객 상담 화면용으로 작년에 만들었다 · 통계 최신
  • 조회 기간에 A 등급 고객이 남긴 평가 27건(VIP 15명) · 평가가 없는 VIP 5명

원본 SQL ​

sql
SELECT c.CUS_ID, c.CUS_NM,
       COUNT(e.EVL_LST_NO)     AS evl_cnt,
       ROUND(AVG(e.EVL_PT), 1) AS avg_pt
  FROM M_CUS c
  LEFT JOIN T_ITM_EVL e
    ON e.CUS_ID = c.CUS_ID
   AND c.CUS_GD = 'A'
   AND e.EVL_DT >= :v_from
   AND e.EVL_DT <  :v_to
 GROUP BY c.CUS_ID, c.CUS_NM
 ORDER BY evl_cnt DESC, c.CUS_ID

실행계획 학습용 예시 ​

xplan
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |                 |      1 |        |   2000 |00:00:00.02 |      97 |
|   1 |  SORT ORDER BY                           |                 |      1 |   2000 |   2000 |00:00:00.02 |      97 |
|   2 |   HASH GROUP BY                          |                 |      1 |   2000 |   2000 |00:00:00.02 |      97 |
|   3 |    NESTED LOOPS OUTER                    |                 |      1 |   2000 |   2012 |00:00:00.02 |      97 |
|   4 |     TABLE ACCESS FULL                    | M_CUS           |      1 |   2000 |   2000 |00:00:00.01 |      30 |
|   5 |     VIEW                                 | VW_LAT_C83A7ED5 |   2000 |      1 |     27 |00:00:00.01 |      67 |
|*  6 |      FILTER                              |                 |   2000 |        |     27 |00:00:00.01 |      67 |
|   7 |       TABLE ACCESS BY INDEX ROWID BATCHED| T_ITM_EVL       |     20 |      1 |     27 |00:00:00.01 |      67 |
|*  8 |        INDEX RANGE SCAN                  | IX_ITM_EVL_CUS  |     20 |      1 |     27 |00:00:00.01 |      40 |
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   6 - filter("C"."CUS_GD"='A')
   8 - access("E"."CUS_ID"="C"."CUS_ID" AND "E"."EVL_DT">=:V_FROM AND "E"."EVL_DT"<:V_TO)

문제 ​

  1. 화면에 2,000줄이 나온 이유를 실행계획 4번 줄과 6번 줄로 설명하시오. ON 절의 c.CUS_GD = 'A'가 실제로 무엇을 정했는지도 설명하시오.
  2. A 등급 20명만 나오고 평가가 없는 VIP는 0건으로 나오도록 SQL을 고치시오. 아우터 조인에서 조건마다 ON과 WHERE 중 어디에 두어야 하는지 규칙으로 정리하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자