[PostgreSQL초격차] _4_주문데이터_날짜 관련 함수, 사칙연산 지표 (할인률,판매가,이익률) 그리고 고객1명당 평균수량과 구매금액

Hyejin Beck·2024년 1월 7일
0

데이터베이스(SQL)

목록 보기
7/40

날짜 관련 함수

날짜 관련해서는 DBMS 종류 (Oracle, MySQL, Postgres 등) 에 따라 달라집니다.
회사에 따라 사용하고 있는 프로그램 언어 종류가 다르니 사실 그때그때마다 날짜 함수에 대해서는 구글링 하며 진행하는것이 좋습니다.

오늘

-- 오늘을 나타내는 기본 구문 3가지 
select now() 
-- yyyy-mm-dd 시간:분:초 +0900GMT

select current_date 
-- yyyy-mm-dd 

select current_timestamp 
-- yyyy-mm-dd 시간:분:초 +0900GMT

문자형식으로 변환 to_char()

to_char(값, '바꿀형태') : 문자형으로 변환 함수

-- 날짜형식에서 문자형식으로 변환
select to_char(now(),'yyyymmdd')
-- 변환전) yyyy-mm-dd 시간:분:초 +0900GMT
-- 변환후) yyyymmdd 

select to_char(now(), 'yyyy / mm / dd')

오늘 날짜 +/- interval

select now() + interval '1day'

select now() + interval '2week'

select now() + interval '3weeks'

select now() - interval '1 month'

select now() - interval '2year'

select now() - interval '30day'

오늘 날짜로부터 년/월/일 추출 date_part

select date_part ('year', now())
-- 오늘이 2024년이니까 2,024가 나옵니다. 

select date_part ('day', now())
-- 오늘이 1월7일이니까 7 만 나옵니다. 

필요한 이유 : 최근 1년동안의 매출액 확인하거나, 오늘 기준으로 최근 60d 추이 등을 자동으로 확인할 수 있는 쿼리문을 작성할 수 있습니다.

구글링?

Postgres의 now() 함수가 mysql에서는 뭐지?

now() in mysql
now() in oracle
interval 1 month in mysql 

정리

-- 날짜 함수 : SQL 언어에 따라 달라지니 사실 필요할때마다 구글링 하는 것이 좋습니다.   
-- 2021년 6월 1일 하루 동안의 가상의 패션 이커머스 데이터 분석 



-- 오늘을 나타내는 기본 구문 3가지 
select now() 
-- yyyy-mm-dd 시간:분:초 +0900GMT

select current_date 
-- yyyy-mm-dd 

select current_timestamp 
-- yyyy-mm-dd 시간:분:초 +0900GMT



-- 날짜형식에서 문자형식으로 변환
select to_char(now(),'yyyymmdd')
-- 변환전) yyyy-mm-dd 시간:분:초 +0900GMT
-- 변환후) yyyymmdd 

select to_char(now(), 'yyyy / mm / dd')


-- 오늘로부터 날짜 더하기/빼기
select now() + interval '1day'

select now() + interval '2week'

select now() + interval '3weeks'

select now() - interval '1 month'

select now() - interval '2year'

select now() - interval '30day'


-- 날짜로부터 year, month, week 추출(확인) 
select date_part ('year', now())
-- 오늘이 2024년이니까 2,024가 나옵니다. 

select date_part ('day', now())
-- 오늘이 1월7일이니까 7 만 나옵니다. 

상품별 할인률,판매가,이익률 계산

online_order테이블에서 사칙연산을 이용해 필요한 값들을 계산해보겠습니다.

소숫점 나오게 numeric

  • 할인률 = 할인액 / 매출액 = discount / gmv
  • 판매가 = 매출액 - 할인액 = gmv - discount
  • 이익률 = 이익액 / 매출액 = product_profit / gmv 또는 total_profit / gmv

우선 확인해보겠습니다.

-- 할인률 = 할인액 / 매출액 = discount / gmv 
select 
	discount,
	gmv , 
	discount / gmv as discount_rate 
from online_order oo 

소숫점이 없는 정수 / 소숫점이 없는 정수 = 소숫점이 없는 정수 로 나와서 0이 됩니다.

소숫점이 있는 형태로 바꿔야 합니다.
형태를 바꾸는 함수 cast()로 사용합니다.

불가

-- 할인률 = 할인액 / 매출액 = discount / gmv 
select 
	discount,
	gmv , 
	cast(discount / gmv as numeric ) as discount_rate 
from online_order oo 

불가

-- 할인률 = 할인액 / 매출액 = discount / gmv 
select 
	cast(discount as numeric) ,
	cast(gmv as numeric) , 
	cast(discount / gmv as numeric) as discount_rate 
from online_order oo 

