Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-069

회계 감사용 포인트 원장 내려받기의 2024년 적립 합계가 18,500포인트 모자란다

실전SELECT이커머스304 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

회계 감사에서 「포인트 원장」 내려받기의 2024년 적립 합계가 point_transactions를 직접 더한 값보다 18,500포인트 적다는 지적이 나왔다. 원인을 찾던 DBA는 SQL의 UNION을 보고 "UNION이 들어간 SQL을 전부 UNION ALL로 바꾸자. 정렬 비용도 준다"라고 제안했다. 지금 UNION을 쓰는 SQL은 아래 셋이다. 박팀장은 SQL마다 바꿔도 되는지, 바꾸면 무엇이 달라지는지 정리해 달라고 했다.

테이블과 인덱스 ​

  • point_transactions: 45,000행 · 210블록 · tx_id(PK) · customer_id · amount · tx_type(EARN · USE · EXPIRE) · created_at · 2024년 9,120건(EARN 5,880 · USE 2,760 · EXPIRE 480) · 이벤트 적립은 배치가 같은 초에 같은 금액(500)으로 한꺼번에 넣는다
  • customers: 5,230행 · 51블록 · reviews: 15,000행 · 775블록 · orders: 34,908행 · 193블록 · status 6가지(PENDING · PAID · SHIPPED · DELIVERED · CANCELLED · RETURNED) · 모두 PK만 있다
  • 2024년 11월: 리뷰 268건(고객 231명) · 주문 720건(고객 652명) · 리뷰와 주문이 다 있는 고객 118명 · 취소 29건 · 반품 14건
  • 통계 최신

원본 SQL ​

sql
-- S1 「11월 활동 고객」 마케팅 문자 대상 (리뷰를 썼거나 주문한 고객)
SELECT c.customer_id, c.name, c.email
  FROM customers c, reviews r
 WHERE r.customer_id = c.customer_id
   AND r.created_at >= DATE '2024-11-01' AND r.created_at < DATE '2024-12-01'
UNION
SELECT c.customer_id, c.name, c.email
  FROM customers c, orders o
 WHERE o.customer_id = c.customer_id
   AND o.order_date >= DATE '2024-11-01' AND o.order_date < DATE '2024-12-01';

-- S2 「포인트 원장」 2024년 내려받기 (회계 감사 자료)
SELECT customer_id, created_at, '적립' AS tp, amount FROM point_transactions
 WHERE tx_type = 'EARN'   AND created_at >= DATE '2024-01-01' AND created_at < DATE '2025-01-01'
UNION
SELECT customer_id, created_at, '사용', amount FROM point_transactions
 WHERE tx_type = 'USE'    AND created_at >= DATE '2024-01-01' AND created_at < DATE '2025-01-01'
UNION
SELECT customer_id, created_at, '소멸', amount FROM point_transactions
 WHERE tx_type = 'EXPIRE' AND created_at >= DATE '2024-01-01' AND created_at < DATE '2025-01-01'
 ORDER BY 1, 2;

-- S3 「취소 · 반품 주문」 월 목록
SELECT order_id, customer_id, order_date, status, total_amount FROM orders
 WHERE status = 'CANCELLED' AND order_date >= :d1 AND order_date < :d2
UNION
SELECT order_id, customer_id, order_date, status, total_amount FROM orders
 WHERE status = 'RETURNED'  AND order_date >= :d1 AND order_date < :d2;

실행계획 학습용 예시 ​

xplan
[S1]
-----------------------------------------------------------------------------------------------
| Id  | Operation              | Name      | Starts | E-Rows | A-Rows |      A-Time | Buffers |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |           |      1 |        |    765 | 00:00:00.02 |    1070 |
|   1 |  SORT UNIQUE           |           |      1 |    950 |    765 | 00:00:00.02 |    1070 |
|   2 |   UNION-ALL            |           |      1 |        |    988 | 00:00:00.01 |    1070 |
|*  3 |    HASH JOIN           |           |      1 |    250 |    268 | 00:00:00.01 |     826 |
|*  4 |     TABLE ACCESS FULL  | REVIEWS   |      1 |    250 |    268 | 00:00:00.01 |     775 |
|   5 |     TABLE ACCESS FULL  | CUSTOMERS |      1 |   5230 |   5230 | 00:00:00.01 |      51 |
|*  6 |    HASH JOIN           |           |      1 |    700 |    720 | 00:00:00.01 |     244 |
|*  7 |     TABLE ACCESS FULL  | ORDERS    |      1 |    700 |    720 | 00:00:00.01 |     193 |
|   8 |     TABLE ACCESS FULL  | CUSTOMERS |      1 |   5230 |   5230 | 00:00:00.01 |      51 |
-----------------------------------------------------------------------------------------------

[S2]
-------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name               | Starts | E-Rows | A-Rows |      A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                    |      1 |        |   9083 | 00:00:00.03 |     630 |
|   1 |  SORT UNIQUE          |                    |      1 |   9100 |   9083 | 00:00:00.03 |     630 |
|   2 |   UNION-ALL           |                    |      1 |        |   9120 | 00:00:00.01 |     630 |
|*  3 |    TABLE ACCESS FULL  | POINT_TRANSACTIONS |      1 |   5900 |   5880 | 00:00:00.01 |     210 |
|*  4 |    TABLE ACCESS FULL  | POINT_TRANSACTIONS |      1 |   2700 |   2760 | 00:00:00.01 |     210 |
|*  5 |    TABLE ACCESS FULL  | POINT_TRANSACTIONS |      1 |    500 |    480 | 00:00:00.01 |     210 |
-------------------------------------------------------------------------------------------------------

[S3 · 2024년 11월]
-------------------------------------------------------------------------------------------
| Id  | Operation             | Name   | Starts | E-Rows | A-Rows |      A-Time | Buffers |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |      1 |        |     43 | 00:00:00.01 |     386 |
|   1 |  SORT UNIQUE          |        |      1 |     44 |     43 | 00:00:00.01 |     386 |
|   2 |   UNION-ALL           |        |      1 |        |     43 | 00:00:00.01 |     386 |
|*  3 |    TABLE ACCESS FULL  | ORDERS |      1 |     29 |     29 | 00:00:00.01 |     193 |
|*  4 |    TABLE ACCESS FULL  | ORDERS |      1 |     15 |     14 | 00:00:00.01 |     193 |
-------------------------------------------------------------------------------------------

[S2 대조]  적립 합계: 내려받기 2,921,500 · point_transactions 2,940,000 (차이 18,500)

문제 ​

  1. 2024년 적립 합계가 18,500포인트 모자란 이유를 S2의 실행계획으로 설명하시오.
  2. DBA의 제안대로 세 SQL의 UNION을 UNION ALL로 바꾸면 SQL마다 결과가 어떻게 달라지는지 판단하시오.
  3. 세 SQL을 각각 맞는 결과를 내도록 고치고, 고친 뒤 결과가 맞는지 무엇으로 대조할지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자