[DEV] 6주차. 데이터 웨어하우스와 SQL과 데이터분석(5)
1. 사용자별로 처음 채널과 마지막 채널 알아내기
ROW_NUMBER
이용
CTE를 빌딩블록으로
WITH first AS (
SELECT userid, ts, channel, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY ts) seq
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
), last AS (
SELECT userid, ts, channel, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY ts DESC) seq
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
)
SELECT first.userid AS userid, first.channel AS first_channel, last.channel AS last_channel
FROM first
JOIN last ON first.userid = last.userid and last.seq = 1
WHERE first.seq = 1;
JOIN
SELECT first.userid AS userid, first.channel AS first_channel, last.channel AS last_channel
FROM (
SELECT userid, ts, channel, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY ts) seq
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
) first
JOIN (
SELECT userid, ts, channel, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY ts DESC) seq
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
) last ON first.userid = last.userid and last.seq = 1
WHERE first.seq = 1;
GROUP BY
SELECT uesrid,
MAX(CASE WHEN rn1 = 1 THEN channel END) first_touch,
MAX(CASE WHEN rn2 = 1 THEN channel END) last_touch
FROM (
SELECT userid,
channel,
(ROW_NUMBER() OVER(PARTITION BY userid ORDER BY st.ts asc)) AS rn1,
(ROW_NUMBER() OVER(PARTITION BY userid ORDER BY st.ts desc)) AS rn2
FROM raw_data.user.sesiion_channel usc
JOIN raw_data.session_stamp st ON usc.sessionid = st.sessionid
)
GROUP BY 1;
FIRST VALUE / LAST VALUE
SELECT DISTINCT
A.userid,
FIRST_VALUE(A.channel) OVER(PARTITION BY userid ORDER BY B.ts rows between unbounded preceding and unbounded following) AS first_channel,
LAST_VALUE(A.channel) OVER(PARTITION BY userid ORDER BY B.ts rows between unbounded preceding and unbounded following) AS last_channel
FROM raw_data.user_session_channel A
LEFT JOIN raw_data.session_timestamp B ON A.sessionid = B.sessionid;
2. Gross Revenue가 가장 큰 userid 10개 찾기
- Gross Revenue : Refund를 포함한 매출
GROUP BY
SELECT
userid,
SUM(amount)
FROM raw_data.session_transaction st
LEFT JOIN raw_data.user_session_channel usc ON st.sessionid = usc.sessionid
GROUP BY 1
ORDER BY 2 DESC
LIMIT 10;
SUM OVER
SELECT DISTINCT
userid,
SUM(amount) OVER(PARTITION BY usc.userid)
FROM raw_data.session_transaction st
LEFT JOIN raw_data.user_session_channel usc ON st.sessionid = usc.sessionid
ORDER BY 2 DESC
LIMIT 10;
3. 트랜잭션
- Atomic하게 실행되어야 하는 SQL들을 묶어서 하나의 작업처럼 처리하는 방법
- DDL이나 DML 중 레코드를 수정/추가/삭제한 것에만 의미가 있음
- SELECT에는 트랜잭션을 사용할 이유가 없음
BEGIN
과END
/BEGIN
,COMMIT
사이에 SQL 문 사용
BEGIN;
A 계좌에서 인출; -- 이 명령어들은 하나의 명령어처럼 처리되어
B 계좌로 인출; -- 다 성공하든지 다 실패해야 함
END;
END
와COMMIT
은 동일BEGIN
전으로 돌아가고 싶다면ROLLBACK
실행
트랜잭션 커밋 모드: autocommit
- autocommit = True
- 모든 레코드 수정/삭제/추가 작업이 기본적으로 바로 DB에 쓰여짐 = commit 된다고 함
- 특정 작업을 트랜잭션으로 묶고 싶다면
BEGIN
과END(COMMIT)
/ROLLBACK
으로 처리 - Google Colab은 기본적으로 모든 SQL문이 바로 커밋됨
- psycopg2는
autocommit
으로 조절 가능
- autocommit = False
- 모든 레코드 수정/삭제/추가 작업이
COMMIT
이 호출될 떄까지 커밋되지 않음 - 많이 쓰이지는 않음
- 모든 레코드 수정/삭제/추가 작업이
4. DELETE FROM vs. TRUNCATE
- DELETE FROM table_name
- 테이블에서 모든 레코드를 삭제
- vs.
DROP TABLE table_name
: 테이블 자체를 삭제 - WHERE을 사용하여 특정 레코드만 삭제 가능
- TRUNCATE table_name
- 테이블에서 모든 레코드를 삭제
DELETE FROM
은 속도가 느림TRUNCATE
가 전체 테이블 내용 삭제 시에는 유리!- 단점
WHERE
을 지원하지 않음- 트랜잭션을 지원하지 않음
5. SQL 문법들
UNION, EXCEPT, INTERSECT
- UNION (합집합)
- 여러 개의 테이블들이나 SELECT 결과를 하나의 결과로 합쳐줌
- UNION ALL은 중복된 값도 모두 출력
- UNION은 중복 제거
- EXCEPT (MINUS)
- 하나의 SELECT 결과에서 다른 SELECT 결과를 뺌
- INTERSECT (교집합)
- 여러 개의 SELECT문에서 같은 레코드들만 찾아줌
- EXCEPT, INTERSECT : SQL을 수정했을 때, 기존 SQL과 새 SQL의 차이점을 실제 레코드를 통해서 테스트하기 위해 많이 사용
COALESCE, NULLIF
- COALESCE(exp1, exp2, …)
- 첫번째 exp부터 값이 NULL이 아닌 것이 나오면 그 값을 리턴
- 모두 NULL이면 NULL 리턴
- NULL 값을 다른 값으로 바꾸고 싶을 때 사용!
- NULLIF(exp1, exp2)
- exp1과 exp2가 같으면 NULL 리턴
- 다르면 exp1 리턴
- 나누기 연산에서 분모에 사용할 수 있음
LISTAGG
- GROUP BY에서 사용되는 Aggregate 함수 중 하나
-
그룹핑된 레코드들 안에서 특정 필드의 값을 옆으로 붙여주는 함수 (listing 형태로 aggregation)
- ex) 사용자 ID 별로 방문한 채널을 시간순으로 리스트
SELECT
userid,
LISTAGG(channel, '->') WITHIN GROUP (ORDER BY ts) channels
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
GROUP BY 1
LIMIT 10;
-- 68 Youtube->Google->Instagram->Youtube->Instagram->Instagrm->Instagrm->Organic ...
WINDOW 함수
function(exp) OVER([PARTITION BY exp][ORDER BY exp])
- functions
- ROW_NUMBER, FIRST_VALUE, LAST_VALUE, LAG
- math: AVG, SUM, COUNT, MAX, MIN, MEDIAN, NTH_VALUE
LAG 함수
- 정렬된 레코드들에서 이전 값, 다음 값을 알고싶을 때 사용
- ex) 사용자 세션을 시간 순으로 봤을 때 앞 세션의 채널
SELECT usc.*, st.ts,
LAG(channel, 1) OVER(PARTITION BY userid ORDER BY ts) prev_channel
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
ORDER BY usc.userid, st.ts;
JSON Parsing Functions
- JSON의 포맷을 이미 아는 상황에서만 사용 가능
- JSON String을 입력으로 받아 특정 필드의 값 추출 가능 (nested 구조 지원)
- ex)
SELECT JSON_EXTRACT_PATH_TEXT('{"f2":{"f3":"1"}, "f4":{"f5":"99", "f6":"star"}}', 'f4', 'f6') -- star 출력