본문 바로가기

프로그래밍/개발자의 SQL 연구소

[MSSQL] 윈도우 함수: SUM OVER PARTITION BY로 분석 쿼리 레벨업하기

문제 상황: GROUP BY의 한계를 넘어, 전체와 부분을 동시에 보고 싶을 때

"각 상품의 개별 매출액을 보면서, 동시에 그 상품이 속한 카테고리의 총매출액도 함께 보고 싶습니다."
"일별 매출액 리스트에서, 각 날짜마다 '그날까지의 누적 매출액'을 추가로 보여주세요."

 

이러한 요구사항에 GROUP BY를 사용하면 어떻게 될까요? GROUP BY는 카테고리별 총매출액을 계산하기 위해 개별 상품의 행들을 모두 하나의 행으로 합쳐버립니다. 결국, 우리는 '부분'의 정보(개별 상품 매출)를 잃어버리게 되죠.

 

과거에는 이 문제를 해결하기 위해 서브쿼리나 복잡한 셀프 조인(Self Join)을 사용하여 성능을 희생해야 했습니다. 하지만 이제는 '윈도우 함수'를 사용해 이 모든 것을 단 하나의 깔끔하고 효율적인 쿼리로 해결할 수 있습니다. SUM() OVER (PARTITION BY ...)는 그중 가장 기본이 되면서도 강력한 함수입니다.

 


 

 

1부: 윈도우 함수란? GROUP BY와의 근본적인 차이

윈도우 함수를 이해하는 가장 좋은 방법은 GROUP BY와의 차이점을 아는 것입니다.

  • GROUP BY (집계): 여러 행을 하나의 요약된 행으로 압축합니다. (예: 과일 바구니에서 과일 종류별 개수를 센다 → 사과: 5개, 바나나: 3개)
  • 윈도우 함수 (분석): 여러 행을 기반으로 계산하지만, 원래의 행들을 그대로 유지한 채 각 행에 계산된 값을 추가합니다. (예: 과일 바구니의 각 과일에 '같은 종류 과일 총 개수'라는 스티커를 붙인다 → 사과 1(5개), 사과 2(5개)..., 바나나 1(3개)...)

이처럼 윈도우 함수는 '전체(또는 그룹)의 맥락 속에서 개별 행을 분석'할 수 있게 해주는 강력한 도구입니다. OVER() 키워드가 바로 윈도우 함수의 시작을 알리는 신호입니다.

 

 


 

2부: 그룹별 합계 - SUM() OVER (PARTITION BY...)

가장 대표적인 활용법으로, GROUP BY처럼 그룹을 나누어 합계를 구하지만 원본 행은 그대로 보존하는 방식입니다.

기본 문법

SUM(합계를 구할 컬럼) OVER (PARTITION BY 그룹으로 묶을 컬럼)

  • SUM(column): 일반적인 SUM 함수와 동일합니다.
  • OVER (...): 이 함수를 윈도우 함수로 사용하겠다는 선언입니다.
  • PARTITION BY group_column: OVER 절의 핵심으로, 어떤 컬럼을 기준으로 그룹(파티션)을 나눌지 지정합니다. GROUP BY의 그룹 지정과 역할이 같습니다.

 

실무 예제: 상품 매출과 카테고리 내 매출 비중 동시 계산

온라인 쇼핑몰의 상품별 매출 데이터에서, 각 상품의 개별 매출액과 함께 해당 상품이 속한 카테고리의 총매출액, 그리고 카테고리 내에서 이 상품이 차지하는 매출 비중을 계산해 보겠습니다.

 

1. 테스트용 매출 테이블 생성
CREATE TABLE ProductSales (
    category NVARCHAR(50),
    product_name NVARCHAR(50),
    sale_amount INT
);

INSERT INTO ProductSales VALUES
(N'가전', N'4K TV', 3000000), (N'가전', N'공기청정기', 800000), (N'가전', N'무선청소기', 1200000),
(N'주방용품', N'에어프라이어', 200000), (N'주방용품', N'전기포트', 50000),
(N'가구', N'1인용 소파', 400000), (N'가구', N'책상', 250000), (N'가구', N'의자', 150000);
 
