Skip to main content

Command Palette

Search for a command to run...

Chapter 4: 매출을 파악하기 위한 데이터 추출

Published
9 min readView as Markdown
U
Keep learning toward wellbeing life with co-livers

9강. 시계열 기반으로 데이터 집계하기

시계열 데이터를 다양한 관점에서 집계하여 매출 추이를 분석하는 방법


1. 날짜별 매출 집계하기

매출 집계의 기본: 날짜별 매출과 평균 구매액 집계

SELECT
    dt,
    COUNT(*) AS purchase_count,
    SUM(purchase_amount) AS total_amount,
    AVG(purchase_amount) AS avg_amount
FROM purchase_log
GROUP BY dt
ORDER BY dt;

2. 이동평균을 사용한 날짜별 추이 보기

일별 변동이 심할 때 7일 이동평균으로 트렌드 파악

7일 이동평균 계산

SELECT
    dt,
    SUM(purchase_amount) AS total_amount,
    -- 최근 최대 7일 동안의 평균 (데이터가 7일 미만이어도 계산)
    AVG(SUM(purchase_amount))
        OVER(ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
        AS seven_day_avg,
    -- 최근 7일 데이터가 있을 때만 계산 (엄격한 7일 이동평균)
    CASE
        WHEN 7 = COUNT(*)
            OVER(ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
        THEN AVG(SUM(purchase_amount))
            OVER(ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
    END AS seven_day_avg_strict
FROM purchase_log
GROUP BY dt
ORDER BY dt;

이동평균 포인트

  • ROWS BETWEEN 6 PRECEDING AND CURRENT ROW: 현재 행 포함 최근 7일
  • 엄격한 이동평균: 7일 데이터가 모두 있을 때만 계산 (CASE + COUNT 활용)

3. 당월 매출 누계 구하기

월별로 파티션을 나누어 누적 매출 계산

기본 방법

SELECT
    dt,
    SUBSTRING(dt, 1, 7) AS year_month,
    SUM(purchase_amount) AS total_amount,
    SUM(SUM(purchase_amount))
        OVER(PARTITION BY SUBSTRING(dt, 1, 7) ORDER BY dt ROWS UNBOUNDED PRECEDING)
        AS agg_amount
FROM purchase_log
GROUP BY dt
ORDER BY dt;

CTE로 가독성 향상

WITH daily_purchase AS (
    SELECT
        dt,
        SUBSTRING(dt, 1, 4) AS year,
        SUBSTRING(dt, 6, 2) AS month,
        SUBSTRING(dt, 9, 2) AS date,
        SUM(purchase_amount) AS purchase_amount
    FROM purchase_log
    GROUP BY dt
)
SELECT
    dt,
    CONCAT(year, '-', month) AS year_month,
    purchase_amount,
    SUM(purchase_amount)
        OVER(PARTITION BY year, month ORDER BY dt ROWS UNBOUNDED PRECEDING)
        AS agg_amount
FROM daily_purchase
ORDER BY dt;

핵심 패턴

SUM(SUM(purchase_amount)) OVER(...)
  • 안쪽 SUM(): GROUP BY로 집계한 날짜별 합계
  • 바깥 SUM(): 윈도우 함수로 누적합 계산

4. 월별 매출의 작대비 구하기

JOIN 없이 CASE 문으로 연도별 매출을 피벗하여 작대비 계산

WITH daily_purchase AS (
    SELECT
        dt,
        SUBSTRING(dt, 1, 4) AS year,
        SUBSTRING(dt, 6, 2) AS month,
        SUBSTRING(dt, 9, 2) AS date,
        SUM(purchase_amount) AS purchase_amount
    FROM purchase_log
    GROUP BY dt
)
SELECT
    month,
    SUM(CASE year WHEN '2014' THEN purchase_amount END) AS amount_2014,
    SUM(CASE year WHEN '2015' THEN purchase_amount END) AS amount_2015,
    100.0
        * SUM(CASE year WHEN '2015' THEN purchase_amount END)
        / SUM(CASE year WHEN '2014' THEN purchase_amount END)
        AS rate
FROM daily_purchase
GROUP BY month
ORDER BY month;

작대비 계산 포인트

  • CASE 문으로 연도별 매출을 각각 집계
  • 비율 계산: 올해 매출 / 작년 매출 * 100

5. Z 차트로 업적의 추이 확인하기

계절 변동을 배제하고 트렌드를 분석하는 방법

Z 차트의 3가지 지표

지표설명의미
월차매출해당 월의 매출월별 실적
매출누계해당 연도 1월부터의 누적 매출연간 목표 달성도
이동년계해당 월 + 과거 11개월 매출 합계계절 변동 제거한 트렌드

Z 차트 해석

  • 매출누계가 직선: 월차매출이 일정
  • 매출누계 기울기 증가: 최근 매출 상승
  • 이동년계가 우상향: 매출 성장 트렌드

Z 차트 쿼리

WITH daily_purchase AS (
    SELECT
        dt,
        SUBSTRING(dt, 1, 4) AS year,
        SUBSTRING(dt, 6, 2) AS month,
        SUBSTRING(dt, 9, 2) AS date,
        SUM(purchase_amount) AS purchase_amount
    FROM purchase_log
    GROUP BY dt
),
monthly_purchase AS (
    SELECT
        year,
        month,
        SUM(purchase_amount) AS amount
    FROM daily_purchase
    GROUP BY year, month
),
calc_index AS (
    SELECT
        year,
        month,
        amount,
        -- 2015년 누계 매출
        SUM(CASE WHEN year = '2015' THEN amount END)
            OVER(ORDER BY year, month ROWS UNBOUNDED PRECEDING)
            AS agg_amount,
        -- 이동년계 (당월 + 과거 11개월)
        SUM(amount)
            OVER(ORDER BY year, month ROWS BETWEEN 11 PRECEDING AND CURRENT ROW)
            AS year_avg_amount
    FROM monthly_purchase
    ORDER BY year, month
)
SELECT
    CONCAT(year, '-', month) AS year_month,
    amount,
    agg_amount,
    year_avg_amount
FROM calc_index
WHERE year = '2015'
ORDER BY year_month;

6. 매출과 관련된 지표 함께 집계하기

매출의 '원인'을 파악하기 위해 구매 횟수, 구매 단가 등 주변 데이터도 함께 분석

WITH daily_purchase AS (
    SELECT
        dt,
        SUBSTRING(dt, 1, 4) AS year,
        SUBSTRING(dt, 6, 2) AS month,
        SUBSTRING(dt, 9, 2) AS date,
        SUM(purchase_amount) AS purchase_amount,
        COUNT(order_id) AS orders
    FROM purchase_log
    GROUP BY dt
),
monthly_purchase AS (
    SELECT
        year,
        month,
        SUM(orders) AS orders,
        AVG(purchase_amount) AS avg_amount,
        SUM(purchase_amount) AS monthly
    FROM daily_purchase
    GROUP BY year, month
)
SELECT
    CONCAT(year, '-', month) AS year_month,
    orders,
    avg_amount,
    monthly,
    -- 당해 연도 누계 매출
    SUM(monthly)
        OVER(PARTITION BY year ORDER BY month ROWS UNBOUNDED PRECEDING)
        AS agg_amount,
    -- 12개월 전 매출
    LAG(monthly, 12)
        OVER(ORDER BY year, month)
        AS last_year,
    -- 작대비
    100.0 * monthly / LAG(monthly, 12) OVER(ORDER BY year, month)
        AS rate
FROM monthly_purchase
ORDER BY year_month;

매출 분석 시 주요 지표

지표설명
orders주문 건수
avg_amount평균 구매 금액
monthly월별 매출
agg_amount연간 누계 매출
last_year작년 동월 매출
rate작대비 (%)

10강. 다면적인 축을 사용해 데이터 집약하기

매출의 시계열뿐만 아니라 상품의 카테고리, 가격 등을 조합해서 데이터의 특징을 추출하는 방법


1. 카테고리별 매출과 소계 계산하기

UNION ALL 방식 (기본)

소카테고리, 대카테고리, 전체 매출을 각각 집계 후 결합

WITH
    sub_category_amount AS (
        -- 소카테고리별 매출
        SELECT category, sub_category, SUM(price) AS amount
        FROM purchase_detail_log
        GROUP BY category, sub_category
    ),
    category_amount AS (
        -- 대카테고리별 매출
        SELECT category, 'all' AS sub_category, SUM(price) AS amount
        FROM purchase_detail_log
        GROUP BY category
    ),
    total_amount AS (
        -- 전체 매출
        SELECT 'all' AS category, 'all' AS sub_category, SUM(price) AS amount
        FROM purchase_detail_log
    )
SELECT * FROM sub_category_amount
UNION ALL SELECT * FROM category_amount
UNION ALL SELECT * FROM total_amount;

ROLLUP 방식 (권장)

SQL99 표준, 성능이 더 좋고 간결함

SELECT
    COALESCE(category, 'all') AS category,
    COALESCE(sub_category, 'all') AS sub_category,
    SUM(price) AS amount
FROM purchase_detail_log
GROUP BY ROLLUP(category, sub_category);

ROLLUP 동작 원리

ROLLUP(category, sub_category) 는 다음을 자동 생성:

  1. 소카테고리별 합계 (category, sub_category)
  2. 대카테고리별 합계 (category, NULL)
  3. 전체 합계 (NULL, NULL)

2. ABC 분석으로 잘 팔리는 상품 판별하기

매출 중요도에 따라 상품을 등급으로 분류하는 재고 관리 기법

ABC 등급 기준

등급구성비 누계설명
A0% ~ 70%매출 상위 (핵심 상품)
B70% ~ 90%매출 중위
C90% ~ 100%매출 하위

ABC 분석 쿼리

WITH
    monthly_sales AS (
        SELECT
            category,
            SUM(price) AS amount
        FROM purchase_detail_log
        WHERE dt BETWEEN '2017-01-01' AND '2017-01-31'
        GROUP BY category
    ),
    sales_composition_ratio AS (
        SELECT
            category,
            amount,
            -- 구성비: 항목별 매출 / 전체 매출
            100.0 * amount / SUM(amount) OVER() AS composition_ratio,
            -- 구성비 누계: 항목별 누계 매출 / 전체 매출
            100.0 * SUM(amount) OVER(ORDER BY amount DESC)
                / SUM(amount) OVER() AS cumulative_ratio
        FROM monthly_sales
    )
SELECT
    *,
    CASE
        WHEN cumulative_ratio BETWEEN 0 AND 70 THEN 'A'
        WHEN cumulative_ratio BETWEEN 70 AND 90 THEN 'B'
        WHEN cumulative_ratio BETWEEN 90 AND 100 THEN 'C'
    END AS abc_rank
FROM sales_composition_ratio
ORDER BY amount DESC;

ABC 분석 포인트

  • SUM(amount) OVER(): 전체 매출 (파티션 없음)
  • SUM(amount) OVER(ORDER BY amount DESC): 매출 높은 순으로 누적합
  • 등급 분류는 SQL보다 리포트 툴에서 하는 것이 유연함

3. 팬 차트로 상품의 매출 증가율 확인하기

기준 시점을 100%로 두고 이후의 변동률을 확인하는 그래프

팬 차트의 용도

  • 매출 금액이 작은 카테고리의 성장률도 파악 가능
  • 트렌드 변화와 성장 분야 발견에 유용

팬 차트 쿼리

WITH
    daily_category_amount AS (
        SELECT
            dt,
            category,
            SUBSTRING(dt, 1, 4) AS year,
            SUBSTRING(dt, 6, 2) AS month,
            SUM(price) AS amount
        FROM purchase_detail_log
        GROUP BY dt, category
    ),
    monthly_category_amount AS (
        SELECT
            CONCAT(year, '-', month) AS year_month,
            category,
            SUM(amount) AS amount
        FROM daily_category_amount
        GROUP BY year, month, category
    )
SELECT
    year_month,
    category,
    amount,
    -- 기준 시점(첫 달) 매출
    FIRST_VALUE(amount)
        OVER(PARTITION BY category ORDER BY year_month ROWS UNBOUNDED PRECEDING)
        AS base_amount,
    -- 기준 대비 비율
    100.0 * amount
        / FIRST_VALUE(amount)
            OVER(PARTITION BY category ORDER BY year_month ROWS UNBOUNDED PRECEDING)
        AS rate
FROM monthly_category_amount
ORDER BY year_month, category;

팬 차트 포인트

  • FIRST_VALUE(): 각 카테고리의 첫 번째(기준) 매출 추출
  • 기준 시점 선택에 명확한 근거 필요

4. 히스토그램으로 구매 가격대 집계하기

가격 분포를 시각화하기 위한 도수분포표 작성

히스토그램 작성 단계

  1. 최댓값, 최솟값, 범위 구하기
  2. 계급 수 결정 및 계급 범위 계산
  3. 각 데이터의 계급 판정
  4. 계급별 도수(개수)와 합계 집계

자동 계급 히스토그램

WITH
    stats AS (
        SELECT
            MAX(price) + 1 AS max_price,  -- +1로 최댓값도 범위 내 포함
            MIN(price) AS min_price,
            MAX(price) + 1 - MIN(price) AS range_price,
            10 AS bucket_num  -- 계급 수
        FROM purchase_detail_log
    ),
    purchase_log_with_bucket AS (
        SELECT
            price,
            min_price,
            1.0 * range_price / bucket_num AS bucket_range,
            -- 계급 판정
            FLOOR(
                1.0 * (price - min_price)
                / (1.0 * range_price / bucket_num)
            ) + 1 AS bucket
        FROM purchase_detail_log, stats
    )
SELECT
    bucket,
    min_price + bucket_range * (bucket - 1) AS lower_limit,
    min_price + bucket_range * bucket AS upper_limit,
    COUNT(price) AS num_purchase,
    SUM(price) AS total_amount
FROM purchase_log_with_bucket
GROUP BY bucket, min_price, bucket_range
ORDER BY bucket;

수동 계급 히스토그램 (권장)

리포트의 직관성을 위해 계급 범위를 수동 설정

WITH
    stats AS (
        SELECT
            100000 AS max_price,   -- 수동 설정
            0 AS min_price,        -- 수동 설정
            100000 AS range_price,
            10 AS bucket_num       -- 10000원 단위
    ),
    purchase_log_with_bucket AS (
        SELECT
            p.price,
            s.min_price,
            1.0 * s.range_price / s.bucket_num AS bucket_range,
            FLOOR(
                1.0 * (p.price - s.min_price)
                / (1.0 * s.range_price / s.bucket_num)
            ) + 1 AS bucket
        FROM purchase_detail_log p
        CROSS JOIN stats s
    )
SELECT
    bucket,
    min_price + bucket_range * (bucket - 1) AS lower_limit,
    min_price + bucket_range * bucket AS upper_limit,
    COUNT(*) AS num_purchase,
    SUM(price) AS total_amount
FROM purchase_log_with_bucket
GROUP BY bucket, min_price, bucket_range
ORDER BY bucket;

히스토그램 해석 팁

  • 산이 2개인 경우: 서로 다른 모집단이 섞여 있을 가능성
  • PostgreSQL: width_bucket() 함수로 간단히 계급 판정 가능

핵심 함수 요약

9강 함수/구문

함수/구문용도
SUM() OVER()윈도우 함수로 누적합 계산
AVG() OVER()윈도우 함수로 이동평균 계산
ROWS BETWEEN n PRECEDING AND CURRENT ROW이동 윈도우 프레임 (n+1개 행)
ROWS UNBOUNDED PRECEDING처음부터 현재 행까지 누적
PARTITION BY그룹별로 윈도우 분리
LAG(컬럼, n)n행 이전 값 참조 (작대비 계산)
SUBSTRING()문자열에서 연/월/일 추출
CONCAT()문자열 결합
WITH (CTE)중간 계산 결과에 이름 붙여 가독성 향상

주요 패턴

누적합 패턴

SUM(SUM(amount)) OVER(PARTITION BY month ORDER BY dt ROWS UNBOUNDED PRECEDING)

이동평균 패턴

AVG(SUM(amount)) OVER(ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)

작대비 패턴

100.0 * 올해매출 / LAG(올해매출, 12) OVER(ORDER BY year, month)

10강 함수/구문

함수/구문용도
ROLLUP(col1, col2)소계/총계 자동 계산 (GROUP BY 확장)
COALESCE()ROLLUP의 NULL을 'all' 등으로 치환
SUM() OVER()전체 합계 (파티션 없음)
SUM() OVER(ORDER BY col)누적합 (구성비 누계)
FIRST_VALUE()기준 시점 값 추출 (팬 차트)
FLOOR()소수점 버림 (계급 판정)
width_bucket()히스토그램 계급 판정 (PostgreSQL)

10강 주요 패턴

ROLLUP 소계/총계 패턴

GROUP BY ROLLUP(category, sub_category)

ABC 분석 구성비 누계 패턴

100.0 * SUM(amount) OVER(ORDER BY amount DESC) / SUM(amount) OVER()

팬 차트 기준 대비 비율 패턴

100.0 * amount / FIRST_VALUE(amount) OVER(PARTITION BY category ORDER BY year_month)

히스토그램 계급 판정 패턴

FLOOR((price - min_price) / bucket_range) + 1

More from this blog

카메라 영상 $\rarr$ 유속 추정(A to Z) 프로젝트 소개

프로젝트 목적 말 그대로 카메라 영상에서 하천 흐름의 유속값과 유속의 분포 데이터를 안정적으로 추정하기 위한 파이프 라인을 설계하기 위해 제로 베이스에서 시작해서 이해도를 높여보기 위한 프로젝트 수식에 대한 이해와 함께 opencv, python 기반으로 실제 구현 코드를 작성해보고 및 소스코드의 현장 적용성도 함께 검토하고자 한다. 프로젝트 수행 방법 기존에 사용하고 있는 태블릿의 코틀린 코드와 UI를 최대한 그대로 사용하고자 하며, 1...

May 1, 20265 min read2

print(obj.__secret) # 오류 발생! (직접 접근 불가)

객체 지향 프로그래밍 클래스와 인스턴스 클래스 메서드들은 중첩된 def이며, 특수한 첫번째 인자는 묵시적으로 대상 인스턴스를 받는다. 네, 정확합니다! self를 설명하는 내용이 맞습니다. 이 문장을 조금 더 자세히 풀어드리면 다음과 같습니다. 1. "클래스 메서드들은 중첩된 def이며" 파이썬에서 클래스 내부의 함수(메서드)는 클래스라는 '객체 내부'에 정의되어 있습니다. def는 함수를 만드는 키워드인데, 클래스라는 범위(scope) ...

Apr 28, 20264 min read2

9장 함수(Python)

9.3 인수와 매개변수 자바와 동일하게? 함수 외부에서는 인수라고 하지만 내부에서는 매개변수라고 한다. 함수로 전달한 값을 인수라고 부른다. 인수의 값은 함수 내에서 해당하는 매개변수에 복사된다. 함수가 명시적으로 return 을 호출하지 않으면, 호출자는 반환값으로 None을 얻는다. 9.3.1 유용한 None None은 아무것도 없다는 것을 뜻하는 파이썬의 특별한 값이다. 빠뜨린 빈 값을 구분하기 위해 None을 사용한다. 불리언 값 ...

Apr 20, 202613 min read

[ITSantiago]의 개발 블로그 | TIL & Tech Log

36 posts