콘텐츠로 이동

재고 관리

사용 테이블

products — 상품 (이름, 가격, 재고, 브랜드)

categories — 카테고리 (부모-자식 계층)

suppliers — 공급업체 (업체명, 연락처)

inventory_transactions — 재고 입출고 (유형, 수량)

order_items — 주문 상세 (수량, 단가)

orders — 주문 (상태, 금액, 일시)

학습 범위

Conditional Aggregation, Window Functions, ABC Analysis, Pareto, CTE

1. 현재 재고 현황과 재고 부족 상품

물류팀이 현재 재고가 부족한 상품 목록을 요청했습니다. 재고 수량이 10개 이하인 활성 상품을 찾아, 상품명, 카테고리, 현재 재고, 가격, 공급업체를 표시하세요. 재고가 0인 상품은 "품절"로 표시합니다.

힌트 1: - products.stock_qty로 현재 재고 확인 - products -> categories, products -> suppliers JOIN - CASE로 "품절" 표시 - products.is_active = 1로 활성 상품만

정답
SELECT
    p.name          AS product_name,
    cat.name        AS category,
    s.company_name  AS supplier,
    p.stock_qty,
    CASE
        WHEN p.stock_qty = 0 THEN '품절'
        WHEN p.stock_qty <= 5 THEN '긴급'
        ELSE '부족'
    END AS stock_status,
    p.price
FROM products AS p
INNER JOIN categories AS cat ON p.category_id = cat.id
INNER JOIN suppliers  AS s   ON p.supplier_id = s.id
WHERE p.is_active = 1
  AND p.stock_qty <= 10
ORDER BY p.stock_qty ASC, p.price DESC;

실행 결과 (3행)

product_name category supplier stock_qty stock_status price
Arctic Freezer 36 A-RGB 화이트 공랭 아틱코리아 0 품절 23,000.00
삼성 SPA-KFG0BUB 멤브레인 삼성전자 공식 유통 4 긴급 30,700.00
로지텍 G502 HERO 실버 유선 로지텍코리아 8 부족 71,100.00

2. 재고 입출고 흐름 분석

지난 1년(2025년)간 상품별 입고량과 출고량을 비교하여 재고 흐름을 파악하세요. 순변동이 마이너스인 상품만 보여주세요.

힌트 1: - inventory_transactionstype 칼럼: 'inbound' (입고), 'outbound' (출고) - 조건부 집계: SUM(CASE WHEN type='inbound' THEN quantity ELSE 0 END) - products와 JOIN하여 현재 재고 포함

정답
SELECT
    p.name          AS product_name,
    p.stock_qty AS current_stock,
    SUM(CASE WHEN it.type = 'inbound'  THEN it.quantity ELSE 0 END) AS total_in,
    SUM(CASE WHEN it.type = 'outbound' THEN it.quantity ELSE 0 END) AS total_out,
    SUM(CASE WHEN it.type = 'inbound'  THEN it.quantity ELSE 0 END)
  - SUM(CASE WHEN it.type = 'outbound' THEN it.quantity ELSE 0 END) AS net_change
FROM inventory_transactions AS it
INNER JOIN products AS p ON it.product_id = p.id
WHERE it.created_at LIKE '2025%'
GROUP BY p.id, p.name, p.stock_qty
HAVING net_change < 0
ORDER BY net_change ASC;

3. 상품 ABC 분석 (파레토 80/20)

매출 기여도에 따라 상품을 A/B/C 등급으로 분류하세요. 전체 매출의 80%를 차지하는 상품을 A등급, 다음 15%를 B등급, 나머지를 C등급으로 분류합니다.

힌트 1: - 상품별 매출을 계산한 후 내림차순 정렬 - 누적 비율 계산에 SUM() OVER (ORDER BY ...) - 전체 매출 대비 누적 비율로 A/B/C 분류

