Atomic
하게 즉, 여러 개의 쿼리이지만 동시에 성공하거나 실패하도록 실행
되어야 하는 SQL들을 묶어서 하나의 작업처럼 처리하는 방법
이다. 트랜잭션
을 통해 묶어 주어야 함.레코드를 추가, 수정, 삭제
한 것에만 의미가 있고 SELECT
에는 트랜잭션을 사용할 이유가 없다.BEGIN과 END
혹은 BEGIN과 COMMIT
사이에 해당 SQL을 사용한다.ROLLBACK
한다.BEGIN;
A의 계좌로부터 인출;
B의 계좌로 입금;
END; -- 이 BEGIN-END 사이의 명령어들은 하나의 명령어처럼 다 성공하거나 실패하거나 둘 중의 하나가 된다.
END
와 COMMIT
은 동일.ROLLBACK
을 실행.COMMIT MODE
에 따라서 달라짐.AUTOCOMMIT = TRUE
BEGIN
과 END(COMMIT)
, ROLLBACK
으로 처리해야 한다.AUTOCOMMIT = FALSE
AUTOCOMMIT = TRUE
BEGIN; END;
혹은 BEGIN; COMMIT;
을 사용한다.autocommit
이라는 파라미터로 조절이 가능하다.PostgreSQL
의 COMMIT MODE와 동일하다..commit()
과 .rollback()
함수로 트랜잭션을 조절 가능하다.DELETE FROM table_name
DROP
과는 명확하게 다르다.TRUNCATE table_name
DELETE FROM
보다 속도가 빠름.DELETE
와 달리 ROLLBACK
이 되지 않는다. 즉, 절대 ROLLBACK
할 필요가 없는 데이터에만 사용해야 한다.WHERE 조건 절
을 통해 특정 레코드만 삭제하는 것 역시 불가능하다.1) UNION, EXCEPT, INTERSECT
UNION (합집합)
UNION
과 UNION ALL
이 존재하는데 UNION
은 중복을 제거하지만 UNION ALL
은 중복을 따로 제거하지 않음-- union을 사용한 경우
SELECT 'keeyong' AS first_name
, 'han' AS last_name
UNION
SELECT 'elon', 'musk'
UNION
SELECT 'keeyong', 'han'
UNION
을 사용하면 다음과 같이 중복된 데이터는 나오지 않으므로 총 두 개의 레코드를 볼 수 있다.SELECT 'keeyong' AS first_name
, 'han' AS last_name
UNION ALL
SELECT 'elon', 'musk'
UNION ALL
SELECT 'keeyong', 'han'
UNION ALL
을 사용하면 중복된 데이터도 모두 나와 총 세 개의 레코드가 있음을 알 수 있다. 필요에 따라 중복이 된 데이터도 조회해야 하는 경우는 UNION ALL
을, 중복 데이터가 필요하지 않은 경우는 UNION
을 사용해 주어야 한다.EXCEPT (차집합)
field 수가 동일
하고, field의 데이터 타입이 동일
해야 함테스트용
즉 QA
로 주로 사용하고, 지금 사용하고 있는 SELECT statement와 새로 만든 SELECT statement의 차
이를 보고 새로 만든 SELECT가 맞게 동작하는지 확인할 수 있음.INTERSECT (교집합)
2) COALESCE, NULLIF
COALESCE (exp1, exp2, ...)
NULLIF (exp1, exp2)
3) LISTAGG
GROUP BY
에 사용되는 Aggregate
함수 중 하나WITHIN GROUP
을 사용해 준다.(ORDER BY TS)
를 사용해 준다.LISTAGG(컬럼명)
만 하면 컬럼에 속하는 데이터들이 구분자 없이 연속해 리스트로 보여진다. 즉, 그 사이에 특정 값을 넣어 구분을 해 주고 싶다면 두 번째 attribute를 통해 구분자를 설정해 준다. LISTAGG(컬럼명, 데이터 사이의 구분자 예를 들어 ','나 '->' 등)
SELECT A.USERID
, LISTAGG(A.CHANNEL, '->') WITHIN GROUP (ORDER BY B.TS) CHANNELS
FROM RAW_DATA.USER_SESSION_CHANNEL A
JOIN RAW_DATA.SESSION_TIMESTAMP B
ON A.SESSIONID = B.SESSIONID
GROUP BY 1
LIMIT 10;
4) LAG
WINDOW 함수
중 한 종류LAG(lagging하려고 하는 field명, 몇 개 이전의 레코드를 읽을 것인지) OVER (PARTITION BY 그룹핑할 field명 ORDER BY 정렬 기준 필드명)
형식으로 작성한다. 이때 바로 이전의 값을 읽으려면 LAG(lagging하려고 하는 field명, 1)
을 하면 된다. 만약 바로 이후의 값을 읽으려면 ORDER BY의 정렬 조건을 DESC(내림차순)
으로 수정해 준다.SELECT A.*
, B.TS
, LAG(CHANNEL, 1) OVER (PARTITION BY USERID ORDER BY TS) PREV_CHANNEL
FROM RAW_DATA.USER_SESSION_CHANNEL A
JOIN RAW_DATA.SESSION_TIMESTAMP B
ON A.SESSIONID = B.SESSIONID
ORDER BY A.USERID, B.TS
LIMIT 100;
ORDER BY
조건을 DESC(내림차순)
으로 진행해 준다.SELECT A.*
, B.TS
, LAG(CHANNEL, 1) OVER (PARTITION BY USERID ORDER BY TS DESC) NEXT_CHANNEL
FROM RAW_DATA.USER_SESSION_CHANNEL A
JOIN RAW_DATA.SESSION_TIMESTAMP B
ON A.SESSIONID = B.SESSIONID
ORDER BY A.USERID, B.TS
LIMIT 100;
5) WINDOW 함수
6) JSON Parsing 함수
f6
의 값을 알고 싶다면 다음과 같은 함수를 써 주면 된다.{
"f2": {
"f3": "1"
},
"f4": {
"f5": "99",
"f6": "star"
}
}
SELECT JSON_EXTRACT_PATH_TEXT('{"f2":{"f3":"1"}, "f4":{"f5": "99", "f6": "star"}}', 'f4', 'f6');
접점
= 채널
= 광고 미디어
최종 전환 (Macro-conversion)
기록 보조 전환 (Micro-conversion)
기록Urchin Tracking Module
📚 어제 과제 풀이 1
사용자별로 처음 채널과 마지막 채널이 무엇이었는지 찾기
1) CTE 빌딩 블록을 사용
- 바로 FROM 절의 하나의 테이블 블록으로 바로
RAW_DATA.USER_SESSION_CHANNEL
의JOIN 테이블
로 사용해도 되지만 동일하게WITH 절
을 사용하였다. 성능적인 차이보다는 쿼리 스타일의 차이라고 하셨는데 나는WITH 절
을 사용하는 게 더 깔끔해서 쿼리를 보기에 이해가 빠르다고 생각되어 애용하는 것 같다.- 다른 점은 나는 하나의 쿼리에서
FIRST
와LAST
를 모두 넘버링하고 이 데이터를 다시 한 번 더 USERID로GROUP BY
를 사용하였는데 여기는 각각FIRST
와LAST
의 쿼리를 생성하였다는 점이었다.WITH FIRST AS ( SELECT USERID , TS , CHANNEL , ROW_NUMBER() OVER (PARTITION BY USERID ORDER BY TS) SEQ FROM RAW_DATA.USER_SESSION_CHANNEL A JOIN RAW_DATA.SESSION_TIMESTAMP B ON A.SESSIONID = B.SESSIONID ), LAST AS ( SELECT USERID , TS , CHANNEL , ROW_NUMBER() OVER (PARTITION BY USERID ORDER BY TS DESC) SEQ FROM RAW_DATA.USER_SESSION_CHANNEL A JOIN RAW_DATA.SESSION_TIMESTAMP B ON A.SESSIONID = B.SESSIONID ) SELECT FIRST.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) GROUP BY 방식
- 내가 푼 풀이 방식과 가장 흡사했다. 내 풀이에서는
서브 쿼리
를 사용해서 사용자별 최초 채널과 최종 채널을 조회했는데CASE-WHEN 절
을 사용할 수도 있었다.SELECT USERID , MAX(CASE WHEN RN1 = 1 THEN CHANNEL END) LAST_TOUCH , MAX(CASE WHEN RN2 = 1 THEN CHANNEL END) FIRST_TOUCH FROM ( SELECT A.USERID , A.SESSIONID , A.CHANNEL , ROW_NUMBER() OVER (PARTITION BY A.USERID ORDER BY B.TS DESC) RN1 , ROW_NUMBER() OVER (PARTITION BY A.USERID ORDER BY B.TS ASC) RN2 FROM RAW_DATA.USER_SESSION_CHANNEL A JOIN RAW_DATA.SESSION_TIMESTAMP B ON A.SESSIONID = B.SESSIONID ) GROUP BY 1;
3) FIRST_VALUE/LAST_VALUE
서브 쿼리
나WITH 절
없이FIRST_VALUE
와LAST_VALUE
라는 윈도우 함수를 사용하면 하나의 쿼리로 출력할 수 있다.- 단 윈도우 함수로 조회하기 위해서는 뒤에 덧붙는 것들이 있는데
ROWS
와BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
이다. 이것은 창의 첫 행부터 마지막 행을 기준으로 행 집합을 지정한다는 뜻이다.SQL
에서는WINDOW 함수
가 유용하게 쓰이고 사용량이 많아 이는 포스팅으로 따로 작성해 보려고 한다.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 ;
📚 어제 과제 풀이 2
Gross Revenue가 가장 큰 UserID 10 개 찾기
1. GROUP BY 사용하기
- 나는 이 방법을 사용했다. 내 풀이와의 차이점은 결론적으로 이 경우 ROW_DATA.SESSION_TRANSACTION에 있는 경우만 합산이 되면 되기 때문에
INNER JOIN
을 써 두 데이터의 교집합으로 계산하여도 무관하다 생각했다. 풀이에서는LEFT JOIN
을 사용하였다.SELECT A.USERID , SUM(B.AMOUNT) GROSS_REVENUE FROM RAW_DATA.USER_SESSION_CHANNEL A LEFT JOIN RAW_DATA.SESSION_TRANSACTION B ON A.SESSIONID = B.SESSIONID GROUP BY A.USERID ORDER BY GROSS_REVENUE DESC LIMIT 10;
2. SUM OVER 사용하기
SUM OVER
라는 윈도우 함수를 사용하는 것이다.- 다만 SUM OVER를 사용할 경우 그룹핑이 되는 것이 아니기 때문에 존재하는 USERID만큼의 데이터가 나오게 되어 중복이 발생한다.
- 이 중복을 방지하기 위해
DISTINCT
를 통해 중복을 막아 준다.- 이 경우보다는 그룹핑을 하는 경우가 선호된다.
SELECT DISTINCT A.USERID , SUM(AMOUNT) OVER(PARTITION BY A.USERID) FROM RAW_DATA.USER_SESSION_CHANNEL A JOIN RAW_DATA.SESSION_TRANSACTION B ON A.SESSIONID = B.SESSIONID ORDER BY 2 DESC LIMIT 10;
📚 어제 과제 풀이 3
raw_data.nps 테이블을 바탕으로 월별 NPS 계산
1) FROM 절에 서브 쿼리 사용
- 전체적인 로직은 비슷하나 나는 WITH 절을 사용했고, 풀이에서는 FROM 절에 서브 쿼리를 사용했다. 결과 값은 동일하다.
SELECT MONTH , ROUND((PROMOTERS-DETRACTORS)::FLOAT/NULLIF(TOTAL_COUNT, 0)*100, 2) NPS FROM ( SELECT LEFT(CREATED_AT, 7) "MONTH" , COUNT(CASE WHEN SCORE >= 9 THEN 1 END) PROMOTERS , COUNT(CASE WHEN SCORE <= 6 THEN 1 END) DETRACTORS , COUNT(CASE WHEN SCORE > 6 AND SCORE < 9 THEN 1 END) PASSIVES , COUNT(1) TOTAL_COUNT FROM RAW_DATA.NPS GROUP BY 1 ORDER BY 1 )
2) 하나의 SELECT문 안에 SUM() 함수를 이용해서 계산하기
- 서브 쿼리나 WITH 절의 사용 없이 전체를 합산할 때 PROMOTER의 값을 1로 두고 빼야 하는 PASSIVES의 값을 -1로 둬 계산한 후에 전체 수로 나누어 계산하는 다음과 같은 방법도 있었다.
SELECT LEFT(CREATED_AT, 7) AS "MONTH" , ROUND(SUM(CASE WHEN SCORE >= 9 THEN 1 WHEN SCORE <= 6 THEN -1 END)::FLOAT*100/COUNT(1), 2) NPS FROM RAW_DATA.NPS GROUP BY 1 ORDER BY 1;