본문 바로가기
Database

MySQL - 자동차 매출 데이터를 이용한 리포트 작성

by 나룽나룽 2023. 10. 5.

본 글은 SQL을 공부하기 위해

<SQL로 맛보는 데이터 전처리 분석> 속 예제를 연습하고 기록한 글이다.

매일 배운 내용을 잘 기록해서 제대로 소화하는 게 이 글의 최종 목표이다.

 

막간 도서 소개

출처 YES 24 (노수영 저)


 

4장

자동차 매출 데이터를 이용한 리포트 작성

 

1. 데이터 생성하기

https://www.mysqltutorial.org/mysql-sample-database.aspx

 

MySQL Sample Database

This page provides you with a MySQL sample database that helps you to practice with MySQL effectively and quickly. You can download the sample database and load it into your MySQL Server.

www.mysqltutorial.org

 

위 링크에 접속해 Download MySQL Sample Database를 다운받는다.

다운로드한 파일은 MySQL Workbench에서 실행한다.

 

 

2. 데이터 구조 이해하기

이제 Classicmodels의 테이블 구조를 알아보기 위해

아래와 같이 ERD(Entity Relational Diagram)를 작성한다.

 

크게 고객, 매출, 상품으로 구성되어 있고

각 테이블 특정 칼럼으로 연결되어 JOIN 함수를 써야 정보를 조합해 데이터를 조회할 수 있다.

 

Classicmodels ERD

 

 

 

3. 구매 지표 추출

1) 매출액 (일자별, 월별, 연도별)

 

a) 일별 매출액 조회

USE classicmodels;

SELECT A.orderdate
    , SUM(priceeach * quantityordered) as sales
FROM orders A
LEFT JOIN orderdetails B
ON A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

필요한 건 주문일자와 판매액 정보이다.

주문일자(orderdate)는 orders 테이블에 존재,

판매액(priceeach * quantityordered)은 orderdetails 테이블에 존재한다. 

각 테이블은 ordernumber이라는 특정 열로 연결되고 있어

LEFT JOIN을 ordernumber 열로 테이블을 조합한다.

 

이후 일별 매출액을 집계하려면

GROUP BY를 통해 주문일자별로 그룹핑한 뒤

집계함수 SUM을 사용해 priceeach * quantityordered의 합으로 구한다.

 

orderdate sales
2003-01-06 93
2003-01-09 87
2003-01-10 73
2003-01-29 399
2003-01-31 320
2003-02-11 170
... ...

 

b) 월별 매출액 조회

orderdate는 'yyyy-mm-dd'의 형태로 구성되어 있다.

이중 'yyyy-mm'만 가져오려면 원하는 문자열로 자르는 SUBSTR() 함수를 사용한다.

 

-- SUBSTR 함수 예시

-- SUBSTR(컬럼, 위치, 길이)
SELECT SUBSTR('2003-01-04', 1, 7);
-- > '2003-01'

 

SELECT SUBSTR(A.orderdate, 1, 7) AS MM
	, SUM(priceeach & quantityordered) as SALES
FROM orders A
LEFT JOIN orderdetails B
ON A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

 

일자별 매출액과 코드가 거의 동일하나,

다른 건 SELECT 첫 번째 계산식으로 SUBSTR이 들어가고 별칭으로 MM으로 만든 것이다.

해당 계산식으로 GROUP BY, ORDER BY를 하기 때문에 GROUP BY 1, ORDER BY 1은 그대로 유지한다.

 

MM SALES
2003-01 972
2003-02 569
2003-03 915
2003-04 965
2003-05 1136
2003-06 873
... ...

c) 연도별 매출액 조회

SELECT SUBSTR(A.orderdate, 1, 4) as YY
	, SUM(priceeach & quantityordered) as SALES
FROM orders A
LEFT JOIN orderdetails B
ON A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

 

월별 매출액 조회와 동일하지만 SUBSTR의 길이를 '4'로 조정해

orderdate에서 'yyyy' 정보만 추출하도록 수정한다.

그 외 내용은 동일하다.

 

YY SALES
2003 18879
2004 25702
2005 10459

 

 

