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 = TRUEBEGIN; 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;