SQL에서 누적합, 누적백분율 활용하기

게시일 : 2019년 01월 29일    
# PostgreSQL # MySQL # WINDOW FUNCTION # ABC analysis

SQL에서 누적합, 누적백분율을 활용하여 분석에 활용한다.

데이터는 Kaggle의 Big Mart Sales를 활용하였다. (일부 컬럼만)

이 데이터는 16가지 종류로 분류되는 물건들의 판매량을 포함하고 있다.

데이터 예시

item_identifier item_type sales
FDA15 Dairy 3735.138
DRC01 Soft Drinks 443.4228
FDN15 Meat 2097.27
FDX07 Fruits and Vegetables 732.38
NCD19 Household 994.7052

누적합, 누적백분율 ( WINDOW FUNCTION 활용 )

SUM 함수는 OVER에 아무 조건을 걸지 않으면 총합을 의미하고,

ORDER BY 조건을 걸면 정렬된 순서대로 누적합을 계산한다. 백분율 계산식만 간단히 세우면 누적백분율도 계산할 수 있다.

WITH total_sales AS(
    SELECT Item_type, SUM(Item_outlet_sales) as sales 
    FROM martsales
    GROUP BY Item_type ORDER BY sales
)
SELECT item_type, sales, 
       ROUND(100.0*sales / SUM(sales) OVER(), 2) as ratio,
       ROUND(100.0*SUM(sales) OVER(ORDER BY sales DESC)
             / SUM(sales) OVER(), 2) as cum_ratio
FROM total_sales;
-- PostgreSQL의 경우, ROUND(100.0*sales / SUM(sales) OVER()::numeric,2)
item_type sales ratio cum_ratio
Fruits and Vegetables 2820059.8168 15.17 15.17
Snack Foods 2732786.0870 14.70 29.87
Household 2055493.7132 11.06 40.92
Frozen Foods 1825734.7886 9.82 50.75
Dairy 1522594.0512 8.19 58.93
Canned 1444151.4926 7.77 66.70
Baking Goods 1265525.3422 6.81 73.51
Health and Hygiene 1045200.1378 5.62 79.13
Meat 917565.6120 4.94 84.07
Soft Drinks 892897.7220 4.80 88.87
Breads 553237.1888 2.98 91.85
Hard Drinks 457793.4272 2.46 94.31
Starchy Foods 351401.2504 1.89 96.20
Others 325517.6096 1.75 97.95
Breakfast 232298.9516 1.25 99.20
Seafood 148868.2194 0.80 100.00

참고:

WITH와 WINDOW FUNCTION이 없는 MySQL 5.x 버전에서는 variable을 활용하여 누적합을 다음과 같이 구할 수 있다.

CROSS JOIN으로 0으로 초기화된 csum이라는 컬럼을 먼저 만들고 누적합을 계산한다. variable 자체를 테이블에 update를 통해 넣어서 자동으로 계산하는 방법도 있다.

SELECT a.Item_type, a.sales, (@csum := @csum + a.sales) as cum_sum
FROM (SELECT Item_type, SUM(Item_outlet_sales) as sales 
	  FROM martsales GROUP BY Item_type ORDER BY sales DESC) a, (SELECT @csum:=0) b;

활용 - ABC 분석

누적백분율을 활용하면 ABC 분석과 같은 경우에 활용할 수 있다.

상위 매출 기준으로 70%는 A등급, 25%는 B등급, 5%는 C등급으로 정의해보았다.

WITH total_sales AS(
	SELECT Item_type, SUM(Item_outlet_sales) as sales 
	FROM martsales
	GROUP BY Item_type ORDER BY sales),
total_ratio AS (
    SELECT item_type, sales, 
       ROUND(100.0*SUM(sales) OVER(ORDER BY sales DESC) 
             / SUM(sales) OVER()::numeric,2) as cum_ratio
    FROM total_sales
)
SELECT item_type, cum_ratio, 
	CASE WHEN cum_ratio < 70 THEN 'A' 
	     WHEN cum_ratio < 95 THEN 'B' ELSE 'C' END as item_grade 
FROM total_ratio;
item_type sales cum_ratio item_grade
Fruits and Vegetables 2820059.8168 15.17 A
Snack Foods 2732786.0870 29.87 A
Household 2055493.7132 40.92 A
Frozen Foods 1825734.7886 50.75 A
Dairy 1522594.0512 58.93 A
Canned 1444151.4926 66.70 A
Baking Goods 1265525.3422 73.51 B
Health and Hygiene 1045200.1378 79.13 B
Meat 917565.6120 84.07 B
Soft Drinks 892897.7220 88.87 B
Breads 553237.1888 91.85 B
Hard Drinks 457793.4272 94.31 B
Starchy Foods 351401.2504 96.20 C
Others 325517.6096 97.95 C
Breakfast 232298.9516 99.20 C
Seafood 148868.2194 100.00 C