데이터 분석가 면접을 위한 고급 SQL: 서브쿼리, 피벗, 쿼리 최적화 완벽 가이드 (2026년)
데이터 분석가 면접에서 자주 출제되는 고급 SQL 주제를 심층적으로 다룹니다. 상관 서브쿼리, 조건부 집계를 활용한 피벗 쿼리, EXPLAIN 분석, 인덱싱 전략, 그리고 흔한 안티패턴까지 실전 코드와 함께 정리합니다.

데이터 분석가 면접에서 SQL은 단순한 SELECT 문을 넘어 복잡한 비즈니스 로직을 쿼리로 표현하는 능력을 평가합니다. 2026년 현재 기업들은 서브쿼리의 실행 원리를 이해하고, 행 데이터를 열로 변환하는 피벗 기법을 구사하며, 실행 계획을 분석하여 성능 병목을 해결할 수 있는 분석가를 찾고 있습니다. 이 글에서는 데이터 분석가 면접에서 가장 빈번하게 등장하는 고급 SQL 주제들을 실전 코드와 함께 체계적으로 정리합니다.
면접관은 단순히 정답 쿼리를 작성하는 능력만 보지 않습니다. 서브쿼리와 JOIN의 성능 차이를 설명할 수 있는지, 왜 특정 접근법을 선택했는지 논리적으로 근거를 제시할 수 있는지, 그리고 대규모 데이터셋에서 쿼리가 어떻게 동작할지 예측할 수 있는지를 종합적으로 평가합니다.
상관 서브쿼리와 일반 서브쿼리의 차이
서브쿼리는 다른 쿼리 내부에 중첩된 SELECT 문입니다. 일반 서브쿼리(non-correlated subquery)는 외부 쿼리와 독립적으로 한 번만 실행됩니다. 평균 주문 금액보다 큰 주문을 찾는 다음 예시에서, 내부 SELECT는 전체 테이블에 대해 단 한 번 실행되어 스칼라 값을 반환합니다.
-- regular_subquery_threshold.sql
-- Find all orders above the average order value
SELECT
order_id,
customer_id,
total_amount
FROM orders
WHERE total_amount > (
-- Executes once, returns a single scalar value
SELECT AVG(total_amount)
FROM orders
);반면, 상관 서브쿼리(correlated subquery)는 외부 쿼리의 각 행에 대해 반복 실행됩니다. 아래 쿼리는 각 직원의 부서 평균 급여를 기준으로 비교하기 때문에, 외부 쿼리가 처리하는 행마다 내부 쿼리가 재평가됩니다.
-- correlated_subquery_department.sql
-- Employees earning above their department average
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
WHERE e.salary > (
-- Re-evaluated for each employee's department
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e.department
);상관 서브쿼리는 직관적이지만, 행 수가 증가하면 O(N*M) 복잡도로 성능이 급격히 저하됩니다. 면접에서는 이를 CTE(Common Table Expression)나 윈도우 함수로 최적화하는 방법을 제시하면 높은 점수를 받을 수 있습니다. 아래는 동일한 결과를 단일 패스로 산출하는 CTE 활용 예시입니다.
-- optimized_with_cte.sql
-- Same result, single pass over the data
WITH dept_avg AS (
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
JOIN dept_avg d ON e.department = d.department
WHERE e.salary > d.avg_salary;CTE를 사용하면 부서별 평균을 한 번만 계산하고 JOIN으로 연결하므로, 테이블 스캔 횟수가 크게 줄어듭니다. 면접 상황에서 상관 서브쿼리를 작성한 후 즉시 CTE 기반 최적화 버전을 제시하면, 성능 의식(performance awareness)을 효과적으로 보여줄 수 있습니다.
데이터 분석가 기술 스크리닝의 서브쿼리 패턴
면접에서 자주 등장하는 또 다른 패턴은 존재 여부 확인입니다. 2026년에 주문을 한 고객을 찾는 두 가지 접근법을 비교하면, EXISTS는 첫 번째 매칭 행을 발견하는 즉시 탐색을 중단하는 반면, IN은 서브쿼리의 전체 결과 집합을 먼저 구성합니다.
-- exists_vs_in.sql
-- Customers who placed at least one order in 2026 (EXISTS — preferred)
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= '2026-01-01'
);
-- Equivalent with IN (slower on large datasets)
SELECT customer_id, name
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
WHERE order_date >= '2026-01-01'
);대규모 데이터셋에서는 EXISTS가 IN보다 효율적인 경우가 많습니다. 옵티마이저가 semi-join으로 변환할 수 있으며, 조기 종료(early termination)가 가능하기 때문입니다. 면접에서 이 차이를 설명할 수 있으면 쿼리 엔진 내부 동작에 대한 이해도를 증명할 수 있습니다.
SELECT 절 내의 스칼라 서브쿼리도 빈출 패턴입니다. 각 제품과 해당 카테고리의 총 매출을 함께 조회하는 다음 예시는, 분석 리포트 작성 시 자주 마주치는 요구사항을 반영합니다.
-- scalar_subquery_select.sql
-- Each product with its category's total revenue
SELECT
p.product_id,
p.product_name,
p.category,
(
SELECT SUM(oi.quantity * oi.unit_price)
FROM order_items oi
JOIN products p2 ON oi.product_id = p2.product_id
WHERE p2.category = p.category
) AS category_total_revenue
FROM products p;이 쿼리 역시 상관 서브쿼리이므로 카테고리 수가 많아지면 성능 문제가 발생할 수 있습니다. 실무에서는 CTE나 윈도우 함수(SUM OVER PARTITION BY)로 대체하는 것이 일반적입니다.
Data Analytics 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
조건부 집계를 활용한 피벗 쿼리
행 기반 데이터를 열 기반으로 변환하는 피벗(Pivot)은 데이터 분석 면접의 핵심 주제입니다. 표준 SQL에서는 CASE WHEN과 집계 함수를 결합하여 피벗을 구현합니다. 아래 예시는 제품별 월간 매출을 하나의 행에 월별 열로 표시합니다.
-- pivot_monthly_revenue.sql
-- Monthly revenue pivot: one row per product, one column per month
SELECT
product_id,
product_name,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1
THEN quantity * unit_price ELSE 0 END) AS jan_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2
THEN quantity * unit_price ELSE 0 END) AS feb_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3
THEN quantity * unit_price ELSE 0 END) AS mar_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 4
THEN quantity * unit_price ELSE 0 END) AS apr_revenue,
-- Repeat for remaining months
SUM(quantity * unit_price) AS total_revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date >= '2026-01-01'
GROUP BY product_id, product_name
ORDER BY total_revenue DESC;이 패턴은 DBMS에 관계없이 동작하므로 면접에서 가장 안전한 접근법입니다. 조건부 집계는 매출 분석 외에도 사용자 행동 분석에 광범위하게 활용됩니다. 다음은 디바이스 유형별 세션 분포를 분석하는 예시입니다.
-- pivot_user_activity.sql
-- User activity: sessions by day of week and device type
SELECT
user_id,
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) AS mobile_sessions,
COUNT(CASE WHEN device_type = 'desktop' THEN 1 END) AS desktop_sessions,
COUNT(CASE WHEN device_type = 'tablet' THEN 1 END) AS tablet_sessions,
ROUND(
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) * 100.0 / COUNT(*),
1
) AS mobile_pct
FROM user_sessions
WHERE session_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
HAVING COUNT(*) >= 5 -- Only users with meaningful activity
ORDER BY mobile_pct DESC;HAVING 절을 통해 의미 있는 활동량을 가진 사용자만 필터링하는 것은 노이즈를 제거하는 실무적 기법입니다. 면접에서 이런 세부 사항을 자연스럽게 포함하면 실무 경험을 간접적으로 어필할 수 있습니다.
PostgreSQL의 CROSSTAB을 활용한 동적 피벗
PostgreSQL 환경에서는 tablefunc 확장의 CROSSTAB 함수를 사용하여 더 체계적인 피벗을 구현할 수 있습니다. 분기별 제품 매출을 피벗하는 다음 예시는 PostgreSQL 특화 면접에서 차별화 포인트가 됩니다.
-- crosstab_dynamic_pivot.sql
-- Enable the extension (once per database)
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- Revenue by product and quarter using CROSSTAB
SELECT *
FROM crosstab(
$$
SELECT
product_name,
'Q' || EXTRACT(QUARTER FROM order_date)::TEXT AS quarter,
SUM(quantity * unit_price) AS revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE EXTRACT(YEAR FROM o.order_date) = 2026
GROUP BY product_name, quarter
ORDER BY product_name, quarter
$$,
$$ VALUES ('Q1'), ('Q2'), ('Q3'), ('Q4') $$
) AS pivot_table(
product_name TEXT,
q1_revenue NUMERIC,
q2_revenue NUMERIC,
q3_revenue NUMERIC,
q4_revenue NUMERIC
);CROSSTAB의 첫 번째 인자는 행 식별자, 카테고리, 값 순서로 정렬된 데이터를 반환하는 쿼리이며, 두 번째 인자는 피벗 열이 될 카테고리 목록입니다. 결과 스키마는 AS 절에서 명시적으로 정의합니다. CROSSTAB은 조건부 집계보다 가독성이 높지만, PostgreSQL 전용이라는 점에서 면접 시 DBMS 범용성을 함께 언급하는 것이 좋습니다.
SQL 쿼리 최적화: EXPLAIN 계획과 비용 분석
쿼리 최적화 역량은 시니어 데이터 분석가 면접에서 필수적으로 검증하는 항목입니다. EXPLAIN ANALYZE는 쿼리 옵티마이저가 생성한 실행 계획과 실제 실행 통계를 함께 보여줍니다.
-- explain_analyze_example.sql
-- Analyze a slow query to identify bottlenecks
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT
c.customer_id,
c.name,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS lifetime_value
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2025-01-01'
GROUP BY c.customer_id, c.name
HAVING SUM(o.total_amount) > 1000
ORDER BY lifetime_value DESC;EXPLAIN 출력에서 확인해야 할 핵심 지표는 다음과 같습니다. Seq Scan은 인덱스 없이 전체 테이블을 순차 탐색하는 것으로, 대형 테이블에서 나타나면 인덱스 추가를 검토해야 합니다. Nested Loop은 소규모 결과셋에는 적합하지만, 양쪽 테이블이 크면 Hash Join이나 Merge Join이 더 효율적입니다. BUFFERS 옵션은 디스크 I/O와 캐시 히트 비율을 보여주어 메모리 튜닝 방향을 제시합니다.
면접에서 EXPLAIN 출력을 해석하는 능력을 보여주면, 단순히 쿼리를 작성하는 수준을 넘어 운영 환경에서의 성능 관리 역량까지 입증할 수 있습니다.
면접 수준의 SQL 최적화를 위한 인덱싱 전략
적절한 인덱스는 쿼리 성능을 극적으로 개선합니다. 면접에서는 어떤 열에 인덱스를 생성해야 하는지, 복합 인덱스의 열 순서가 왜 중요한지, 커버링 인덱스가 무엇인지를 설명할 수 있어야 합니다.
-- indexing_strategies.sql
-- Index on order_date: filters a small percentage of rows
CREATE INDEX idx_orders_date ON orders (order_date);
-- Composite index for queries filtering on both columns
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date);
-- Covering index: includes columns needed in SELECT
-- Enables Index Only Scan (no table access)
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date)
INCLUDE (total_amount, status);복합 인덱스에서 열 순서는 선택도(selectivity)와 쿼리 패턴에 따라 결정합니다. WHERE 절에서 등호 조건으로 사용되는 열을 앞에, 범위 조건으로 사용되는 열을 뒤에 배치하는 것이 일반적인 원칙입니다. 커버링 인덱스의 INCLUDE 절은 인덱스에 추가 열을 포함시켜 테이블 힙 접근 없이 Index Only Scan으로 결과를 반환할 수 있게 합니다.
부분 인덱스(Partial Index)는 특정 조건을 만족하는 행만 인덱싱하여 인덱스 크기를 줄이고 쓰기 성능 오버헤드를 최소화합니다.
-- partial_index.sql
-- Only index active orders (smaller, faster index)
CREATE INDEX idx_orders_active
ON orders (customer_id, order_date)
WHERE status = 'active';
-- Only index recent data for dashboard queries
CREATE INDEX idx_orders_recent
ON orders (order_date)
WHERE order_date >= '2026-01-01';대시보드 쿼리가 항상 최근 데이터만 조회한다면, 부분 인덱스를 통해 전체 테이블 대비 훨씬 작은 인덱스로 동일한 성능을 확보할 수 있습니다.
인덱스를 무조건 많이 생성하는 것은 해결책이 아닙니다. 각 인덱스는 INSERT, UPDATE, DELETE 연산마다 유지보수 비용을 발생시킵니다. 사용하지 않는 인덱스는 쓰기 성능만 저하시키므로, pg_stat_user_indexes 뷰를 통해 인덱스 사용 현황을 주기적으로 모니터링해야 합니다.
흔한 쿼리 안티패턴
면접에서 자주 출제되는 안티패턴 중 하나는 인덱스 열에 함수를 적용하는 것입니다. WHERE 절에서 열에 함수를 적용하면 옵티마이저가 해당 열의 인덱스를 활용할 수 없게 됩니다.
-- anti_patterns.sql
-- BAD: function on indexed column disables the index
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2026;
-- GOOD: range comparison uses the index
SELECT * FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01';EXTRACT 함수를 적용하면 order_date 열의 모든 값을 변환한 후 비교해야 하므로 인덱스 스캔이 불가능해집니다. 범위 비교로 변환하면 B-tree 인덱스의 정렬 순서를 그대로 활용할 수 있습니다.
또 다른 위험한 안티패턴은 NOT IN과 NULL 값의 조합입니다. 서브쿼리 결과에 NULL이 하나라도 포함되면 NOT IN 조건은 항상 빈 결과를 반환합니다. SQL의 3값 논리(TRUE, FALSE, UNKNOWN) 때문에 NULL과의 비교는 UNKNOWN을 반환하고, NOT IN은 모든 비교가 TRUE일 때만 행을 포함시키기 때문입니다.
-- not_in_null_trap.sql
-- BAD: returns empty if any customer_id is NULL in subquery
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);
-- GOOD: NOT EXISTS handles NULLs correctly
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
);NOT EXISTS는 NULL 값에 영향을 받지 않으며, 옵티마이저가 anti-join으로 효율적으로 변환할 수 있습니다. 면접에서 NOT IN의 NULL 함정을 자발적으로 언급하면 SQL의 근본적인 동작 원리를 깊이 이해하고 있음을 보여줄 수 있습니다.
고급 SQL 개념을 학습하는 것만으로는 충분하지 않습니다. 실제 데이터셋으로 쿼리를 작성하고, EXPLAIN 출력을 분석하며, 다양한 인덱싱 전략을 실험해 보는 과정이 면접 합격의 열쇠입니다. 반복적인 실습을 통해 패턴을 체화하면 면접 상황에서도 자연스럽게 최적의 접근법을 제시할 수 있습니다.
Data Analytics 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
결론
데이터 분석가 면접에서 고급 SQL 역량은 기술적 차별화의 핵심 요소입니다. 이 글에서 다룬 내용을 정리하면 다음과 같습니다.
- 상관 서브쿼리 vs 일반 서브쿼리: 실행 방식의 차이를 이해하고, 상관 서브쿼리의 성능 한계를 CTE나 윈도우 함수로 극복하는 방법을 제시할 수 있어야 합니다
- EXISTS vs IN: 대규모 데이터셋에서 EXISTS가 선호되는 이유와 semi-join 최적화 원리를 설명할 수 있어야 합니다
- 조건부 집계 피벗: CASE WHEN + 집계 함수 패턴은 DBMS에 관계없이 동작하는 범용 피벗 기법입니다
- CROSSTAB: PostgreSQL 환경에서 더 체계적인 피벗을 구현하는 방법으로, DBMS 특화 지식을 보여줄 수 있습니다
- EXPLAIN ANALYZE: 실행 계획을 읽고 Seq Scan, Nested Loop, 버퍼 사용량 등 핵심 지표를 해석하는 능력은 시니어 레벨의 필수 역량입니다
- 인덱싱 전략: 복합 인덱스의 열 순서, 커버링 인덱스, 부분 인덱스의 활용 시나리오를 구체적으로 설명할 수 있어야 합니다
- 안티패턴 회피: 인덱스 열에 함수 적용 금지, NOT IN의 NULL 함정 등 실무에서 흔히 발생하는 함정을 인식하고 올바른 대안을 제시할 수 있어야 합니다
이러한 주제들을 깊이 있게 준비하면, 면접에서 단순한 쿼리 작성자가 아닌 데이터 엔지니어링 감각을 갖춘 분석가로 평가받을 수 있습니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

데이터 분석가를 위한 SQL: 윈도우 함수, CTE, 고급 쿼리 기법
SQL 윈도우 함수, CTE(공통 테이블 식), 고급 분석 쿼리를 실용적인 코드 예제와 함께 설명합니다. 데이터 분석가 면접 준비와 실무에 필수적인 기법입니다.

2026년 데이터 애널리틱스 면접 질문 TOP 25
2026년 데이터 애널리틱스 면접 대비 가이드입니다. SQL, Python, Power BI, 통계, 행동 면접에서 자주 출제되는 25개 질문을 코드 예시와 함께 상세히 해설합니다.

Pandas 3.0 완벽 가이드(2026): 새로운 API, 주요 변경사항, 면접 질문 총정리
Pandas 3.0의 Copy-on-Write, PyArrow 문자열 백엔드, pd.col() 표현식 빌더 등 핵심 변경사항을 상세히 분석합니다. 데이터 분석 엔지니어 면접에서 출제되는 핵심 질문도 함께 다룹니다.