정답 분자 또는 분모 둘 중 하나라도 cast () 로 numeric 변환 해주면 됩니다.

-- 할인률 = 할인액 / 매출액 = discount / gmv 
select 
	discount,
	gmv , 
	cast(discount as numeric) / gmv  as discount_rate 
from online_order oo 

할인률

0.05 소수점 숫자를 백분률 % 퍼센트로 바꾸려면
concat(round(소숫점숫자 * 100) , '%')

-- online_order 테이블에서 할인액과 매출액을 이용한 할인률 컬럼 생성 
select 
	discount,
	gmv , 
	-- 할인률 = 할인액 discount / 매출액 gmv 
	-- dicount / gmv 하면 정수/정수 여서 정수인 0 만 나옵니다. 
	-- cast(discount as numeric) / gmv  는 0.05 
	-- round((cast(discount as numeric) / gmv) * 100) 는 5 
 	-- concat(round((cast(discount as numeric) / gmv) * 100), '%') 는 5% 
	concat(round((cast(discount as numeric) / gmv) * 100), '%') as discount_rate 
from online_order oo 

할인률, 판매가, 이익률 계산

-- online_order 테이블에서 할인률, 판매가, 이익률 컬럼 생성 
-- 할인률 = 할인액 / 매출액 = discount / gmv 
-- 판매가 = 매출액 - 할인액 = gmv - discount 
-- 상품이익률 = 이익액 / 매출액 = product_profit / gmv 
-- 종합이익률 = 이익액 / 매출액 = total_profit / gmv 
-- 0.05 소수점 숫자를 백분률 % 퍼센트로 바꾸려면 = concat(round(소숫점숫자 * 100) , '%') 
select 
	discount,
	gmv , 
	concat(round((cast(discount as numeric) / gmv) * 100), '%') as discount_rate , --할인률 
	gmv  - discount as paid_amount  , --판매가 
	concat(round((cast(product_profit as numeric) / gmv) * 100), '%') as product_magin,  -- 상품마진률 
	concat(round((cast(total_profit  as numeric) / gmv) * 100), '%') as total_magin
from online_order oo 

카테고리별 집계 (join과 sum())

위에 만든 할인률, 판매가, 이익률을 상품(category테이블의 cate1)별 로 집계해보겠습니다.

  • online_order 테이블의 itemid = item 테이블의 id
  • item테이블의 category_id = category테이블의 id

이렇게 세 테이블을 연결 연결!

select 
	c.cate1, 
	concat(round((cast(discount as numeric) / gmv) * 100), '%') as discount_rate , --할인률 
	gmv  - discount as paid_amount  , --판매가 
	concat(round((cast(product_profit as numeric) / gmv) * 100), '%') as product_magin,  -- 상품마진률 
	concat(round((cast(total_profit  as numeric) / gmv) * 100), '%') as total_magin --종합이익률 
from online_order oo 
join item i on oo.itemid  = i.id 
join category c on i.category_id = c.id 

역시나 Group by 를 통해 상품별 집계를 해주겠습니다.

에러 : 그냥 Group by 1 만 추가했더니 에러가 나왔습니다.

select 
	c.cate1, 
	concat(round((cast(discount as numeric) / gmv) * 100), '%') as discount_rate , --할인률 
	gmv  - discount as paid_amount  , --판매가 
	concat(round((cast(product_profit as numeric) / gmv) * 100), '%') as product_magin,  -- 상품마진률 
	concat(round((cast(total_profit  as numeric) / gmv) * 100), '%') as total_magin --종합이익률 
from online_order oo 
join item i on oo.itemid  = i.id 
join category c on i.category_id = c.id 
group by 1 

에러가 나온 이유는 cate1 각 값에 따라 여러개의 row가 있기 때문에 sum() 으로 묶어 줘야 합니다.
쉽게 말하자면 cate1가 블라우스 일 때, 여러개의 rows가 있어서 이걸 합쳐줘야 하니깐요

집계 함수가 있을 때, group by를 해주려면 각각의 분모와 분자에 sum()으로 묶어줘야 합니다.

더 정확한 수치를 확인하기 위해 백분율 함수 등을 제거하겠습니다.

select 
	c.cate1, 
	sum(cast(discount as numeric)) / sum(gmv) as discount_rate, 
	sum(gmv) - sum(discount) as paid_mount, 
	sum(cast(product_profit as numeric)) / sum(gmv) as product_magin, 
	sum(cast(total_profit as numeric)) / sum(gmv) as total_margin 
from online_order oo 
join item i on oo.itemid = i.id 
join category c on i.category_id  = c.id 
group by 1 
order by 3 desc

소숫점 2번째 자리까지 표시되게끔 round(값, 2)

