방법 (1) 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;
방법 (2) 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;
방법 (3) GROUP BY 방식
SELECT userid,
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 usc.userid ORDER BY st.ts asc)) AS rn1, -- 오름차순으로 계산
(ROW_NUMBER() OVER (PARTITION BY usc.userid ORDER BY st.ts desc)) AS rn2 -- 내림차순으로 계산
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
)
GROUP BY 1;
방법 (4) FIRST_VALUE/LAST_VALUE - 가장 직관적!
SELECT DISTINCT
A.userid,
FIRST_VALUE(A.channel) over(partition by A.userid order by B.ts
rows between unbounded preceding and unbounded following) AS First_Channel,
LAST_VALUE(A.channel) over(partition by A.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
ORDER BY 1;
방법 (1) 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;
방법 (2) SUM OVER
SELECT DISTINCT
usc.userid,
SUM(amount) OVER(PARTITION BY usc.userid)
FROM raw_data.user_session_channel AS usc
JOIN raw_data.session_transaction AS revenue ON revenue.sessionid = usc.sessionid
ORDER BY 2 DESC
LIMIT 10;
서비스에 대해 고객들에게 '주변에 추천하겠는가?' 라는 질문을 기반으로 고객 만족도를 0(의향 없음)에서 10(의향 아주 높음)까지 점수 매겼을 때, 점수에 따라 다음과 같이 나눈다.
방법 (1)
SELECT month,
ROUND((promoters-detractors)::float/total_count*100, 2) AS overall_nps
FROM (
SELECT LEFT(created, 7) AS month,
COUNT(CASE WHEN score >= 9 THEN 1 END) AS promoters,
COUNT(CASE WHEN score <= 6 THEN 1 END) AS detractors,
COUNT(CASE WHEN score > 6 AND score < 9 THEN 1 END) AS passives,
COUNT(1) AS total_count
FROM raw_data.nps
GROUP BY 1
ORDER BY 1
);
방법 (2)
SELECT LEFT(created, 7) AS month,
ROUND(SUM(CASE
WHEN score >= 9 THEN 1
WHEN score <= 6 THEN -1 END)::float*100/COUNT(1), 2) -- 분자를 한번에 구함
FROM raw_data.nps
GROUP BY 1
ORDER BY 1;
트랜잭션이란?
Atomic하게 실행되어야 하는 SQL들을 묶어서 하나의 작업처럼 처리하는 방법
- 이는 DDL이나 DML 중 레코드를 수정/추가/삭제한 것에만 의미가 있음
- SELECT에는 트랜잭션을 사용할 이유 X
- BEGIN과 END 혹은 BEGIN과 COMMIT 사이에 해당 SQL들을 사용
- 만약 BEGIN 전의 상태로 돌아가고 싶다면 ROLLBACK 실행
예시: 은행 계좌 이체
- 계좌 이체는 인출과 입금의 두 과정으로 이뤄짐
- 만일 인출은 성공했는데, 입금이 실패한다면?
- 이 두 과정은 동시에 성공하던지 실패해야 한다(Atomic하다는 의미!)
- 이런 과정들을 트랜잭션으로 묶어주자(조회만 한다면 이는 트랜잭션으로 묶일 필요 없다)
트랜잭션 커밋 모드: autocommit
- autocommit = True
autocommit = False
Google Colab의 트랜잭션 방식
- 기본적으로 모든 SQL statement가 바로 커밋됨(autocommit = True)
- 이를 바꾸고 싶다면 BEGIN과 END(COMMIT)/ROLLBACK을 사용
psycopg2의 트랜잭션 방식
- autocommit이라는 파라미터로 조절가능
- autocommit = True가 되면 기본적으로 PostgreSQL의 커밋 모드와 동일
- autocommit = False가 되면 커넥션 객체의 .commit()과 .rollback()함수로 트랜잭션 조절 가능
UNION(합집합)
- 여러 개의 테이블들이나 SELECT 결과를 하나의 결과로 합쳐줌
- UNION vs. UNION ALL (UNION은 중복을 제거한 결과를 반환)
EXCEPT(MINUS)
- 하나의 SELECT 결과에서 다른 SELECT 결과를 빼주는 것이 가능
INTERSECT(교집합)
- 여러 개의 SELECT문에서 같은 레코드들만 찾아줌
COALESCE(Expression 1, Expression 2, ...)
- 첫번째 Expression 부터 값이 NULL이 아닌 것이 나오면 그 값을 리턴하고, 모두 NULL이면 NULL을 리턴
- NULL을 다른 값으로 바꾸고 싶을 때 사용
NULLIF(Expression 1, Expression 2)
- Expression 1과 Expression 2의 값이 같으면 NULL을 리턴
GROUP BY에서 사용되는 Aggregate 함수 중 하나
SELECT userid, LISTAGG(channel, '->') WITHIN GROUP (ORDER BY ts) channels -- ts 순서대로 사용자별 채널을 리스트, 구분자 지정까지
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
GROUP BY 1
LIMIT 10;
-- 이전 채널 찾기
SELECT usc.*, st.ts,
LAG(channel, 1) OVER (PARTITION BY userId ORDER BY ts) prev_channel -- LAG(필드명, 1): 하나 앞의 레코드에서 값을 읽어와라
FROM raw_data.user_session_channel usc
JOIN raw_data.session_timestamp st ON usc.sessionid = st.sessionid
ORDER BY usc.userid, st.ts
LIMIT 100;
JSON 포맷을 이미 아는 상황에서만 사용가능하며, JSON String을 입력으로 받아 특정 필드의 값을 추출가능(nested 구조 지원)
SELECT JSON_EXTRACT_PATH_TEXT('{"f2":{"f3": "1"},"f4":{"f5":"99","f6":"star"}}','f4', 'f6');