2. 윈도우 함수를 사용한 분석 쿼리
SELECT
    category,
    product_name,
    sale_amount,
    -- 1. 카테고리(category)를 기준으로 그룹을 나눠(PARTITION BY) 매출액(sale_amount)의 합계를 구함
    SUM(sale_amount) OVER (PARTITION BY category) AS category_total_sales,

    -- 2. 위에서 구한 카테고리 총매출액을 이용해 상품별 매출 비중을 계산
    (CAST(sale_amount AS FLOAT) / SUM(sale_amount) OVER (PARTITION BY category)) * 100 AS sales_percentage
FROM
    ProductSales;

 

실행 결과

GROUP BY를 사용했다면 불가능했을, 개별 행의 정보와 그룹 전체의 집계 정보가 아름답게 공존하는 결과가 나옵니다.

category product_name sale_amount category_total_sales sales_percentage
가전 4K TV 3000000 5000000 60.0
가전 공기청정기 800000 5000000 16.0
가전 무선청소기 1200000 5000000 24.0
가구 1인용 소파 400000 800000 50.0
가구 책상 250000 800000 31.25
가구 의자 150000 800000 18.75
주방용품 에어프라이어 200000 250000 80.0
주방용품 전기포트 50000 250000 20.0

 


 

3부: 누적 합계 (Running Total) - SUM() OVER (ORDER BY...)

윈도우 함수는 그룹을 나누는 것뿐만 아니라, 특정 순서에 따라 값을 계속 더해가는 '누적 합계'를 계산하는 데에도 탁월한 성능을 보입니다.

 

기본 문법

SUM(합계를 구할 컬럼) OVER (ORDER BY 순서를 정할 컬럼)

  • ORDER BY: OVER 절 안에 ORDER BY를 사용하면, 지정된 컬럼의 순서에 따라 처음부터 현재 행까지의 값을 누적하여 계산합니다.

 

실무 예제: 일별 누적 매출액(Running Total) 계산

-- 1. 테스트용 일별 매출 테이블 생성
CREATE TABLE DailySales (
    sale_date DATE,
    daily_revenue INT
);
INSERT INTO DailySales VALUES
('2025-08-01', 100), ('2025-08-02', 120), ('2025-08-03', 80),
('2025-08-04', 150), ('2025-08-05', 200);

-- 2. 날짜(sale_date) 순서대로 누적 매출(cumulative_revenue) 계산
SELECT
    sale_date,
    daily_revenue,
    SUM(daily_revenue) OVER (ORDER BY sale_date) AS cumulative_revenue
FROM
    DailySales;

 

실행 결과

sale_date daily_revenue cumulative_revenue
2025-08-01 100 100
2025-08-02 120 220 (100+120)
2025-08-03 80 300 (220+80)
2025-08-04 150 450 (300+150)
2025-08-05 200 650 (450+200)

 


 

4부: 궁극의 조합 - PARTITION BY와 ORDER BY

두 기능을 합치면 '그룹별 누적 합계'라는, 분석의 끝판왕 격인 계산이 가능해집니다.

 

실무 예제: 카테고리별 일일 누적 매출액 계산

-- PARTITION BY와 ORDER BY를 함께 사용하여
-- 카테고리별로(PARTITION BY) 날짜순(ORDER BY) 누적 합계를 계산
-- 누적 합계는 카테고리가 바뀔 때마다 다시 1부터 시작됨
SELECT
    category,
    sale_date,
    sale_amount,
    SUM(sale_amount) OVER (PARTITION BY category ORDER BY sale_date) AS cumulative_sales_by_category
FROM
    CategoryDailySales; -- (예제 테이블 생략)

 

최종 요약

  • GROUP BY는 행을 압축하고, 윈도우 함수는 행을 유지한 채 계산 값을 추가합니다.
  • 그룹 내 전체 합계를 각 행에 표시하려면 SUM() OVER (PARTITION BY...)를 사용하세요. (예: 카테고리별 총매출액)
  • 시간 순서 등에 따른 누적 합계를 계산하려면 SUM() OVER (ORDER BY...)를 사용하세요. (예: 일별 누적 매출)
  • SUM 외에 AVG, COUNT, MAX, MIN 등 다른 집계 함수도 동일한 OVER() 구문과 함께 사용할 수 있습니다.

 

Recent Posts
Popular Posts