실습위주로 진행됨
GROUP BY는 테이블 레코드들을 컬럼값 기준으로 그룹핑해서 그룹별로 다양한 정보를 계산할 때 사용됨.
ex) COUNT, SUM, AVG, MIN, MAX, LISTATT 등
보통은 하나의 필드를 가지고 그룹핑을 하지만 여러 개의 필드가지고 그룹핑 할 수도 있음.
Aggregate 함수 적용하면 alias써서 새로운 결과에 대한 필드이름을 붙이는게 일반적임.
이전과 동일한 테이블 사용
| Table | Fields |
|---|---|
| session_timestamp | sessionid (string), ts (timestamp) |
| user_session_channel | userid (integer), sessionid (string), channel (string) |
| session_transaction | sessionid (string), refunded (boolean), amount (integer) |
| channel | channelname (string) |
1. 월별 세션 수 계산
%%sql
SELECT
LEFT(ts, 7) AS mon, -- 일 별로 보고싶으면 7이 아니라 10으로 변경하면 됨
COUNT(1) AS session_count
FROM raw_data.session_timestamp
GROUP BY 1 -- GROUP BY mon / GROUP BY LEFT(ts,7) 이렇게 써도 됨
ORDER BY 1;

LEFT는 문자열만 받을 수 있는 함수인데 ts같이 날짜가 들어오면 알아서 문자열로 CAST 됨.
GROUP BY mon이기 때문에 month로 그룹핑이 되고, 같은 그룹내에 속한 레코드들의 카운트를 진행한 예시.
2. 가장 많이 사용된 채널
주의할 점은 가장 많다는 것의 정의를 확실히 해야한다는 점.
(사용자 기반인지 사용자 관계없이 세션 기반인지와 같은)
실습에서는 2가지로 작성해봄.
1) 사용자가 많은 채널
2) 세션이 많은 채널
2가지 경우 동시에 보는 코드는 다음과 같음.
%%sql
SELECT
channel,
COUNT(1) AS session_count,
COUNT(DISTINCT userId) AS user_count
FROM raw_data.user_session_channel
GROUP BY 1 -- GROUP BY channel
ORDER BY 2 DESC; -- ORDER BY session_count DESC

3. 가장 많은 세션을 만들어낸 사용자 id
사용자 id를 가지고 그룹핑을 해야하는 예시
필요한 정보는 session, 사용자 정보!
%%sql
SELECT
userId,
COUNT(1) AS count
FROM raw_data.user_session_channel
GROUP BY 1 -- GROUP BY userId
ORDER BY 2 DESC -- ORDER BY count DESC
LIMIT 1;

userid로 그룹핑하고 그룹내에서 레코드 수 count 진행.
ORDER BY 내림차순하고 LIMIT 1하면 제일 큰 값 보겠다는 의미.
4. 월별 유니크한 사용자 수(MAU - Monthly Active User)
월별로 사용자가 몇 번을 방문했는지 알아보는 예시
(사용자가 한달에 몇 번을 방문해도 한 번으로 카운트)
%%sql
SELECT
TO_CHAR(A.ts, 'YYYY-MM') AS month,
COUNT(DISTINCT B.userid) AS mau
FROM raw_data.session_timestamp A
JOIN raw_data.user_session_channel B ON A.sessionid = B.sessionid
GROUP BY 1
ORDER BY 1 DESC;

방문 시기와 사용자 정보 얻어야해서 user_session_channel 테이블과 session_timestamp 테이블을 JOIN 해야함(JOIN 관련해서는 다음 시간에 자세히 소개 예정).

COUNT(DISTINCT B.userid) AS mau 해당 구문에 DISTINCT 안쓰면 session 수와 동일한 결과 값이 나옴.

TO_CHAR은 문자열이 아닌 필드를 인자로 받아서 문자열로 바꿔주는 함수
TO_CHAR 함수와 차이는 있지만 동일한 역할을 하는 함수들
LEFT(A.ts, 7)
DATE_TRUNC('month', A.ts)
SUBSTRING(A.ts, 1, 7)
AS는 alias로 필드/테이블 이름에 붙일 수 있고 이해하기 쉬운 이름을 붙이는게 일반적임.
AS는 필수는 아니고 생략 가능!
5. 월별 채널별 유니크한 사용자 수
4번 실습에서 GROUP BY 하는 필드가 하나 더 늘어나고 나머지는 동일함.
%%sql
SELECT
TO_CHAR(A.ts, 'YYYY-MM') AS month,
channel,
COUNT(DISTINCT B.userid) AS mau
FROM raw_data.session_timestamp A
JOIN raw_data.user_session_channel B ON A.sessionid = B.sessionid
GROUP BY 1, 2
ORDER BY 1 DESC, 2;