2) 구매자 수, 구매 건수(일자별, 월별, 연도별)

 

Classicmodels ERD

a) 일자별 구매자 수, 구매건수 조회

SELECT 
	orderdate
	, COUNT(DISTINCT customernumber) AS 구매자수
    , COUNT(ordernumber) AS 구매건수
FROM orders
GROUP BY 1
ORDER BY 1;

결과예시

orderdate 구매자수 구매건수
2003-01-06 1 1
2003-01-09 1 1
2003-01-10 1 1
2003-01-29 1 1

 

 

b) 구매자 수가 2명 이상인 날짜 조회 (서브쿼리)

SELECT A.*
FROM (SELECT 
	orderdate
	, COUNT(DISTINCT customernumber) AS 구매자수
    , COUNT(ordernumber) AS 구매건수
FROM orders
GROUP BY 1
ORDER BY 1
) A
WHERE A.구매자수 >= 2;

여기에서 A는 서브쿼리의 결과 집합을 가리키는 별칭이다.

별칭을 사용해 코드의 가독성을 향상시킨다.

 

결과예시

orderdate 구매자수 구매건수
2003-05-21 2 2
2003-06-16 2 2
2003-10-06 2 2
2003-10-10 2 2

 

3) 인당 매출액 AMV (연도별, 월별)

-- 연도별 인당 구매금액(AMV) 구하기 : 매출액 / 구매자 수

SELECT SUBSTR(A.orderdate, 1, 4) as YY
		, COUNT(DISTINCT A.customernumber) AS 구매자수
		, SUM(priceeach * quantityordered) AS SALES
        , SUM(priceeach * quantityordered) / COUNT(DISTINCT A.customernumber) AS AMV
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;


-- 월별 인당 구매금액 구하기
SELECT SUBSTR(A.orderdate, 1, 7) as MM
		, COUNT(DISTINCT A.customernumber) AS 구매자수
		, SUM(priceeach * quantityordered) AS SALES
        , SUM(priceeach * quantityordered) / COUNT(DISTINCT A.customernumber) AS AMV
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

결과 예시(월별)

MM 구매건수 SALES AMV
2003-01 5 116692.77 23338.554000
2003-02 3 128403.64 42801.213333
2003-03 6 160517.14 26752.856667
2003-04 7 185848.59 26549.798571

 

4) 건당 매출액 ATV (연도별, 월별)

-- 연도별 건당 구매금액(ATV) 구하기 : 매출액 / 구매건수
-- 1건의 거래가 평균적으로 얼마의 매출을 일으키는가?
SELECT SUBSTR(A.orderdate, 1, 4) as YY
		, COUNT(DISTINCT A.ordernumber) AS 구매건수
		, SUM(priceeach * quantityordered) AS SALES
        , SUM(priceeach * quantityordered) / COUNT(DISTINCT A.ordernumber) AS ATV
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

-- 월별 건강 구매금액(ATV) 구하기
SELECT SUBSTR(A.orderdate, 1, 7) as MM
		, COUNT(DISTINCT A.ordernumber) AS 구매건수
		, SUM(priceeach * quantityordered) AS SALES
        , SUM(priceeach * quantityordered) / COUNT(DISTINCT A.ordernumber) AS ATV
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
GROUP BY 1
ORDER BY 1;

결과 예시(월별)

MM 구매건수 SALES ATV
2003-01 5 116692.77 23338.554000
2003-02 3 128403.64 42801.213333
2003-03 6 160517.14 26752.856667
2003-04 7 185848.59 26549.798571

 

3. 그룹별 구매 지표 구하기

1) 국가별 도시별 매출액

SELECT C.country
		, C.city
		, SUM(B.priceeach * B.quantityordered) AS SALES
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
LEFT JOIN customers C
on A.customernumber = C.customernumber
GROUP BY 1, 2
ORDER BY 1, 2;

결과예시

country city SALES
Australia Chatswood 133907.12
Australia Glen Waverly 55866.02
Australia Melbourne 180585.07
Australia North Sydney 137034.22

 

2) 북미, 비북미별 매출액

