June 29, 2022
01.‘6강’ 여러 개의 값에 대한 조작
01.1 날짜/시간 계산하기
01.2 IP 주소 다루기
02.
7강
하나의 테이블에 대한 조작02.1 그룹의 특징 잡기
02.2 그룹 내부의 순서
02.3 세로 기반 데이터를 가로 기반으로 변환하기
사전준비
DROP TABLE IF EXISTS mst_users_with_dates;
CREATE TABLE mst_users_with_dates (
user_id varchar(255)
, register_stamp varchar(255)
, birth_date varchar(255)
);
INSERT INTO mst_users_with_dates
VALUES
('U001', '2016-02-28 10:00:00', '2000-02-29')
, ('U002', '2016-02-29 10:00:00', '2000-02-29')
, ('U003', '2016-03-01 10:00:00', '2000-02-29')
;
select * from mst_users_with_dates;
실습 쿼리
--- 6-11 미래 또는 과거의 날짜/ 시간을 계산하는 쿼리
select
user_id,
register_stamp::timestamp as register_stamp,
register_stamp::timestamp + '1 hour'::interval as after_1_hour,
register_stamp::timestamp - '30 minutes'::interval as before_30_minutes,
register_stamp::date as register_date,
(register_stamp::date + '1 day'::interval)::date as after_1_day,
(register_stamp::date - '1 month'::interval)::date before_1_month
from mst_users_with_dates;
날짜 데이터들의 차이 계산하기
--- 6-12 두 날짜의 차이를 계산하는 쿼리
select
user_id,
current_date as today,
register_stamp::date as register_date,
current_date - register_stamp::date as diff_days
from mst_users_with_dates;
사용자의 생년월일로 나이 계산하기
--- 6-13 age 함수를 사용해 나이를 계산하는 쿼리
select
user_id,
current_date as today,
register_stamp::date as register_date,
birth_date::date as birth_date,
extract(year from age(birth_date::date)) as current_age,
extract(year from age(register_stamp::date, birth_date::date))as register_age
from mst_users_with_dates;
---6-14 연 부분 차이를 계산하는 쿼리
select
user_id,
current_date as today,
register_stamp::date as register_date,
birth_date::date as birth_date,
(current_date- birth_date::date)/365 as current_age,
(register_stamp::date-birth_date::date)/365 as register_age
from mst_users_with_dates;
--- 6-15 날짜를 정수로 표현해서 나이를 계산하는 함수
select floor((20160228-20000229)/10000) as age;
--- 6-16 등록 시점과 현재 시점 나이를 문자열로 계산하는 쿼리
select
user_id,
substring(register_stamp,1,10) as register_date,
birth_date,
floor(-- 등록시점 나이 계산하기
(cast(replace(substring(register_stamp,1,10),'-','') as integer)
- cast(replace(birth_date,'-','')as integer)
)/10000
) as register_age,
floor(-- 현재시점 나이 계산하기
(cast(replace(cast(current_date as text ),'-','') as integer)
- cast(replace(birth_date,'-','')as integer)
)/10000
) as current_age
from mst_users_with_dates;
일반적인 웹 서비스는 로그 데이터에 사용자 IP 주소로 저장
IP 주소 자료형 활용하기
--- 6-17 inet 자료형을 사용한 IP주소 비교 쿼리
select
cast('127.0.0.1' as inet) < cast('127.0.0.2' as inet) as lt,
cast('127.0.0.1' as inet) > cast('192.168.0.1' as inet) as gt;
--- 6-18 inet 자료형을 사용해 IP 주소 범위를 다루는 쿼리
select cast('127.0.0.1' as inet) << cast('127.0.0.0/8' as inet) as is_contained;
정수 또는 문자열로 IP주소 다루기
정수 자료형으로 변환하면 숫자 대소 비교등이 가능해짐
--- 6-19 IP주소에서 4개의 10진수 부분을 추출하는 쿼리
select
ip,
cast(split_part(ip, '.', 1)as integer) as ip_part_1,
cast(split_part(ip, '.', 2)as integer) as ip_part_2,
cast(split_part(ip, '.', 3)as integer) as ip_part_3,
cast(split_part(ip, '.', 4)as integer) as ip_part_4
from
(select '192.168.0.1' as ip) as t;
--- 6-20 IP주소를 정수 자료형 표기로 변환하는 쿼리
select
ip,
cast(split_part(ip, '.', 1)as integer) * 2^24
+cast(split_part(ip, '.', 2)as integer) * 2^16
+cast(split_part(ip, '.', 3)as integer) * 2^8
+cast(split_part(ip, '.', 4)as integer) * 2^0
as ip_integer
from
(select '192.168.0.1' as ip) as t;\
--- 6-21 IP주소를 0으로 메운 문자열로 변환하는 쿼리
select
ip,
lpad(split_part(ip, '.', 1),3, '0')
||lpad(split_part(ip, '.', 2),3, '0')
||lpad(split_part(ip, '.', 3),3, '0')
||lpad(split_part(ip, '.', 4),3, '0')
as ip_padding
from
(select '192.168.0.1' as ip) as t;
7강
하나의 테이블에 대한 조작데이터 집약
집약 함수란?
예를 들어 모든 레코드의 수를 리턴해주는 COUNT 함수
테이블 전체의 특징량 계산하기
DROP TABLE IF EXISTS review;
CREATE TABLE review (
user_id varchar(255)
, product_id varchar(255)
, score numeric
);
INSERT INTO review
VALUES
('U001', 'A001', 4.0)
, ('U001', 'A002', 5.0)
, ('U001', 'A003', 5.0)
, ('U002', 'A001', 3.0)
, ('U002', 'A002', 3.0)
, ('U002', 'A003', 4.0)
, ('U003', 'A001', 5.0)
, ('U003', 'A002', 4.0)
, ('U003', 'A003', 4.0)
;
select * from review;
테이블 전체의 특징량 계산하기
--- 7-1 집약 함수를 사용해서 테이블 전체의 특징량을 계산하는 쿼리
select
count(*) as total_count,
count(distinct user_id) as user_count,
count(distinct product_id) as product_count,
sum(score) as sum,
avg(score) as avg,
max(score) as max,
min(score) as min
from
review;
그루핑한 데이터의 특징량 계산하기
--- 7-2 사용자 기반으로 데이터를 분할하고 집약함수를 적용하는 쿼리
select
user_id,
count(*) as total_count,
count(distinct product_id) as product_count,
sum(score) as sum,
avg(score) as avg,
max(score) as max,
min(score) as min
from
review
group by
user_id;
집약 함수를 적용한 값과 집약 전의 값을 동시에 다루기
--- 7-3 윈도 함수를 사용해 집약 함수의 결과와 원래 값을 동시에 다루는 쿼리
select
user_id,
product_id,
score, -- 개별 리뷰 점수
avg(score) over() as avg_score, --전체 평균 리뷰 점수
avg(score) over(partition by user_id)as user_avg_score, -- 사용자 평균 리뷰 점수
score - avg(score) over(partition by user_id) as user_avg_score_diff -- 개별 리뷰 점수와 사용자 평균 리뷰 점수의 차이
from review;
ORDER BY 구문으로 순서 정의하기
사전 준비
DROP TABLE IF EXISTS popular_products;
CREATE TABLE popular_products (
product_id varchar(255)
, category varchar(255)
, score numeric
);
INSERT INTO popular_products
VALUES
('A001', 'action', 94)
, ('A002', 'action', 81)
, ('A003', 'action', 78)
, ('A004', 'action', 64)
, ('D001', 'drama' , 90)
, ('D002', 'drama' , 82)
, ('D003', 'drama' , 78)
, ('D004', 'drama' , 58)
;
실습 쿼리
--- 7-3 윈도 함수의 ORDER BY 구문을 사용해 테이블 내부의 순서를 다루는 쿼리
select
product_id,
score,
row_number() over(order by score desc) as row, --점수로 유일한 순위를 붙임
rank() over(order by score desc) as rank, --같은 순위를 허용해서 순위를 붙임
dense_rank() over(order by score desc) as dense_rank, --같은 순위가 있을 때 같은 순위 다음에 있는 순위를 건너 뛰고 순위를 붙임
lag(product_id) over(order by score desc) as lag1,-- 현재행 보다 앞에 있는 행의 값 추출하기
lag(product_id,2) over(order by score desc) as lag2,
lead(product_id) over(order by score desc) as lead1,-- 현재행 보다 뒤에 있는 행의 값 추출하기
lead(product_id,2) over(order by score desc) as lead2
from popular_products
order by row;
order by 구문과 집약 함수 조합하기
--- 7-5 order by 구문과 집약 함수를 조합해서 계산하는 쿼리
select
product_id,
score,
--점수 순서로 유일한 순서를 붙임
row_number() over(order by score desc) as row,
-- 순위 상위부터의 누계 점수 계산하기
sum(score) over(order by score desc rows between unbounded preceding and current row)
as cum_score,
-- 현재 행과 앞 뒤의 행이 가진 값을 기반으로 평균 점수 계산하기
avg(score) over(order by score desc rows between 1 preceding and 1 following)
as local_avg,
--순위가 높은 상품 ID 추출하기
first_value(product_id) over(order by score desc rows between unbounded preceding and unbounded following)
as first_value,
-- 순위가 낮은 상품 ID 추출하기
last_value(product_id) over(order by score desc rows between unbounded preceding and unbounded following)
as last_value
from popular_products
order by row;
윈도 프레임 지정에 대해서
ROWS BETWEEN start AND end
CURRENT ROW(현재의 행)
n PRECEDING(n행 앞)
n FOLLOWING(n행 뒤)
UNBOUNDED PRECEDING(이전 행 전부)
UNBOUNDED FOLLOWING(이후 행 전부)
--- 7-6 윈도 프레임 지정별 상품 ID를 집약하는 쿼리
select
product_id,
--점수 순서로 유일한 순위를 붙임
row_number() over(order by score desc) as row,
-- 가장 앞 순위부터 가장 뒷 순위까지의 범위를 대상으로 상품 ID집약하기
array_agg(product_id) over(order by score desc rows between unbounded preceding and unbounded following)
as whole_agg,
-- 가장 앞 순위부터 현재 순위 까지의 범위를 대상으로 상품 ID집약하기
array_agg(product_id) over(order by score desc rows between unbounded preceding and current row)
as cum_agg,
-- 순위 하나 앞과 하나 뒤까지의 범위를 대상으로 상품 ID집약하기
array_agg(product_id) over(order by score desc rows between 1 preceding and 1 following)
as local_agg
from popular_products
where category = 'action'
order by row;
partition by와 order by 조합하기
--- 7-7 윈도 함수를 사용해 카테고리들의 순위를 계산하는 함수
select
category,
product_id,
score,
--카테고리별로 점수 순서로 정렬하고 유일한 순위를 붙임
row_number() over(partition by category order by score desc) as row,
--카테고리별로 같은 순위를 허가하고 순위를 붙임
rank() over(partition by category order by score desc) as rank,
-- 카테고리별로 같은 순위가 있을 때
-- 같은 순위 다음에 있는 순위를 건너 뛰고 순위를 붙임
dense_rank() over(partition by category order by score desc)
as dense_rank
from popular_products
order by category, row;
각 카테고리의 상위 n개 추출하기
--- 7-8 카테고리들의 순위 상위 2개까지의 상품을 추출하는 쿼리
select *
from
-- 서브 쿼리 내부에서 순위 계산
(
select
category,
product_id,
score,
--카테고리별로 점수 순서로 유일한 순위를 붙임
row_number() over(partition by category order by score desc) as rank
from popular_products
)as popular_products_with_rank
-- 외부 쿼리에서 순위 활용해 압축하기
where rank <=2
order by category, rank;
-- 7-9 카테고리별 순위 최상위 상품을 추출하는 쿼리
select distinct
category,
--카테고리별로 순위 최상위 상품ID 추출하기
first_value(product_id) over(partition by category order by score desc
rows between unbounded preceding and unbounded following)
as product_id
from popular_products;
행을 열로 변환하기
해당 쿼리는 날짜별로 지표들이 하나씩 존재하므로 표현식의 조건 true 하나뿐
사전 준비
DROP TABLE IF EXISTS daily_kpi;
CREATE TABLE daily_kpi (
dt varchar(255)
, indicator varchar(255)
, val integer
);
INSERT INTO daily_kpi
VALUES
('2017-01-01', 'impressions', 1800)
, ('2017-01-01', 'sessions' , 500)
, ('2017-01-01', 'users' , 200)
, ('2017-01-02', 'impressions', 2000)
, ('2017-01-02', 'sessions' , 700)
, ('2017-01-02', 'users' , 250)
;
select * from daily_kpi;
DROP TABLE IF EXISTS purchase_detail_log;
CREATE TABLE purchase_detail_log (
purchase_id integer
, product_id varchar(255)
, price integer
);
INSERT INTO purchase_detail_log
VALUES
(100001, 'A001', 3000)
, (100001, 'A002', 4000)
, (100001, 'A003', 2000)
, (100002, 'D001', 5000)
, (100002, 'D002', 3000)
, (100003, 'A001', 3000)
;
select * from purchase_detail_log;
행을 열로 변환하기
--- 7-10 행으로 저장된 지표 값을 열로 변환하는 쿼리
select
dt,
max(case when indicator = 'impressions' then val end) as impressions,
max(case when indicator = 'sessions' then val end) as sessions,
max(case when indicator = 'users' then val end) as users
from daily_kpi
group by dt
order by dt;
행을 쉼표로 구분한 문자열로 집약하기
--- 7-11 행을 집약해서 쉼표로 구분된 문자열로 변환하기
select
purchase_id,
--상품 ID를 배열에 집약하고 쉼표로 구분된 문자열로 변환하기
string_agg(product_id, ',') as product_idx,
sum(price) as amount
from purchase_detail_log
group by purchase_id
order by purchase_id;