Learning
레슨 9 / 10·3개 토픽

윈도우 함수와 뷰

순위 함수

윈도우 함수는 현재 행과 관련된 행 집합(윈도우)에 대해 계산을 수행합니다. GROUP BY와 달리 행을 축소하지 않고 각 행에 결과를 추가합니다. OVER() 절로 윈도우를 정의하며, PARTITION BYORDER BY로 범위와 정렬을 지정합니다.

sql
-- ROW_NUMBER: 연속 순번 (중복 없음)
SELECT
    name, department, salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num
FROM employees;

-- RANK: 동순위 허용, 다음 순위 건너뜀 (1, 2, 2, 4)
SELECT
    name, department, salary,
    RANK() OVER (ORDER BY salary DESC) AS rank_num
FROM employees;

-- DENSE_RANK: 동순위 허용, 다음 순위 연속 (1, 2, 2, 3)
SELECT
    name, department, salary,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank_num
FROM employees;

-- PARTITION BY: 부서별 순위
SELECT
    name, department, salary,
    RANK() OVER (
        PARTITION BY department
        ORDER BY salary DESC
    ) AS dept_rank
FROM employees;

-- 부서별 TOP 3 추출
WITH ranked AS (
    SELECT
        name, department, salary,
        ROW_NUMBER() OVER (
            PARTITION BY department ORDER BY salary DESC
        ) AS rn
    FROM employees
)
SELECT * FROM ranked WHERE rn <= 3;

LAG, LEAD, 누적 집계

sql
-- LAG: 이전 행 값 참조 (전월 대비)
SELECT
    month,
    revenue,
    LAG(revenue, 1) OVER (ORDER BY month) AS prev_month,
    revenue - LAG(revenue, 1) OVER (ORDER BY month) AS diff,
    ROUND(
        (revenue - LAG(revenue, 1) OVER (ORDER BY month))
        / LAG(revenue, 1) OVER (ORDER BY month) * 100, 1
    ) AS growth_pct
FROM monthly_sales;

-- LEAD: 다음 행 값 참조
SELECT
    order_date,
    customer_id,
    LEAD(order_date, 1) OVER (
        PARTITION BY customer_id ORDER BY order_date
    ) AS next_order_date,
    DATEDIFF(
        LEAD(order_date, 1) OVER (
            PARTITION BY customer_id ORDER BY order_date
        ),
        order_date
    ) AS days_until_next
FROM orders;

-- SUM() OVER: 누적 합계
SELECT
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date) AS running_total,
    AVG(amount) OVER (
        ORDER BY order_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM daily_sales;

-- COUNT, AVG OVER with PARTITION
SELECT
    name, department, salary,
    COUNT(*) OVER (PARTITION BY department) AS dept_size,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg,
    salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg
FROM employees;

VIEW (뷰)

뷰는 저장된 쿼리를 가상 테이블처럼 사용할 수 있게 해줍니다. 복잡한 쿼리를 캡슐화하여 재사용성을 높이고, 보안을 위해 특정 열만 노출하는 데 활용합니다. 단순 뷰는 INSERT/UPDATE/DELETE가 가능합니다.

sql
-- 뷰 생성: 고객 주문 요약
CREATE VIEW customer_order_summary AS
SELECT
    u.id AS customer_id,
    u.name,
    u.email,
    COUNT(o.id) AS total_orders,
    COALESCE(SUM(o.total_amount), 0) AS total_spent,
    MAX(o.ordered_at) AS last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email;

-- 뷰 사용 (일반 테이블처럼)
SELECT * FROM customer_order_summary
WHERE total_spent > 100000
ORDER BY total_spent DESC;

-- 뷰 수정
CREATE OR REPLACE VIEW customer_order_summary AS
SELECT
    u.id AS customer_id,
    u.name,
    u.email,
    COUNT(o.id) AS total_orders,
    COALESCE(SUM(o.total_amount), 0) AS total_spent,
    MAX(o.ordered_at) AS last_order_date,
    DATEDIFF(CURDATE(), MAX(o.ordered_at)) AS days_since_last
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email;

-- 갱신 가능한 단순 뷰
CREATE VIEW active_products AS
SELECT id, name, price, stock
FROM products
WHERE is_active = TRUE;

-- 뷰를 통한 UPDATE (단순 뷰는 가능)
UPDATE active_products
SET price = price * 0.9
WHERE stock > 100;

-- 뷰 삭제
DROP VIEW IF EXISTS customer_order_summary;
  • ROW_NUMBER() — 연속 순번 부여 (중복 없음)
  • RANK() — 동순위 허용, 다음 순위 건너뜀 (1,2,2,4)
  • DENSE_RANK() — 동순위 허용, 다음 순위 연속 (1,2,2,3)
  • LAG(col, n) — n개 이전 행의 값 참조
  • LEAD(col, n) — n개 다음 행의 값 참조
  • SUM() OVER() — 윈도우 범위 내 누적/이동 집계
  • PARTITION BY — 윈도우를 그룹으로 분할
  • CREATE VIEW — 저장된 쿼리를 가상 테이블로 정의
💡

윈도우 함수는 GROUP BY 없이도 집계 결과를 각 행에 표시할 수 있어 매우 강력합니다. 실무에서 "부서별 상위 N명", "전월 대비 증감", "누적 합계" 같은 분석 쿼리에 필수적으로 사용됩니다. 뷰는 JOIN이나 집계가 포함되면 갱신이 불가능하므로 주의하세요.