GROUP BY
로 지정해 주고, 그룹별로 계산할 내용을 결정할 때 AGGREGATE
함수를 사용한다.1) 월별 세션 수를 계산하는 SQL
SELECT LEFT(ts, 7) AS MON -- 시간을 문자열로 보고 7자리만 추출, ts는 시간 데이터이기 때문에 7자리를 추출한다는 것은 'yyyy-mm'을 추출한다는 것
, COUNT(1) AS SESSION_COUNT
FROM raw_data.session_timestamp
GROUP BY 1
ORDER BY 1; -- 1을 하면 default인 오름차순으로 (ASC)
2) 가장 많이 사용된 채널 SQL
user_session_channel
session_timestamp
(세션 정보는 user_session_channel
에도 존재하기 때문)%%sql
SELECT CHANNEL
, COUNT(*) AS SESSION_CNT
, COUNT(DISTINCT USERID) AS USER_CNT
FROM RAW_DATA.USER_SESSION_CHANNEL
GROUP BY 1 -- ORDER BY나 GROUP BY 뒤에 숫자가 붙는 경우는 기준이 되는 컬럼을 정하기 위해서
ORDER BY 2 DESC; -- 지금과 같은 경우 CHANNEL로 그룹핑하고 CHANNEL COUNT를 기준으로 내림차순 정렬
-- 즉, GROUP BY CHANNEL ORDER BY SESSION_CNT DESC와 동일
3) 가장 많은 세션을 만들어낸 사용자 ID SQL
SELECT USERID
, COUNT(*) USER_SESSION_CNT
FROM RAW_DATA.USER_SESSION_CHANNEL
GROUP BY 1
ORDER BY 2 DESC
4) 월별 유니크한 사용자 수 (MAU-Monthly Active User) SQL
user_session_channel
, session_timestamp
테이블을 둘 다 사용해야 함.SESSIONID (PK)
TS 컬럼(DATE 형식)
에서 년-월
만 추출하는 방법 (차이가 없음) 2019-11-01 00:00:00
과 같은 출력이 나옴. 다른 TIMESTAMP 타입
이 유지되기 때문. 출력을 다른 경우와 같이 YYYY-MM 형식
으로 하고 싶다면 추가적인 가공 필요. -- 내가 문제를 보고 짠 쿼리
SELECT TO_CHAR(S.TS, 'YYYY-MM') AS MONTH
, COUNT(DISTINCT U.USERID) AS MAU -- 같은 사용자는 월별로 몇 번을 방문했든 한 번만 카운트 하도록 처리
FROM RAW_DATA.USER_SESSION_CHANNEL U
, RAW_DATA.SESSION_TIMESTAMP S
WHERE U.SESSIONID = S.SESSIONID -- PK이자 접점인 SESSIONID를 기준으로 JOIN 해 줌
GROUP BY 1
ORDER BY 1 DESC;
-- 강의에서 나온 쿼리
SELECT TO_CHAR(S.TS, 'YYYY-MM') AS MONTH
, COUNT(DISTINCT U.USERID) AS MAU
FROM RAW_DATA.SESSION_TIMESTAMP S
JOIN RAW_DATA.USER_SESSION_CHANNEL U -- INNER JOIN 양쪽에 공통적인 레코드들만 남기고 나머지는 조회되지 않도록
ON U.SESSIONID = S.SESSIONID
GROUP BY 1
ORDER BY 1 DESC;
5) 월별 채널별 유니크한 사용자 수 SQL
SELECT TO_CHAR(S.TS, 'YYYY-MM') AS MONTH
, U.CHANNEL
, COUNT(DISTINCT U.USERID) AS MAU
FROM RAW_DATA.SESSION_TIMESTAMP S
JOIN RAW_DATA.USER_SESSION_CHANNEL U
ON U.SESSIONID = S.SESSIONID
GROUP BY 1, 2
ORDER BY 1 DESC, 2;
SELECT
를 가지고 테이블을 생성하는 방법 CTAS
를 사용해서 조인해 두면 편하게 데이터를 조회할 수 있다.-- 기존에 해당 테이블이 있었다면 DROP하여 테이블 삭제
DROP TABLE IF EXISTS ADHOC.SESSION_SUMMARY;
-- CTAS를 이용해 ADHOC 데이터베이스에 테이블 생성
CREATE TABLE ADHOC.SESSION_SUMMARY
AS SELECT B.*
, A.TS
FROM RAW_DATA.SESSION_TIMESTAMP A
JOIN RAW_DATA.USER_SESSION_CHANNEL B
ON A.SESSIONID = B.SESSIONID
[SQL] 데이터 품질 확인
중요한 부분이라는 생각이 들어서 따로 포스팅 해 두었다.
1. GROUP BY (포지션 or 컬럼)
ORACLE
에서는 GROUP BY COLUMN_NAME만 지원하였고, SELECT를 통해 노출이 되는 COLUMN_NAME이라면 모두 GROUP BY 조건절에 들어가야 했다. 그렇지 않으면ORA-00937: 단일 그룹의 그룹 함수가 아닙니다
다음과 같은 오류가 발생했는데postgresql
의 경우 원하는 컬럼만을 그룹핑 할 수 있으며 1, 2, 3 등 숫자를 통해 SELECT 할 컬럼들의 포지션만으로 그룹핑이 가능하였다.- 예를 들어, 월별 사용자를 조회하는 쿼리라고 하면
ORACLE
에서는USERID
를 GROUP BY절에 그룹핑 기준으로 넣어 주어야 한다.-- ORACLE SELECT TO_CHAR(S.TS, 'YYYY-MM') AS MONTH , COUNT(U.USERID) AS MAU FROM RAW_DATA.USER_SESSION_CHANNEL U , RAW_DATA.SESSION_TIMESTAMP S WHERE U.SESSIONID = S.SESSIONID GROUP BY S.TS, U.USERID
- 그렇지만
postgresql
에서는MONTH
라는 새로 만들어진 컬럼을 포지션을 통해GROUP BY
해 줄 수 있다. 동일하게GROUP BY MONTH
를 해 주어도 같은 결과가 나온다.-- 강의에서 나온 쿼리 SELECT TO_CHAR(S.TS, 'YYYY-MM') AS MONTH , COUNT(DISTINCT U.USERID) AS MAU FROM RAW_DATA.SESSION_TIMESTAMP S JOIN RAW_DATA.USER_SESSION_CHANNEL U ON U.SESSIONID = S.SESSIONID GROUP BY 1
📚 과제
이 네 가지 테이블을 이용해서 채널별 월 매출액 테이블 만들기
Column
- month
- channel
- uniqueUsers (총 방문 사용자)
- paidUsers (구매 사용자: refund한 경우도 판매로 고려) - session_transaction에 존재하는 사용자
- conversionRate (구매 사용자/ 총 방문 사용자) - float 형식으로 소수점 단위가 나오도록 해야 함
- grossRevenue (Refund 포함) - amount 필드를 sum
- netRevenue (Refund 제외)
-- 내가 작성한 쿼리 SELECT TO_CHAR(B.TS, 'YYYY-MM') AS month , A.CHANNEL AS channel , COUNT(DISTINCT(CASE WHEN A.USERID IS NOT NULL THEN A.USERID END)) AS uniqueUsers --DISTINCT를 사용해 중복을 제거하면 NULL도 포함되므로 NULL인 경우는 COUNT 되지 않도록 제외 , COUNT(DISTINCT(CASE WHEN C.SESSIONID IS NOT NULL THEN C.SESSIONID END)) AS paidUsers , CONVERT(float, paidUsers) / CONVERT(float, uniqueUsers) conversionRate --convert를 사용하여 소수점 자리까지 계산되도록 , SUM(C.AMOUNT) AS grossRevenue , SUM(CASE WHEN C.REFUNDED THEN 0 ELSE C.AMOUNT END) AS netRevenue --refunded가 True면 환불받은 거라 카운트가 되지 않아야 함 FROM RAW_DATA.USER_SESSION_CHANNEL A , RAW_DATA.SESSION_TIMESTAMP B , RAW_DATA.SESSION_TRANSACTION C WHERE A.SESSIONID = B.SESSIONID (+) AND A.SESSIONID = C.SESSIONID (+) GROUP BY 1, 2 ORDER BY 1, 2
만약
LEFT JOIN절
을 사용한다면 다음과 같은 쿼리도 가능해 보인다.SELECT TO_CHAR(B.TS, 'YYYY-MM') AS month , A.CHANNEL AS channel , COUNT(DISTINCT(CASE WHEN A.USERID IS NOT NULL THEN A.USERID END)) AS uniqueUsers , COUNT(DISTINCT(CASE WHEN C.SESSIONID IS NOT NULL THEN C.SESSIONID END)) AS paidUsers , CONVERT(float, paidUsers) / CONVERT(float, uniqueUsers) conversionRate , SUM(C.AMOUNT) AS grossRevenue , SUM(CASE WHEN C.REFUNDED THEN 0 ELSE C.AMOUNT END) AS netRevenue FROM RAW_DATA.USER_SESSION_CHANNEL A LEFT JOIN RAW_DATA.SESSION_TIMESTAMP B ON A.SESSIONID = B.SESSIONID LEFT JOIN RAW_DATA.SESSION_TRANSACTION C ON A.SESSIONID = C.SESSIONID GROUP BY 1, 2 ORDER BY 1, 2
- 결과