-- 북미, 비북미 구분 (CASE WHEN 조건 THEN 결과 END)
SELECT 
	CASE WHEN country IN ('USA', 'Canada') THEN 'North America'
    ELSE 'Others' END CNTRY
    , country
FROM customers;


-- 북미, 비북미 매출액 
SELECT 
	CASE WHEN COUNTRY IN ('USA', 'Canada') THEN 'North America'
	ELSE 'Others' END CNTRY,
SUM(B.priceeach * B.quantityordered) AS SALES
FROM orders A
LEFT JOIN orderdetails B
on A.ordernumber = B.ordernumber
LEFT JOIN customers C
on A.customernumber = C.customernumber
GROUP BY 1
ORDER BY 1;

결과예시

CNTRY SALES
North America 3479191.91
Others 6124998.70

 

3) 매출 Top 5 국가 및 매출

-- 국가별 Top Product 및 매출
CREATE TABLE stat AS
SELECT
	C.country
    , SUM(priceeach * quantityordered) SALES
FROM orders A
LEFT JOIN orderdetails B
ON A.ordernumber = B.ordernumber
LEFT JOIN customers C
ON A.customernumber = C.customernumber
GROUP BY 1
ORDER BY 2 DESC;
-- 서브쿼리 활용
SELECT
	country
    ,  SALES
    , DENSE_RANK() OVER(ORDER BY SALES DESC) RNK
FROM stat;


SELECT *
FROM (SELECT
	country
    ,  SALES
    , DENSE_RANK() OVER(ORDER BY SALES DESC) RNK
FROM stat) TOP
WHERE TOP.RNK <= 5

결과 예시

country SALES RNK
USA 3273280.05 1
Spain 1099389.09 2
France 1007374.02 3
Australia 562582.59 4
New Zealand 476847.01 5

 

4. 재구매율

여기에서 재구매율은 같은 고객이 특정 기간(EX. 2018년)에 구매한 경우를 찾고

이를 기반으로 1년 후 (EX.2019년)에 연달아 구매한 구매자의 비중을 의미한다.

 

SELECT A.customernumber,
		A.orderdate,
		B.customernumber,
        B.orderdate
FROM orders A
LEFT JOIN orders B
ON A.customernumber = B.customernumber 
AND SUBSTR(A.orderdate, 1, 4) = SUBSTR(B.orderdate, 1, 4)-1;

 

결과 예시

customernumber orderdate customernumber orderdate
363 2003-01-06 363 2004-11-04
128 2003-01-09 128 2004-03-15
128 2003-01-09 128 2004-11-05
181 2003-01-10 181 2004-04-05

 

 

위의 결과를 통해 각 연도별 구매자 수를 집계한다면

ex. 2019년 구매자 수 / 2018년 구매자 수 로 나누어 

최종적으로 각 연도의 재구매율을 구할 수 있다.

 

1) 국가별 2004, 2005 Retention Rate(%)

SELECT C.country
		, SUBSTR(A.orderdate, 1, 4) YY
        , COUNT(DISTINCT A.customernumber) BU_1
        , COUNT(DISTINCT B.customernumber) BU_2
        , COUNT(DISTINCT B.customernumber) / COUNT(DISTINCT A.customernumber) RETENTION_RATE
FROM ORDERS A
LEFT JOIN ORDERS B
ON A.customernumber = B.customernumber
AND SUBSTR(A.orderdate, 1, 4) = SUBSTR(B.orderdate, 1, 4)-1
LEFT JOIN customers C
ON A.customernumber = C.customernumber
GROUP BY 1, 2;

 

이전 쿼리가 거의 비슷하지만 국가별로 그룹핑해야하기 때문에

고객의 거주 국가 정보를 결합하는 LEFT JOIN 함수가 사용되어야 한다.

 

결과 예시

country YY BU_1 BU_2 RETENTION_RATE
Australia 2003 5 3 0.6000
Australia 2004 3 2 0.6667
Australia 2005 4 0 0.0000
Austria 2003 2 1 0.5000
Austria 2004 1 1 1.0000
Austria 2005 2 0 0.0000