한 회사는 지역 단위로 직원들의 매출 성과를 분석하고자 한다.
데이터는 다음과 같은 테이블 구조로 관리된다.
regions (
id INT PRIMARY KEY,
name VARCHAR(50)
)
states (
id INT PRIMARY KEY,
name VARCHAR(50),
regionId INT
)
employees (
id INT PRIMARY KEY,
name VARCHAR(50),
stateId INT
)
sales (
id INT PRIMARY KEY,
amount INT,
employeeId INT
)
각 지역(region)에 대해 다음 정보를 조회하라.
지역 이름
해당 지역의 직원 1인당 평균 매출
0으로 처리한다전체 지역 중 가장 높은 평균 매출과 해당 지역 평균 매출의 차이
WITH region_avgs AS (
SELECT
r.name AS region_name,
COALESCE(
SUM(sl.amount) * 1.0
/ NULLIF(COUNT(DISTINCT e.id), 0),
0
) AS avg_amount
FROM regions r
LEFT JOIN states st
ON r.id = st.regionId
LEFT JOIN employees e
ON st.id = e.stateId
LEFT JOIN sales sl
ON sl.employeeId = e.id
GROUP BY r.name
)
SELECT
region_name,
avg_amount,
(SELECT MAX(avg_amount) FROM region_avgs) - avg_amount AS diff_highest_avg
FROM region_avgs;
regions이다이 문제의 핵심 조건은 다음 문장이다.
직원이 없거나 매출이 없는 지역도 결과에 포함되어야 한다
즉, 결과 집합에서 절대 사라지면 안 되는 엔티티는 regions이다.
따라서 JOIN 방향은 반드시 다음 구조여야 한다.
regions
LEFT JOIN states
LEFT JOIN employees
LEFT JOIN sales
INNER JOIN을 사용하면 직원이나 주(state)가 없는 지역은 결과에서 탈락한다.
이는 요구사항 위반이다.
SUM(sl.amount) / COUNT(DISTINCT e.id)
이 수식은 다음 의미를 가진다.
하지만 이 계산에는 두 가지 위험이 존재한다.
COUNT(DISTINCT e.id) = 0
→ 0으로 나누기 발생
→ SQL 에러 또는 NULL 반환
SUM(sl.amount) = NULL
→ 평균이 NULL이 됨
NULLIF와 COALESCE의 역할이를 방지하기 위해 다음과 같이 처리한다.
SUM(sl.amount) * 1.0
/ NULLIF(COUNT(DISTINCT e.id), 0)
NULLIF(x, 0)COALESCE(..., 0)즉,
직원이 없거나, 매출이 전혀 없는 지역의 평균 매출은 0이다
라는 요구사항을 정확히 구현한다.
COUNT(DISTINCT e.id)를 사용한 이유직원은 여러 건의 매출을 가질 수 있다.
만약 COUNT(e.id)를 사용하면 매출 건수만큼 직원이 중복 계산된다.
COUNT(DISTINCT e.id)
→ 지역 내 고유한 직원 수 계산
→ 평균 정의에 정확히 부합한다.
(SELECT MAX(avg_amount) FROM region_avgs) - avg_amount
CTE(region_avgs)를 사용했기 때문에 중복 계산 없이 깔끔하게 표현된다.
이 문제는 단순한 집계 문제가 아니다.
핵심은 다음 질문이다.
“결과에서 반드시 살아남아야 하는 테이블은 무엇인가?”
정답 사고 흐름은 다음과 같다.
regions 기준지역 단위 평균 매출 분석에서는 JOIN 방향이 계산식보다 중요하며, 직원이 없는 경우를 고려해 LEFT JOIN과 NULLIF, COALESCE 처리가 필수적이다.