ilovechoonsik
[STARTERS 4기 TIL] SQL 심화 #4 - WINDOW 함수, Quantity 기준 분석 (230413) 본문
[STARTERS 4기 TIL] SQL 심화 #4 - WINDOW 함수, Quantity 기준 분석 (230413)
춘시기좋아 2023. 4. 13. 19:46
📖 오늘 내가 배운 것
1. 지난 과제 피드백
2. 제품/카테고리 매출 지표 분석
3. 금일 과제
4. 핵심 내용 정리
1. 지난 과제 피드백






- 말 천천히, 쿼리 결과 테이블 피피티에 넣으면 좋겠다, 결과 부분에 핵심 내용은 볼드나 글씨 진하게 하면 좋겠다!
- 근속기간이 높을수록 매출이 높나!? 너무 당연한 사실! -> 기간을 기준으로 두고 구했어야 했다.
2. 제품/카테고리 매출 지표 분석
- 전체 제품 매출 순위, 비율
- 카테고리 별 제품 순위, 비율
📌 사용할 테이블
categories
products
order
order_details
suppliers 공급업체 제품 공급하는 업체
📌 주의할 점은?
판매 당시의 uit_price? 단가는 변화
order_details와 products의 unit_price가 다르다! 시가가 있나 보다
# suppliers에서 어느 나라에서 온 제품인지 확인할 수 있다.
2.1 임시 테이블 생성
orders : 주문번호, 고객번호, 주문일, 연,월.일,분기
order_details : 제품번호 판매단가 수량 할인율 매출액
categoreis : 카테고리 id, 카테고리명
products : 제품id, 제품명, 마스터단가, 단종여부
suppliers : 공급자 id, 공급자명, 국가, 도시
orders- order_details 1:N
products - categories N:1
등등 일대다 관계가 많다!
인지하고 JOIN
/*
임시 테이블 만들기 : cte_products_sales
orders : 주문번호, 고객번호, 주문일, 연,월.일,분기
order_details : 제품번호 판매단가 수량 할인율 매출액
categoreis : 카테고리 id, 카테고리명
products : 제품id, 제품명, 마스터단가, 단종여부
suppliers : 공급자 id, 공급자명, 국가, 도시
*/
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
2.2 제품(카테고리)별 매출 순위 및 비율
📌 작업 순서
1. 필요한 컬럼 가져오고
2. 순위 구하고
3. 비율 구하고
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 매출액 계산 (그룹핑)
, cte_amount as (
select 카테고리명
, 제품ID
, 제품명
, 공급사
, 공급사국가
, sum(매출액) 매출액
from cte_products_sales
group by 1,2,3,4,5
)
-- 2. 순위
, cte_rank_totamount as (
select *
, rank() over (order by 매출액 DESC) as 순위
, sum(매출액) over() as 전체매출액
from cte_amount
), cte_ratio as (
-- 3. 매출비율 계산
select *
, (매출액 / 전체매출액 * 100) as 매출비율
from cte_rank_totamount
)
-- 4. 최종 테이블
select 카테고리명, 제품ID, 제품명, 공급사, 공급사국가, 매출액, 순위
, round(매출비율::numeric ,2)::varchar(10)||'%' as 매출비율
from cte_ratio;
프랑스의 뭐시기 공급사가 제공하는 ~한 카테고리의 제품이 11%
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 매출액 계산 (그룹핑)
, cte_products_sales2 as (
select 카테고리명
, 제품ID
, 제품명
, 공급사
, 공급사국가
, sum(매출액) 매출액
from cte_products_sales
group by 1,2,3,4,5
)
-- 2. 순위, 전체매출액계산
, rankk as (
select *
, rank() over (partition by 카테고리명 order by 매출액 DESC) as 매출액순위
, sum(매출액) over(partition by 카테고리명) as 전체매출액
from cte_products_sales2
), cte_ratio as(
-- 3. 매출비율 계산
select *, (매출액/전체매출액) * 100 as 매출비율
from rankk
)
-- 최종테이블
select 카테고리명, 제품ID, 제품명, 공급사, 공급사국가, 매출액, 매출액순위
, ROUND(매출비율::numeric ,2)::varchar(10)||'%' as 매출비율
from cte_ratio
order by 1, 7;
프랑스의 뭐시기 공급사가 제공하는 ~한 카테고리의 제품이 11%
그럼 얘는 왜 이렇게 비싼 걸까? 가설을 세워보자
1. 단가가 비싸서
2. 판매량 높아서
3. 트랜드여서? (정량적으로 가능? - 시계열(계절성))
4. 판매 기간이 길어서
# remind : group by로 묶을 때는 묶이는 컬럼 외에는 집계 함수만 존재할 수 있다!
때문에 추가적인 window 함수 등을 적용하려면 서브쿼리 사용하는 게 좋음!
+++ case when 사용한 pivot의 경우 max, min 등으로 각 열에 해당하는 행 데이터 한 개만 받아올 수 있다.
2.3 ABC 분석
제품 분석을 하게 되면 우리가 중점적으로 관리를 해줘야 하는 제품을 확인하고 맞춤화 전략을 펼쳐볼 수 있겠지?
그때 사용되는 게 ABC 분석이다.
- 제품을 A,B,C 등급으로 나눠서 관리
관련 자료
https://terms.naver.com/entry.naver?docId=1125408&cid=40942&categoryId=31608
ABC분석
통계적 방법에 의해 관리대상을 A, B, C 그룹으로 나누고, 먼저 A그룹을 최중점 관리대상으로 선정하여 관리노력을 집중함으로써 관리효과를 높이려는 분석방법. 이는 "극히 소수의 요인에 의해
terms.naver.com
https://ko.wikipedia.org/wiki/ABC_%EB%B6%84%EC%84%9D
ABC 분석 - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. ABC 분석(-分析, ABC analysis)은 '가치의 크기에 대응한 노력의 투입'에 의해서 효과를 올리는 기법으로 요령이 좋은 방법이다. 재고관리(在庫管理)의 기법으로 1951
ko.wikipedia.org
📌 순서?
1. 매출액이 많은 순으로 정리
2. 총매출을 100%로 하여 고객별 백분비 산출
3. 그 누적 구성비율을 상위의 고객부터 순서대로 누적해 간다.
4. 그래프의 세로에 매출액 점유비의 누적치를, 가로축에 고객을 기입하고 고객별 누적구성비를 표시해 간다
5. 세로축의 70%와 90%의 누적치 해당점에서 가로선을 긋고, 그래프의 선과의 교차점에서 수직선을 긋는다
/*ABC 분석!*/
-- 1. 제품별 매출액
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 제품별 매출액 구하기
, cte_amount as(
select 제품ID, 제품명, sum(매출액) as 매출액
from cte_products_sales
group by 1,2
order by 제품ID
)
-- 2. 구성비
, cte_ratio as (
select *
, sum(매출액) over() as 전체매출액
, 매출액/sum(매출액) over() * 100 as 구성비
from cte_amount
), cte_ration_agg as (
-- 3. 구성비 누계 (주의점 : 1등 부터 매출이 높은 순으로 정렬해야 함)
select *
, sum(구성비) over(order by 구성비 desc) as 구성비누계
from cte_ratio
order by 구성비 desc
) -- 70 %까지를 A그룹, 90 %까지를 B그룹, 그 이상을 C그룹
select *
, case
when 구성비누계 <= 70 then 'A'
when 구성비누계 <= 90 then 'B'
else 'C'
end 등급
from cte_ration_agg
order by 구성비 DESC;
2.4 Pivot Table 1
매출 상위 3개 제품 가져온다! 제품별 매출액!
/*Pivot Table*/
--1. 매출 상위 3개 제품 가져온다 -> 피벗한다.
-- 1. 제품별 매출액
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 제품별 매출액 구하기
, cte_amount as (
select 카테고리명, 제품명
, sum(매출액) as 매출액
from cte_products_sales
group by 1,2
)
-- 2. rank
, cte_rank as (
select *
, rank () over (partition by 카테고리명 order by 매출액 desc) 랭크
from cte_amount
)
-- 3. rank top 3
, cte_rank_top3 as (
select *
from cte_rank
where 랭크 <= 3
)
-- 4. 각 열을 case when 사용해서 만들어준다!
-- 여기서 max는 카테고리명으로 그룹핑 되었기 때문에 집계함수를 사용할 수 밖에 없어서 넣는 것.
-- 어차피 각 열 행 당 결과는 rank에 딱딱 맞는 1개이기 때문에 max,min, mean 뭐 다 상관없다.
select 카테고리명
, max(case when 랭크 = 1 then 제품명 end ) as "1위"
, max(case when 랭크 = 2 then 제품명 end ) as "2위"
, max(case when 랭크 = 3 then 제품명 end ) as "3위"
from cte_rank_top3
group by 카테고리명;
2.5 Pivot Table 2
두 번째 방법!
매출순위로 그룹핑 후 카테고리 명 뽑기!!!!!
조건 : 카테고리마다 제품 수가 다르기 때문에 null 처리까지
--두 번째 방법!
--매출순위로 그룹핑 후 카테고리 명 뽑기!
--카테고리 마다 제품 수가 다르다!, null 처리까지 하기~
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
--, p.product_id as 제품ID order_details와 중복!
, p.product_name as 제품명
, p.unit_price as 마스터단가
, p.discontinued as 단종여부
-- 서플라이어
, s.supplier_id as 공급자ID
, s.contact_name as 공급사
, s.country as 공급사국가
, s.city as 공급사도시
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 제품별 매출액 구하기
, cte_amount as (
select 카테고리명, 제품명
, sum(매출액) as 매출액
from cte_products_sales
group by 1,2
)
-- 2. rank
, cte_rank as (
select *
, rank () over (partition by 카테고리명 order by 매출액 desc) 랭크
from cte_amount
)
-- 3. 각 랭크에 해당하는 카테고리 별 제품
select 랭크
, COALESCE(max(case when 카테고리명 = 'Beverages' then 제품명 end), '') as Beverages
, COALESCE(max(case when 카테고리명 = 'Condiments' then 제품명 end), '') as Condiments
, COALESCE(max(case when 카테고리명 = 'Confections' then 제품명 end), '') as Confections
, COALESCE(max(case when 카테고리명 = 'Dairy Products' then 제품명 end), '') as "Dairy Products"
, COALESCE(max(case when 카테고리명 = 'Grains/Cereals' then 제품명 end), '') as "Grains/Cereals"
, COALESCE(max(case when 카테고리명 = 'Meat/Poultry' then 제품명 end), '') as "Meat/Poultry"
, COALESCE(max(case when 카테고리명 = 'Produce' then 제품명 end), '') as Produce
, COALESCE(max(case when 카테고리명 = 'Seafood' then 제품명 end), '') as Seafood
from cte_rank
group by 1
order by 1;
# 공백, 특수문자 alias는 ""로 감싸서 넣기!
# null 처리는 COALESCE로! COALESCE(식, null 일 때 대체 값)
# 이전번에 했던 것과 같이 그룹핑되어있을 때는 집계함수를 사용해야 하고, 어차피 각 랭크에 해당하는 제품을 한 개씩만 가져오는 것이기 때문에 MAX를 통해 집계인 척하기
3. 금일 과제
이전까지는 매출액을 기준으로 했는데? 과제는 수량을 기준으로 해서 가장 많이 팔린 게 뭔지 구하기
3.1 전체 판매수량 TOP10
- 먼저 판매수량 기준 RANK로 정렬!
- RANK 제품ID 제품명 총판매수량 퐁 매출액 카테고리명
- 여기서 LIMIT 10
/*
1. 전체 판매수량 TOP10
- 먼저 판매수량 기준 RANK로 정렬!
- RANK 제품ID 제품명 총 판매수량 퐁 매출액 카테고리명
- 여기서 LIMIT 10
*/
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
, p.product_name as 제품명
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 제품별 매출액,판매수량
, cte_amount as (
select 제품ID, 제품명, 카테고리명
, sum(수량) as 판매수량
, sum(매출액) as 매출액
from cte_products_sales
group by 1,2,3
order by 판매수량 desc
)
-- 2. 랭크구하기
, cte_rank_top10 as (
select *
, row_number() over(order by 판매수량 DESC) as Rank
from cte_amount
)
-- 3. 판매수량 top 10
select rank as "Rank"
, 제품ID as "제품ID"
, 제품명
, 판매수량 as "총 판매수량"
, 매출액 as "총 매출액"
, 카테고리명
from cte_rank_top10
limit 10
3.2 국가별 판매수량 TOP5
국가별로 파티션 나눠서 다시 랭크, 국가명 기준으로
- 국가명 RANK 1 RANK 2 RANK 3 RANK 4 RANK 5
- 그리고 각 제품명 넣기
/*
2. 국가별 판매수량 TOP5
국가별로 파티션 나눠서 다시 랭크, 국가명 기준으로
- 국가명 RANK 1 RANK 2 RANK 3 RANK 4 RANK 5
- 그리고 각 제품명 넣기
*/
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
, o.ship_country as 주문국가
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
, p.product_name as 제품명
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 필요 컬럼 선정 + 판매수량, 매출액
, cte_amount as (
select 주문국가, 제품ID, 제품명
, sum(수량) as 판매수량
, sum(매출액) as 매출액
from cte_products_sales
group by 1,2,3
order by 1 desc
)
-- 2. 국가 별 판매수량 랭크구하기
, cte_rank as (
select *
, row_number() over(partition by 주문국가 order by 판매수량 DESC) as Rank
from cte_amount
)
-- 3. 국가 별 판매수량 top 5
, cte_rank_top5 as (
select *
from cte_rank
order by 매출액
), cte_pivot as (
-- 4. PIVOT 및 국가별 총 매출액 순 내림차순 정렬
select 주문국가 as 국가명
, sum(매출액) as 총매출액
, max(case when Rank = 1 then 제품명 end ) as "RANK 1"
, max(case when Rank = 2 then 제품명 end ) as "RANK 2"
, max(case when Rank = 3 then 제품명 end ) as "RANK 3"
, max(case when Rank = 4 then 제품명 end ) as "RANK 4"
, max(case when Rank = 5 then 제품명 end ) as "RANK 5"
from cte_rank_top5
group by 주문국가
order by 총매출액 desc
)
-- 5. 국가별 판매량 TOP 5 제품 - 최종정리
select 국가명, "RANK 1", "RANK 2", "RANK 3", "RANK 4", "RANK 5"
from cte_pivot
3.3 1997년 분기별 판매수량 TOP10
97년만 가져와서 분기별 총 판매 수량
/*
3. 1997년 분기별 판매수량 TOP10
97년만 가져와서 분기별 총 판매 수량
*/
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
, p.product_name as 제품명
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
)
-- 1. 1997년의 분기, 판매수량 등 필요 컬럼 가져오기
, cte_amount as (
select 분기, 제품명
, sum(수량) as 판매수량
, sum(매출액) as 매출액
from cte_products_sales
where 연 = '1997'
group by 1,2
order by 1
)
-- 2. 각 분기 partition 놓고 rank 구하기
, cte_rank as (
select *
, row_number () over (partition by 분기 order by 판매수량 desc) 랭크
from cte_amount
)
-- 3. 각 분기 별 rank top 10 조건 걸어주기
, cte_rank_top10 as (
select *
from cte_rank
where 랭크 <= 10
)-- 4. pivot - 최종정리
select 랭크
, COALESCE(max(case when 분기 = '1' then 제품명 end), '') as "1997-1분기"
, COALESCE(max(case when 분기 = '2' then 제품명 end), '') as "1997-2분기"
, COALESCE(max(case when 분기 = '3' then 제품명 end), '') as "1997-3분기"
, COALESCE(max(case when 분기 = '4' then 제품명 end), '') as "1997-4분기"
from cte_rank_top10
group by 1
order by 1;
3.4 1997년 분기별 판매수량 TOP10 - 순위변화
- 지난 분기에 비해 몇 등이 올랐는지까지 표현
RANK 제품명 1분기 순위변화 제품명 2분기 순위변화 제품명 3분기 순위변화 제품명 4분기 순위변화
조건 : RANK는 RoW_NUMBER로
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
, p.product_name as 제품명
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
), cte_amount as (
-- 1. 1997년의 분기, 판매수량 등 필요 컬럼 가져오기
select 연, 분기, 제품명
, sum(수량) as 판매수량
from cte_products_sales
where (연 = '1996' and 분기 = '4') or 연 = '1997'
group by 1,2,3
order by 연, 분기 asc
), cte_rank as (
-- 2. 랭크 구하기
select *
, row_number () over (partition by 연, 분기 order by 판매수량 desc) 랭크
from cte_amount
order by 제품명, 연, 분기
)
-- 3. LAG 사용해서 이전 분기와 연산/비교하기
, cte_lag as (
select *
, lag(랭크) over(partition by 제품명 order by 연, 분기) - 랭크 as 순위변화
from cte_rank
)
-- 4. pivot - 최종정리
select 랭크 as rank
, max(case when 연 = '1997' and 분기 = '1' then 제품명 end) as "97_1분기"
, max(case when 연 = '1997' and 분기 = '1' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '2' then 제품명 end) as "97_2분기"
, max(case when 연 = '1997' and 분기 = '2' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '3' then 제품명 end) as "97_3분기"
, max(case when 연 = '1997' and 분기 = '3' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '4' then 제품명 end) as "97_4분기"
, max(case when 연 = '1997' and 분기 = '4' then 순위변화 end) as "순위변화"
from cte_lag
group by 1
limit 10;
97_1분기의 답이 강사님과 다른 건, 아래 우측 사진과 같이 판매수량 153인 두 제품이 존재하기 때문이다!
총매출까지 고려하여 rank하고 싶다면 간단하게 row number 사용 시 order by 인자에 총매출액 desc 추가로 넣어주면 된다!
with cte_products_sales as (
select -- order
o.order_id as 주문번호
, o.customer_id as 고객번호
, o.order_date as 주문일
, to_char(order_date, 'YYYY') as 연
, to_char(order_date, 'MM') as 월
, to_char(order_date, 'dd') as 일
, to_char(order_date, 'q') as 분기
-- order_details
, od.product_id as 제품ID
, od.unit_price as 판매당시금액
, od.quantity as 수량
, od.discount as 할인율
, round( od.unit_price * od.quantity * (1-od.discount) ) as 매출액
-- categories
, c.category_id as 카테고리ID
, c.category_name as 카테고리명
, p.product_name as 제품명
from orders o
left join order_details od ON od.order_id = o.order_id
left join products p on p.product_id = od.product_id
left join categories c on p.category_id = c.category_id
left join suppliers s ON p.supplier_id = s.supplier_id
), cte_amount as (
-- 1. 1997년의 분기, 판매수량 등 필요 컬럼 가져오기
select 연, 분기, 제품명
, sum(수량) as 판매수량
, sum(매출액) as 총매출액
from cte_products_sales
where (연 = '1996' and 분기 = '4') or 연 = '1997'
group by 1,2,3
order by 연, 분기, 판매수량, 총매출액 asc
), cte_rank as (
-- 2. 랭크 구하기
select *
, row_number () over (partition by 연, 분기 order by 판매수량 desc, 총매출액 desc) 랭크
from cte_amount
order by 제품명, 연, 분기
), cte_lag as (
select *
, lag(랭크) over(partition by 제품명 order by 연, 분기) - 랭크 as 순위변화
from cte_rank
)
select 랭크 as rank
, max(case when 연 = '1997' and 분기 = '1' then 제품명 end) as "97_1분기"
, max(case when 연 = '1997' and 분기 = '1' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '2' then 제품명 end) as "97_2분기"
, max(case when 연 = '1997' and 분기 = '2' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '3' then 제품명 end) as "97_3분기"
, max(case when 연 = '1997' and 분기 = '3' then 순위변화 end) as "순위변화"
, max(case when 연 = '1997' and 분기 = '4' then 제품명 end) as "97_4분기"
, max(case when 연 = '1997' and 분기 = '4' then 순위변화 end) as "순위변화"
from cte_lag
group by 1
limit 10;
4. 핵심내용 정리
1. WINDOW FUNCTION - 상황 별로 사용하는 함수가 있음! 익숙해지기~ LAG, RANK, 집계함수 활용
2. % 붙이기? - PostgreSQL 에서는 수치::varchar(10)||"%"
3. 소수점 2자리까지만 뽑기? - round(수웃자::numeric, 2) (numeric여야 round에서 잘림)
4. GROUP BY/WINDOW FUNCTION은 엮으면 사용하려면 CTE 사용해야 함! - GROUP BY 후에는 집계 씌운 컬럼만 올 수 있음.
💪🏻 좋았던 점, 앞으로 개선해야 할 점 (추가로 배워야 할 점)
📌 전부 한글로 as 붙이며 진행하면 어떨지 궁금해서 시도해 봤는데 더 헷갈렸다..
📌 금일 배운 다양한 윈도우 함수 활용은 확실히 난이도 있게 느껴졌다!
꼭 복습해서 익숙해질 수 있도록 하자
#유데미, #유데미코리아, #유데미부트캠프, #취업부트캠프, #부트캠프후기, #스타터스부트캠프, #데이터시각화 #데이터분석 #태블로
'STARTERS 4기 🚉 > TIL 👶🏻' 카테고리의 다른 글
[STARTERS 4기 TIL] SQL 심화 #6 - 고객 분석, 윈도우 함수, Decil, RFM (230417) (0) | 2023.04.17 |
---|---|
[STARTERS 4기 TIL] SQL 심화 #5 - Z-차트 및 그룹핑 함수 (230414) (1) | 2023.04.14 |
[STARTERS 4기 TIL] SQL 심화 #3 - WINDOW 함수, employees 테이블 분석 (230412) (0) | 2023.04.13 |
[STARTERS 4기 TIL] SQL 심화 #2 - 기초 복습, 매출 지표 분석 (230411) (0) | 2023.04.12 |
[STARTERS 4기 TIL] SQL 심화 #1 - 데이터 파악 (230410) (0) | 2023.04.10 |