테마
이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.
이커머스 회계팀은 매월 1일 00:00에 「포인트 대사 리포트」를 받아 포인트 부채를 확인한다. 리포트는 고객마다 적립, 사용, 소멸 합계와 잔액을 한 줄에 보여 준다. 같은 시각에 「포인트 소멸 배치」도 돈다. 회계팀이 물었다. "한 줄 안에서 적립에서 사용과 소멸을 빼면 잔액이 되어야 하는데 37명이 안 맞아요. SQL 한 문장이면 한 시점이라고 들었는데요?" 낮에 다시 뽑으면 모두 맞는다.
customers: 5,230행 · 51블록 · PK customer_idpoint_transactions: 45,000행 · 210블록 · PK tx_id만 있다 · tx_type은 EARN, USE, EXPIRE · 이 문제에서 amount는 부호 없이 적고 tx_type으로 더할지 뺄지를 정한다fn_point_sum(고객, 종류)와 fn_point_bal(고객)은 화면 여러 곳이 쓰는 공용 함수다.-- 공용 함수 (둘 다 point_transactions 를 읽는다)
CREATE OR REPLACE FUNCTION fn_point_sum(p_cus NUMBER, p_tp VARCHAR2) RETURN NUMBER IS
v NUMBER;
BEGIN
SELECT NVL(SUM(amount), 0) INTO v
FROM point_transactions
WHERE customer_id = p_cus AND tx_type = p_tp;
RETURN v;
END;
CREATE OR REPLACE FUNCTION fn_point_bal(p_cus NUMBER) RETURN NUMBER IS
v NUMBER;
BEGIN
SELECT NVL(SUM(CASE tx_type WHEN 'EARN' THEN amount ELSE -amount END), 0) INTO v
FROM point_transactions
WHERE customer_id = p_cus;
RETURN v;
END;
-- 「포인트 대사 리포트」 (매월 1일 00:00:05)
SELECT c.customer_id, c.name, c.grade,
fn_point_sum(c.customer_id, 'EARN') AS earn_amt,
fn_point_sum(c.customer_id, 'USE') AS use_amt,
fn_point_sum(c.customer_id, 'EXPIRE') AS expire_amt,
fn_point_bal(c.customer_id) AS bal_amt
FROM customers c
ORDER BY c.customer_id;
[리포트 — ALLSTATS LAST (11월 1일 00:00:05)]
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 5230 | 00:00:09.41 | 51 |
| 1 | SORT ORDER BY | | 1 | 5230 | 5230 | 00:00:09.41 | 51 |
| 2 | TABLE ACCESS FULL | CUSTOMERS | 1 | 5230 | 5230 | 00:00:09.36 | 51 |
--------------------------------------------------------------------------------------------
[함수 안의 SQL — V$SQL (같은 실행)]
SQL_TEXT EXECUTIONS BUFFER_GETS ELAPSED
SELECT NVL(SUM(AMOUNT), 0) FROM POINT_TRANSACTIONS
WHERE CUSTOMER_ID = :B2 AND TX_TYPE = :B1 15,690 3,294,900 6.9초
SELECT NVL(SUM(CASE TX_TYPE WHEN 'EARN' THEN AMOUNT ELSE -AMOUNT END), 0)
FROM POINT_TRANSACTIONS WHERE CUSTOMER_ID = :B1 5,230 1,098,300 2.3초
(실행 한 번에 TABLE ACCESS FULL 210블록)
[고객 2,417 의 한 줄 — 함수가 불린 순간]
00:00:07.204 fn_point_sum EARN → 12,000
00:00:07.205 fn_point_sum USE → 3,000
00:00:07.206 fn_point_sum EXPIRE → 0
00:00:07.206 소멸 배치가 고객 2,417 에 EXPIRE 2,500 INSERT · COMMIT
00:00:07.207 fn_point_bal → 6,500 ← 12,000 - 3,000 - 0 = 9,000 이어야 한 줄이 맞는다
[11월 1일 리포트] 5,230행 · 한 줄 안에서 맞지 않는 고객 37명 · 14:00 에 다시 뽑으면 0명
V$SQL의 숫자가 왜 이렇게 다른지도 쓰시오.