select 
	c.cate1, 
	round(sum(cast(discount as numeric)) / sum(gmv),2) as discount_rate, 
	sum(gmv) - sum(discount) as paid_mount, 
	round(sum(cast(product_profit as numeric)) / sum(gmv),2) as product_magin, 
	round(sum(cast(total_profit as numeric)) / sum(gmv),2) as total_margin 
from online_order oo 
join item i on oo.itemid = i.id 
join category c on i.category_id  = c.id 
group by 1 
order by 3 desc

백분율로 다시 변환되게 * 100

select 
	c.cate1, 
	round(sum(cast(discount as numeric)) / sum(gmv),2) * 100 as discount_rate, 
	sum(gmv) - sum(discount) as paid_mount, 
	round(sum(cast(product_profit as numeric)) / sum(gmv),2) *100  as product_magin, 
	round(sum(cast(total_profit as numeric)) / sum(gmv),2) * 100 as total_margin 
from online_order oo 
join item i on oo.itemid = i.id 
join category c on i.category_id  = c.id 
group by 1 
order by 3 desc

숫자열+문자열은 ||'%'

여기에 문자열 '%' 추가하려면 concat보다는 || 가 더 간단합니다.
postgres에는 가능하지만 다른 sql에서는 안 될 수 있습니다.

select 
	c.cate1, 
	round(sum(cast(discount as numeric)) / sum(gmv)* 100)  || '%'  as discount_rate, 
	sum(gmv) - sum(discount) as paid_mount, 
	round(sum(cast(product_profit as numeric)) / sum(gmv)* 100)  || '%'   as product_magin, 
	--round(sum(cast(total_profit as numeric)) / sum(gmv), 2) * 100  || '%' as total_margin 하게되면 26.00% 
	--round(sum(cast(total_profit as numeric)) / sum(gmv) * 100)  || '%' as total_margin    하게되면 26% 
	round(sum(cast(total_profit as numeric)) / sum(gmv) * 100)  || '%' as total_margin 
from online_order oo 
join item i on oo.itemid = i.id 
join category c on i.category_id  = c.id 
group by 1 
order by 3 desc

숫자열+문자열은 cast(값 as varchar)

다른 sql언어에서 안되는경우

round(sum(cast(total_profit as numeric)) / sum(gmv) * 100)  || '%' as total_margin 
가 안되는 경우, 

cast(round(sum(cast(total_profit as numeric)) / sum(gmv) * 100) as varchar) || '%' as total_margin 
로 해줍니다. 

고객 관점 분석

  • 고객관점에서의 분석 (1인당 평균 구매수량과 1인당 평균 구매금액)
  • 100명의 고객이 구매했을 때, 총 판매수량이 200개라고 가정한다.
  • 1인당 평균 구매수량 = 총 판매수량 / 총 고객 수
  • 1인당 평균 구매금액 = 총 구매금액 / 총 고객 수

1인당 구매수량이 높은 상품

-- 1인당 구매수량이 높은 상품 확인 = 상품별 판매수량/고객수 
-- 상품확인은 item테이블의 item_name + 구매수량(판매수량)은 order테이블의 unitsold + 중복값제거된 고객수는 userid의 고유값 셈  
select
	i.item_name ,                           -- 상품별 
	sum(unitsold) as unitsold ,             -- 판매수량의 합계 
	count (distinct userid) as user_count,  -- 고유 유저수 
	sum(unitsold) / count(distinct userid) as avg_unitsold_per_customer
from online_order oo 
join item i on oo.itemid = i.id 
group by 1 

역시나 나누기 했을때는 정수/정수 = 정수 로 표현됩니다.
cast( 값 as numeric) 으로 분자나 분모 둘 중 하나만 해줍니다.

select
	i.item_name ,                           -- 상품별 
	sum(unitsold) as unitsold ,             -- 판매수량의 합계 
	count (distinct userid) as user_count,  -- 고유 유저수 
	sum(cast(unitsold as numeric)) / count(distinct userid) as avg_unitsold_per_customer
from online_order oo 
join item i on oo.itemid = i.id 
group by 1 

소숫점 두 자리까지 나오도록 하겠습니다.
round(값, 2)

select
	i.item_name ,                           -- 상품별 
	sum(unitsold) as unitsold ,             -- 판매수량의 합계 
	count (distinct userid) as user_count,  -- 고유 유저수 
	round(sum(cast(unitsold as numeric)) / count(distinct userid),2) as avg_unitsold_per_customer
from online_order oo 
join item i on oo.itemid = i.id 
group by 1 

이제 order by 로 내림차순 정렬 하겠습니다.