정답
WITH product_revenue AS (
    SELECT
        p.id,
        p.name,
        cat.name AS category,
        ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue
    FROM order_items AS oi
    INNER JOIN orders   AS o ON oi.order_id   = o.id
    INNER JOIN products AS p ON oi.product_id = p.id
    INNER JOIN categories AS cat ON p.category_id = cat.id
    WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY p.id, p.name, cat.name
),
ranked AS (
    SELECT *,
        SUM(revenue) OVER () AS total_revenue,
        SUM(revenue) OVER (ORDER BY revenue DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS cumulative_revenue
    FROM product_revenue
)
SELECT
    name AS product_name,
    category,
    revenue,
    ROUND(100.0 * revenue / total_revenue, 2) AS pct_of_total,
    ROUND(100.0 * cumulative_revenue / total_revenue, 2) AS cumulative_pct,
    CASE
        WHEN 100.0 * cumulative_revenue / total_revenue <= 80 THEN 'A'
        WHEN 100.0 * cumulative_revenue / total_revenue <= 95 THEN 'B'
        ELSE 'C'
    END AS abc_class
FROM ranked
ORDER BY revenue DESC
LIMIT 30;

실행 결과 (총 30행 중 상위 7행)

product_name category revenue pct_of_total cumulative_pct abc_class
Razer Blade 18 블랙 게이밍 노트북 1,079,568,800.00 3.08 3.08 A
Razer Blade 16 실버 게이밍 노트북 859,072,800.00 2.45 5.52 A
ASUS Dual RTX 4060 Ti 블랙 NVIDIA 858,610,800.00 2.45 7.97 A
Razer Blade 18 블랙 게이밍 노트북 839,487,500.00 2.39 10.36 A
Razer Blade 18 화이트 게이밍 노트북 665,604,800.00 1.90 12.26 A
MSI GeForce RTX 4070 Ti Super GAMING X NVIDIA 647,024,000.00 1.84 14.11 A
MSI Radeon RX 7900 XTX GAMING X 화이트 AMD 585,793,600.00 1.67 15.77 A

4. 공급업체 성과 평가

각 공급업체의 공급 상품 수, 총 매출, 반품률, 평균 고객 평점을 산출하세요. 반품률이 높은 공급업체를 식별합니다.

힌트 1: - suppliers -> products -> order_items -> orders 순으로 JOIN - 반품률: 반품 수 / 전체 판매 수 - reviews는 LEFT JOIN으로 평점 포함

정답
WITH supplier_sales AS (
    SELECT
        s.id AS supplier_id,
        s.company_name AS supplier_name,
        COUNT(DISTINCT p.id) AS product_count,
        SUM(oi.quantity)     AS units_sold,
        ROUND(SUM(oi.quantity * oi.unit_price), 2) AS total_revenue
    FROM suppliers AS s
    INNER JOIN products   AS p  ON s.id = p.supplier_id
    INNER JOIN order_items AS oi ON p.id = oi.product_id
    INNER JOIN orders     AS o  ON oi.order_id = o.id
    WHERE o.status NOT IN ('cancelled')
    GROUP BY s.id, s.company_name
),
supplier_returns AS (
    SELECT
        s.id AS supplier_id,
        COUNT(ret.id) AS return_count
    FROM suppliers AS s
    INNER JOIN products   AS p   ON s.id = p.supplier_id
    INNER JOIN order_items AS oi ON p.id = oi.product_id
    INNER JOIN orders     AS o2  ON oi.order_id = o2.id
    INNER JOIN returns    AS ret ON ret.order_id = o2.id
    GROUP BY s.id
),
supplier_reviews AS (
    SELECT
        s.id AS supplier_id,
        ROUND(AVG(r.rating), 2) AS avg_rating,
        COUNT(r.id) AS review_count
    FROM suppliers AS s
    INNER JOIN products AS p ON s.id = p.supplier_id
    INNER JOIN reviews  AS r ON p.id = r.product_id
    GROUP BY s.id
)
SELECT
    ss.supplier_name,
    ss.product_count,
    ss.units_sold,
    ss.total_revenue,
    COALESCE(sr.return_count, 0) AS return_count,
    ROUND(100.0 * COALESCE(sr.return_count, 0) / ss.units_sold, 2) AS return_rate_pct,
    COALESCE(srev.avg_rating, 0) AS avg_rating,
    srev.review_count
FROM supplier_sales AS ss
LEFT JOIN supplier_returns AS sr   ON ss.supplier_id = sr.supplier_id
LEFT JOIN supplier_reviews AS srev ON ss.supplier_id = srev.supplier_id
ORDER BY return_rate_pct DESC;

실행 결과 (총 45행 중 상위 7행)

supplier_name product_count units_sold total_revenue return_count return_rate_pct avg_rating review_count
브라더코리아 1 21 4,254,600.00 2 9.52 4.00 1
한성컴퓨터 4 295 311,361,100.00 19 6.44 3.75 32
델코리아 3 566 528,065,100.00 27 4.77 4.21 63
레노버코리아 5 577 996,843,200.00 25 4.33 3.58 59
레이저코리아 9 2958 4,004,408,800.00 120 4.06 3.89 272
캐논코리아 5 892 292,499,200.00 36 4.04 3.94 97
주연테크 4 255 357,712,900.00 10 3.92 4.00 25

5. 월별 재고 회전율 추이

2024년 월별 재고 회전율을 보여주세요. 재고 회전율 = 해당 월 출고 수량 합계 / 월말 누적 재고.

힌트 1: - 월별 출고량: inventory_transactions에서 type='outbound' 필터 - 윈도우 함수로 누적 재고 계산

정답
WITH monthly_flow AS (
    SELECT
        SUBSTR(created_at, 1, 7) AS year_month,
        SUM(CASE WHEN type = 'outbound' THEN quantity ELSE 0 END) AS total_out,
        SUM(CASE WHEN type = 'inbound'  THEN quantity ELSE 0 END) AS total_in
    FROM inventory_transactions
    WHERE created_at LIKE '2024%'
    GROUP BY SUBSTR(created_at, 1, 7)
),
with_cumulative AS (
    SELECT
        year_month,
        total_out,
        total_in,
        SUM(total_in - total_out) OVER (
            ORDER BY year_month
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS cumulative_net_stock
    FROM monthly_flow
)
SELECT
    year_month,
    total_in,
    total_out,
    cumulative_net_stock,
    CASE
        WHEN cumulative_net_stock > 0
        THEN ROUND(1.0 * total_out / cumulative_net_stock, 2)
        ELSE NULL
    END AS turnover_ratio
FROM with_cumulative
ORDER BY year_month;

실행 결과 (총 12행 중 상위 7행)

year_month total_in total_out cumulative_net_stock turnover_ratio
2024-01 1996 -199 2195 -0.09
2024-02 935 -163 3293 -0.05
2024-03 3500 -248 7041 -0.04
2024-04 3513 -122 10,676 -0.01
2024-05 3394 -253 14,323 -0.02
2024-06 2671 -171 17,165 -0.01
2024-07 2899 -138 20,202 -0.01

6. 보너스: 카테고리별 ABC 분석과 재고 부족 비율

ABC 분석을 카테고리 단위로 수행하세요. 각 카테고리의 ABC 등급과 함께, 상품 수, 재고 부족(stock_qty <= 10) 상품 비율을 표시합니다.

힌트 1: - 카테고리별 매출과 재고 부족 비율을 한 CTE에서 계산 - 누적 매출로 ABC 등급 분류

정답
WITH category_revenue AS (
    SELECT
        cat.id AS category_id,
        cat.name AS category,
        COUNT(DISTINCT p.id) AS product_count,
        ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue,
        ROUND(100.0 * SUM(CASE WHEN p.stock_qty <= 10 THEN 1 ELSE 0 END)
                    / COUNT(DISTINCT p.id), 1) AS low_stock_pct
    FROM categories AS cat
    INNER JOIN products    AS p  ON cat.id = p.category_id
    INNER JOIN order_items AS oi ON p.id   = oi.product_id
    INNER JOIN orders      AS o  ON oi.order_id = o.id
    WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY cat.id, cat.name
),
ranked AS (
    SELECT *,
        SUM(revenue) OVER () AS total_revenue,
        SUM(revenue) OVER (ORDER BY revenue DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS cumulative
    FROM category_revenue
)
SELECT
    category,
    product_count,
    revenue,
    ROUND(100.0 * cumulative / total_revenue, 1) AS cumulative_pct,
    CASE
        WHEN 100.0 * cumulative / total_revenue <= 80 THEN 'A'
        WHEN 100.0 * cumulative / total_revenue <= 95 THEN 'B'
        ELSE 'C'
    END AS abc_class,
    low_stock_pct
FROM ranked
ORDER BY revenue DESC;

실행 결과 (총 40행 중 상위 7행)

category product_count revenue cumulative_pct abc_class low_stock_pct
게이밍 노트북 9 4,684,236,900.00 13.30 A 0.0
NVIDIA 7 2,695,883,800.00 21.00 A 0.0
게이밍 모니터 10 2,645,570,200.00 28.60 A 0.0
AMD 8 2,419,754,300.00 35.50 A 0.0
일반 노트북 10 2,324,971,800.00 42.10 A 0.0
2in1 9 1,852,395,500.00 47.40 A 0.0
Intel 소켓 13 1,506,501,500.00 51.70 A 0.0