Post

Oracle SQL: 윈도우 함수(Window Functions) 마스터하기

#study #sql #oracle #database #automation

실무에서 왜 중요한가?

윈도우 함수는 대규모 데이터 분석에서 필수적입니다. 복잡한 자체 조인(self-join)이나 서브쿼리 없이 행 간 비교, 누적값, 순위 계산을 한 번에 처리할 수 있습니다.

특히 매출 분석, 사용자 행동 추적, 시계열 데이터 처리에서 성능 차이가 극적입니다.

핵심 개념

  • OVER 절의 역할 윈도우 함수의 작동 범위를 정의합니다. PARTITION BY로 그룹을 나누고 ORDER BY로 정렬 순서를 지정합니다.

  • ROW_NUMBER vs RANK vs DENSE_RANK ROW_NUMBER는 중복 없이 순번을 매기고, RANK는 동점에 같은 순위를 부여하며 건너뜁니다. DENSE_RANK는 동점 후에도 순위를 건너뛰지 않습니다.

  • LAG와 LEAD 함수 이전 행의 값(LAG)이나 다음 행의 값(LEAD)에 접근합니다. 전월 대비 증감률 계산에 유용합니다.

  • SUM OVER와 누적값 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW를 사용하여 누적합을 계산합니다.

  • FIRST_VALUE와 LAST_VALUE 파티션 내 첫 번째 또는 마지막 값을 가져옵니다. 기준값 대비 비교 분석에 활용됩니다.

실습 예제

예제 1: 월별 매출과 누적 매출

SELECT
    EXTRACT(YEAR_MONTH FROM order_date) AS month,
    SUM(amount) AS monthly_sales,
    SUM(SUM(amount)) OVER (
        ORDER BY EXTRACT(YEAR_MONTH FROM order_date)
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS cumulative_sales
FROM orders
GROUP BY EXTRACT(YEAR_MONTH FROM order_date)
ORDER BY month;

이 쿼리는 각 월의 매출과 함께 누적 매출을 한 번에 계산합니다.

예제 2: 부서별 급여 순위와 전월 대비 변화

SELECT
    employee_id,
    department_id,
    salary,
    RANK() OVER (
        PARTITION BY department_id
        ORDER BY salary DESC
    ) AS dept_salary_rank,
    LAG(salary, 1) OVER (
        PARTITION BY department_id
        ORDER BY hire_date
    ) AS previous_salary,
    salary - LAG(salary, 1) OVER (
        PARTITION BY department_id
        ORDER BY hire_date
    ) AS salary_change
FROM employees
ORDER BY department_id, dept_salary_rank;

부서 내 급여 순위와 입사 순서 기준 이전 직원의 급여를 함께 조회합니다.

예제 3: 고객별 첫 주문 대비 현재 주문 비교

SELECT
    customer_id,
    order_date,
    amount,
    FIRST_VALUE(amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
    ) AS first_order_amount,
    ROUND(
        (amount - FIRST_VALUE(amount) OVER (
            PARTITION BY customer_id
            ORDER BY order_date
        )) / FIRST_VALUE(amount) OVER (
            PARTITION BY customer_id
            ORDER BY order_date
        ) * 100, 2
    ) AS growth_percent
FROM orders
ORDER BY customer_id, order_date;

각 고객의 첫 주문 금액 대비 현재 주문의 성장률을 계산합니다.

자주 하는 실수

  • PARTITION BY 없이 OVER() 사용 전체 결과 집합을 하나의 윈도우로 취급합니다. 부서별 순위가 필요하면 반드시 PARTITION BY department_id를 추가하세요.

  • ORDER BY 생략 윈도우 함수의 동작이 예측 불가능해집니다. RANK나 ROW_NUMBER는 ORDER BY가 필수입니다.

  • ROWS 범위 설정 오류 ROWS BETWEEN을 명시하지 않으면 기본값이 UNBOUNDED PRECEDING AND CURRENT ROW입니다. 전체 누적이 필요하면 UNBOUNDED FOLLOWING을 추가하세요.

  • GROUP BY와 윈도우 함수 혼용 시 주의 집계 함수와 윈도우 함수를 함께 사용할 때 SELECT 절의 모든 열이 GROUP BY에 포함되어야 합니다.

  • 성능 고려 부족 큰 테이블에서 PARTITION BY 열에 인덱스가 없으면 성능이 급격히 저하됩니다. 실행 계획을 확인하세요.

오늘의 실습 체크리스트

  • ROW_NUMBER, RANK, DENSE_RANK의 차이를 직접 실행해서 확인
  • 자신의 프로젝트 데이터로 LAG/LEAD 함수 작성
  • 누적합 쿼리를 작성하고 결과 검증
  • FIRST_VALUE와 LAST_VALUE를 조합한 쿼리 작성
  • 실행 계획(EXPLAIN PLAN)을 확인하고 인덱스 전략 검토
  • 복잡한 윈도우 함수 쿼리를 주석과 함께 문서화

댓글