select
	i.item_name ,                           -- 상품별 
	sum(unitsold) as unitsold ,             -- 판매수량의 합계 
	count (distinct userid) as user_count,  -- 고유 유저수 
	round(sum(cast(unitsold as numeric)) / count(distinct userid),2) as avg_unitsold_per_customer
from online_order oo 
join item i on oo.itemid = i.id 
group by 1 
order by 4 desc 

-- 커플룩 > 기본필수 라운드티 > 여름필수 브이넥 등 여러벌 구매할수 있는 제품의 구매수량이 높습니다. 

1인당 구매금액이 높은 상품

-- 1인당 평균 구매금액은 위에 unitsold대신 gmv로 대체해줍니다. 
select
	i.item_name ,                           -- 상품별 
	sum(unitsold) as unitsold ,             -- 판매수량의 합계 
	count (distinct userid) as user_count,  -- 고유 유저수 
	round(sum(cast(unitsold as numeric)) / count(distinct userid),2) as avg_unitsold_per_customer,
	round(sum(cast(gmv as numeric)) / count(distinct userid)) as avg_gmv_per_customer
from online_order oo 
join item i on oo.itemid = i.id 
group by 1 
order by 5 desc 

1인당 구매금액이 높은 성별/연령대

-- 1인당 구매금액이 높은 성별/연령대 확인
-- online_order 테이블의 userid = user_info테이블의 userid 
-- 성별(user테이블), 연령(user테이블), 구매액sum(order테이블), 유저수count+distinct(order테이블)
select 
	gender
	, age_band 
	, sum(gmv) as gmv 
	, count(distinct oo.userid) as user_count 
from online_order oo 
join user_info ui on oo.userid = ui.userid 
group by 1,2 

1인당 구매금액 = 구매액 합계 / 고유 유저수

-- 1인당 구매금액이 높은 성별/연령대 확인
-- online_order 테이블의 userid = user_info테이블의 userid 
-- 성별(user테이블), 연령(user테이블), 구매액sum(order테이블), 유저수count+distinct(order테이블)
select 
	gender
	, age_band 
	, sum(gmv) as gmv 
	, count(distinct oo.userid) as user_count 
	, sum(gmv) / count(distinct oo.userid) as avg_gmv_per_customer
from online_order oo 
join user_info ui on oo.userid = ui.userid 
group by 1,2 
order by 5 desc

1인당 구매금액은 25~34세 남성들이 많은걸로 확인됩니다.
하지만 유저 수(모수) 가 합쳐도 고작 3명...! 너무 작습니다.
이것만 봐서 남성들의 구매파워가 강하다! 라고 말하긴 좀 그렇습니다...;;
이 3명을 제외하면 30-34세의 여성들의 파워가 가장 강한 것 같습니다!

이론 되짚어보기

sql은 한 가지 기능에 따라 여러가지 함수가 있습니다.
물론 sql 언어에 따라 달라질 수도 있습니다!

데이터 유형

  • int 정수 (소숫점 없음)
  • bigint 큰 정수 (숫자가 많을 때 사용)
  • numeric 소숫점 있는 수
cast(정수 as numeric ) 으로 소숫점 있는 숫자로 변환
정수 :: numeric 으로도 변환 가능합니다. 
  • date 시, 분, 초
  • timestamp 년, 월, 일

데이터 변환

기존컬럼 :: 바꿀 형태로 간단하게 바꿀 수도 있습니다.

데이터 가공(문자)

문자열 추출 : left , right, substring
문자열 자르기 : trim, ltrim, rtrim
문자열 길이 반환 : len, length
문자열 병합 : || , concat()
문자열 변경 : replace

대문자 변경 : upper
소문자 변경 : lower

조건문 : case when ~ else ~ end
Null값 : coalesce() , nvl()

데이터 가공(숫자)

반올림 : round
올림 : ceiling
내림 : trunc
절대값 반환 : abs
Null값 : coalesce , nvl

데이터 가공(날짜)

날짜 더하기 : - interval '더할날짜', dateadd()
날짜 빼주기 : datediff()
날짜 추출 : date_part(), to_char()

현재 날짜의 월1일 : date_trunc('month', now())
현재 날짜의 분기1일 : date_trunc('quarter', now())
현재 날짜의 주 시작일 추출 : date_truc('week', now())

배운 내용 확인

성장률, 구매비중 , 랭킹 등에 대해서는 sql로는 복잡해지니 Python, Tableau 같은 프로그램에서 진행하는 것이 더 좋습니다.

SQL은 단순하게! 빠르게! 원하는 데이터를 필터링을 걸어 추출하는 목적입니다.
과거 데이터 기반 시각적 요소나 예측 등에 대해서는 다른 프로그램으로 진행합니다!

profile
데이터기반 스토리텔링을 통해 인사이트를 얻습니다.

0개의 댓글

관련 채용 정보