4번 실습에서는 월별로 그룹핑했는데 채널까지 해서 더 세세하게 그룹핑하는 예시.
그렇게 그룹핑하고 나서 유니크한 사용자 수 구한 것.
앞 문제와 차이는 GROUP BY, ORDER BY에 필드 하나가 아닌 두 개를 사용해 본 것.
CTAS는 SELECT를 가지고 새로운 테이블을 생성하는 방법
언제 유용한가?
데이터 엔지니어들이 ETL을 해서 외부 데이터를 데이터 웨어하우스에 저장하면 데이터 웨어하우스 2단계 구조에서 raw_data 스키마 밑에 테이블을 저장하게 됨.
그러한 테이블들을 JOIN 해야하는 경우가 발생하고 이러한 JOIN 한 새로운 테이블을 만들어두면 분석하는 코드가 훨씬 간단해짐.
실습은 각자 부여된 스키마 밑에 테이블을 저장하면서 진행.
1. CTAS : SELECT를 가지고 새로운 테이블 생성
%%sql
DROP TABLE IF EXISTS mkflower0917.mikyung_session_summary;
CREATE TABLE mkflower0917.mikyung_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;
2. 월별 유니크한 사용자 수
이전에 했던 월별 유니크한 사용자 수를 생성된 새로운 테이블을 바탕으로 풀어봄.
%%sql
SELECT
TO_CHAR(ts, 'YYYY-MM') AS month,
COUNT(DISTINCT userid) AS mau
FROM mkflower0917.mikyung_session_summary -- 만들어 둔 테이블
GROUP BY 1
ORDER BY 1 DESC;
새로운 테이블을 사용하게 되면 다양한 형태로 테이블의 데이터 품질을 확인해봐야함.
4가지 방법이 존재
CTAS 통해 테이블을 새로 만들었으니 해당 테이블을 검증해봄
(강사님이 만들어둔 테이블은 위 조건에 걸리지 않게 만들어놔서 문제는 없을 것이지만 방법에 대한 연습으로 생각하고 실습진행)
1. 중복된 레코드 체크
1) 우선 주어진 테이블의 모든 레코드 수를 카운트하고 중복제거한 레코드 수를 카운트해서 비교
%%sql
SELECT COUNT(1) FROM mkflower0917.mikyung_session_summary;

아래는 중복제거 후 count ver.
%%sql
SELECT COUNT(1)
FROM (
SELECT DISTINCT userId, sessionId, ts, channel FROM mkflower0917.mikyung_session_summary
); -- 중복 제거 후 다시 count

두 개의 결과가 동일하게 101520이 나왔기 때문에 중복된 데이터가 없다는 것을 의미
중복 제거 코드를 FROM에 nested하지 않고 WITH 문 쓰는 코드로도 가능
%%sql
-- FROM에 nested하지 않고 WITH문 써서 한 경우
With ds AS (
SELECT DISTINCT userId, sessionId, ts, channel FROM mkflower0917.mikyung_session_summary
)
SELECT COUNT(1)
FROM ds;
위에서는 FROM안에 SELECT 문을 넣었는데 그렇게 안하고 WITH 등을 써서 위로 빼내는 경우를 CTE라고 부름.
WITH 새로운 테이블 이름 as (SELECT문) 형태.
이렇게하면 WITH의 결과가 새로운 테이블 이름을 갖는 임시 테이블로 생성됨.
새로운 테이블을 밑에서 빼내어 중복 사용 가능함!
2. 최근 데이터 존재여부 체크
ts 필드에서 가장 작은값과 가장 큰 값을 체크함.
그러면 언제부터 언제까지 레코드를 갖고있는지 파악 가능.
%%sql
SELECT MIN(ts), MAX(ts)
FROM mkflower0917.mikyung_session_summary;

3. Primary key uniqueness 지켜지는지 체크
예시에서는 sessionid가 primary key가 됨.
sessionid를 가지고 GROUP BY를 하면 같은 sessionid끼리 모이는데, 그것을 count한 값이 항상 1이 되어야 uniqueness가 지켜진거고 값이 1보다 크면 중복이 있다는 의미.
%%sql
SELECT sessionId, COUNT(1)
FROM mkflower0917.mikyung_session_summary
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1; -- 가장 큰 값 하나만 뽑음

4. 값이 비어있는 컬럼 있는지 체크
CASE WHEN 구문써서 컬럼이 NULL이면 카운트하는 방식을 사용.
%%sql
SELECT
COUNT(CASE WHEN sessionId is NULL THEN 1 END) sessionid_null_count,
COUNT(CASE WHEN userId is NULL THEN 1 END) userid_null_count,
COUNT(CASE WHEN ts is NULL THEN 1 END) ts_null_count,
COUNT(CASE WHEN channel is NULL THEN 1 END) channel_null_count
FROM mkflower0917.mikyung_session_summary;

CASE WHEN 구문에서 ELSE문이 없는데 이럴 경우 NULL이 return됨.
(즉, 컬럼값이 NULL이면 1, NULL이면 NULL이 리턴되는 형태)
COUNT함수는 NULL을 세지 않기 때문에 컬럼값이 NULL이 아닌 경우만 카운트 됨.
결과는 NULL 값이 없기 때문에 전부 0이 리턴된 것.
강사님이 빈 값이 없게 테이블을 만들어 놓으셔서 그런거고 현업에서는 비어있는 값이 존재함.
채널별 월별 매출액 테이블 만들기
session_timestamp
user_session_channel
session_transaction
위 3개의 테이블 사용하여 숙제 진행.
다음과 같은 필드로 구성할 것.
| 컬럼 |
|---|
| month |
| channel |
| uniqueUser(총 방문자) |
| paidUsers(구매자) - refund한 경우도 판매로 고려 |
| conversionRate(구매자 / 총 방문자) |
| grossRevenue(총 수입) - refund 포함 |
| netRevenue - refund 제외한 수입 |
paidUser는 session_transaction 테이블에 나타나는 user를 말함.
고려할 점은 session_transaction 테이블에는 모든 세션이 존재하지는 않는다는 점(sessionid 가지고 JOIN 시 INNER JOIN말고 LEFT JOIN해야함)