테마
CRM팀의 「구매 고객과 평가 고객 대조」는 한 달 동안 주문한 고객과 상품평을 남긴 고객을 한 표로 맞대어 '주문만', '평가만', '둘 다'를 나눈다. 기간을 바꿔 가며 하루 300번쯤 돌린다.
개발자는 처음에 두 인라인 뷰의 조인 조건 양쪽에 (+)를 붙였다가 ORA-01468이 나자, 한쪽씩 아우터 조인한 두 SQL을 UNION으로 이었다. 아래는 2024년 6월의 실행계획이다.
T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ)만 있다 · 한 달 주문은 테이블 전체에 흩어져 있다 · 2024년 6월 4,172건(고객 1,752명)T_ITM_EVL 상품평가: 5,000행 · 48블록 · PK_T_ITM_EVL (ITM_ID, EVL_LST_NO) · EVL_DT에는 인덱스가 없다 · 2024년 6월 211건(고객 200명)SELECT o.CUS_ID, o.ord_cnt, o.ord_amt, NVL(e.evl_cnt, 0) AS evl_cnt
FROM (SELECT CUS_ID, COUNT(*) AS ord_cnt, SUM(ORD_AMT) AS ord_amt
FROM T_ORD
WHERE ORD_DT >= :v_from AND ORD_DT < :v_to
GROUP BY CUS_ID) o,
(SELECT CUS_ID, COUNT(*) AS evl_cnt
FROM T_ITM_EVL
WHERE EVL_DT >= :v_from AND EVL_DT < :v_to
GROUP BY CUS_ID) e
WHERE e.CUS_ID(+) = o.CUS_ID
UNION
SELECT e.CUS_ID, NVL(o.ord_cnt, 0), NVL(o.ord_amt, 0), e.evl_cnt
FROM (SELECT CUS_ID, COUNT(*) AS evl_cnt
FROM T_ITM_EVL
WHERE EVL_DT >= :v_from AND EVL_DT < :v_to
GROUP BY CUS_ID) e,
(SELECT CUS_ID, COUNT(*) AS ord_cnt, SUM(ORD_AMT) AS ord_amt
FROM T_ORD
WHERE ORD_DT >= :v_from AND ORD_DT < :v_to
GROUP BY CUS_ID) o
WHERE o.CUS_ID(+) = e.CUS_ID
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1772 |00:00:00.05 | 1888 |
| 1 | SORT UNIQUE | | 1 | 1952 | 1772 |00:00:00.05 | 1888 |
| 2 | UNION-ALL | | 1 | | 1952 |00:00:00.04 | 1888 |
|* 3 | HASH JOIN OUTER | | 1 | 1752 | 1752 |00:00:00.02 | 944 |
| 4 | VIEW | | 1 | 1752 | 1752 |00:00:00.02 | 896 |
| 5 | HASH GROUP BY | | 1 | 1752 | 1752 |00:00:00.02 | 896 |
|* 6 | TABLE ACCESS FULL | T_ORD | 1 | 4170 | 4172 |00:00:00.01 | 896 |
| 7 | VIEW | | 1 | 200 | 200 |00:00:00.01 | 48 |
| 8 | HASH GROUP BY | | 1 | 200 | 200 |00:00:00.01 | 48 |
|* 9 | TABLE ACCESS FULL | T_ITM_EVL | 1 | 208 | 211 |00:00:00.01 | 48 |
|* 10 | HASH JOIN OUTER | | 1 | 200 | 200 |00:00:00.02 | 944 |
| 11 | VIEW | | 1 | 200 | 200 |00:00:00.01 | 48 |
| 12 | HASH GROUP BY | | 1 | 200 | 200 |00:00:00.01 | 48 |
|* 13 | TABLE ACCESS FULL | T_ITM_EVL | 1 | 208 | 211 |00:00:00.01 | 48 |
| 14 | VIEW | | 1 | 1752 | 1752 |00:00:00.02 | 896 |
| 15 | HASH GROUP BY | | 1 | 1752 | 1752 |00:00:00.02 | 896 |
|* 16 | TABLE ACCESS FULL | T_ORD | 1 | 4170 | 4172 |00:00:00.01 | 896 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("E"."CUS_ID"(+)="O"."CUS_ID")
6 - filter(("ORD_DT">=:V_FROM AND "ORD_DT"<:V_TO))
9 - filter(("EVL_DT">=:V_FROM AND "EVL_DT"<:V_TO))
10 - access("O"."CUS_ID"(+)="E"."CUS_ID")
13 - filter(("EVL_DT">=:V_FROM AND "EVL_DT"<:V_TO))
16 - filter(("ORD_DT">=:V_FROM AND "ORD_DT"<:V_TO))