4. Core Event를 "click_payment"라고 설정하고 Weekly Retention을 구해주세요. WITH base AS( SELECT event_date, event_timestamp, user_pseudo_id, event_name, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen, MAX(IF(ep.key = 'food_id', ep.value.int_value, NULL)) AS food_id, MAX(IF(ep.key = 'session_od', ep.value.string_value, NULL)) AS session_od FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep GROUP BY ALL ), payment AS( SELECT user_pseudo_id, event_timestamp, event_date, event_name, --firebase_screen, FROM base WHERE event_name = 'click_payment' ORDER BY event_date ), payment_first_week AS( SELECT *, DATE_TRUNC(MIN(event_date) OVER(PARTITION BY user_pseudo_id), WEEK(MONDAY)) AS first_week, DATE_TRUNC(event_date, WEEK(MONDAY)) AS event_week FROM( SELECT * EXCEPT(event_timestamp, event_date), DATE(DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul')) AS event_date FROM payment ) ), payment_user_cnt AS( SELECT diff_of_week, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM( SELECT *, DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week FROM payment_first_week ) GROUP BY diff_of_week ORDER BY diff_of_week ) ############################### 리텐션 비율 구하기 ############################### SELECT *, SAFE_DIVIDE(user_cnt, first_week_user_cnt) AS retention_rate FROM( SELECT *, FIRST_VALUE(user_cnt) OVER(ORDER BY diff_of_week) AS first_week_user_cnt FROM payment_user_cnt ) 여기까지는 payment 테이블 WHERE절에 event_name으로 "click_payment" 조건을 걸어준 것 빼곤 강의의 코드를 거의 그대로 사용했습니다. 결과는 이렇게 나왔고, 저는 diff_of_week가 4일 때 갑자기 user_cnt가 상승 했다가 5일 때 다시 이전 수치로 돌아간 것에 주목했습니다(약 18% 상승). SELECT event_week, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM( SELECT *, DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week FROM payment_first_week ) WHERE diff_of_week = 4 GROUP BY event_week ORDER BY user_cnt DESC 그래서 diff_of_week가 4일 때의 "click_payment" 이벤트를 발생시킨 유저의 수를 추출해보았습니다. 결과를 보았을 때, 2022-10-31 ~ 2023-01-09의 유저 수가 많음 을 확인하였고 이는, 해당 기간이 첫 주문 후 4주가 지난 사람들의 재주문 건수가 많았을 것 이라는 생각이 들었습니다. ##################### 해당 날짜, 이전, 이후의 데이터 추출 #################### # target_date 테이블: 해당 날짜를 만족하는 행만 추출 , target_date AS( SELECT * FROM base WHERE user_pseudo_id IN( SELECT user_pseudo_id FROM payment_first_week WHERE first_week BETWEEN '2022-10-03' AND '2022-12-12' ) ORDER BY user_pseudo_id, event_timestamp # before_target_date 테이블: 해당 날짜 이전 ), before_target_date AS( SELECT * FROM base WHERE user_pseudo_id IN( SELECT user_pseudo_id FROM payment_first_week WHERE first_week BETWEEN '2022-08-01' AND '2022-10-02' ) ORDER BY user_pseudo_id, event_timestamp # after_target_date 테이블: 해당 날짜 이후 ), after_target_date AS( SELECT * FROM base WHERE user_pseudo_id IN( SELECT user_pseudo_id FROM payment_first_week WHERE first_week BETWEEN '2022-12-13' AND '2023-12-31' ) ORDER BY user_pseudo_id, event_timestamp ) ################ click_payment를 발생시킨 유저와 그 때의 날짜 추출 ################ # payment_users 테이블: 해당 날짜에서 event_name칼럼에 "click_payment"를 가지고 있는 user_pseudo_id와 그 때의 event_date를 추출 , payment_users_target_date AS( SELECT event_date, user_pseudo_id FROM( SELECT *, CASE WHEN event_name = 'click_payment' THEN 1 ELSE 0 END AS payment_user FROM target_date ) WHERE payment_user = 1 ) # payment_users_before 테이블: 해당 날짜 이전의 payment user의 정보 ,payment_users_before AS( SELECT event_date, user_pseudo_id FROM( SELECT *, CASE WHEN event_name = 'click_payment' THEN 1 ELSE 0 END AS payment_user FROM before_target_date ) WHERE payment_user = 1 ) # payment_users_after 테이블: 해당 날짜 이후의 payment user의 정보 ,payment_users_after AS( SELECT event_date, user_pseudo_id FROM( SELECT *, CASE WHEN event_name = 'click_payment' THEN 1 ELSE 0 END AS payment_user FROM after_target_date ) WHERE payment_user = 1 ) 그래서 전체 날짜를 위의 해당 날짜와 그 전, 후 3개로 나누어 해당 기간의 정보만을 담는 테이블을 생성하고, 각 기간에 "click_payment" 이벤트를 가지고 있는 유저의 user_pseudo_id와 그 때의 event_date를 추출하였습니다. ################ 3개의 기간동안 사람들이 주문한 음식의 food_id 추출 ################ # 해당 기간동안 사람들이 주문한 food_id , food_cnt_target_date AS( SELECT food_id, COUNT(*) AS food_cnt FROM target_date td JOIN payment_users_target_date pu ON td.user_pseudo_id = pu.user_pseudo_id AND td.event_date = pu.event_date WHERE event_name = "click_cart" GROUP BY food_id ORDER BY food_cnt DESC --ORDER BY td.user_pseudo_id, event_timestamp ), # 해당 기간 이전 사람들이 주문한 food_id food_cnt_before_target AS( SELECT food_id, COUNT(*) AS food_cnt FROM before_target_date td JOIN payment_users_before pu ON td.user_pseudo_id = pu.user_pseudo_id AND td.event_date = pu.event_date WHERE event_name = "click_cart" GROUP BY food_id ORDER BY food_cnt DESC ), # 해당 기간 이후 사람들이 주문한 food_id food_cnt_after_target AS( SELECT food_id, COUNT(*) AS food_cnt FROM after_target_date td JOIN payment_users_after pu ON td.user_pseudo_id = pu.user_pseudo_id AND td.event_date = pu.event_date WHERE event_name = "click_cart" GROUP BY food_id ORDER BY food_cnt DESC ) 위에서 구한 id와 date를 이용해 세 기간동안 사람들이 주문한 음식과 음식 별 건수를 추출하였습니다. 전체 데이터를 살펴본 결과, "click_payment"는 항상 주문 마지막에 발생하는 이벤트이며, 이 row에는 food_id는 없고, "click_cart" 이벤트를 발생시켰을 때 해당 주문건에 대한 food_id가 row에 포함되어 있어 event_name을 "click_cart"로 지정하였습니다. ################ 각 food_id 별 해당 기간, 이전, 이후의 주문 건수 카운트 ################ , total_cnt AS( SELECT td.food_id, btd.food_cnt AS food_cnt_before_target_date, td.food_cnt AS food_cnt_target_date, atd.food_cnt AS food_cnt_after_target_date FROM food_cnt_target_date td JOIN food_cnt_before_target btd ON td.food_id = btd.food_id JOIN food_cnt_after_target atd ON td.food_id = atd.food_id ) ################ 각 food_id 별 해당 기간, 이전, 이후의 주문 건수 비율 ################ SELECT food_id, food_cnt_before_target_date, ROUND(SAFE_DIVIDE(food_cnt_before_target_date, SUM(food_cnt_before_target_date) OVER()), 4) AS rate_before, food_cnt_target_date, ROUND(SAFE_DIVIDE(food_cnt_target_date, SUM(food_cnt_target_date) OVER()), 4) AS rate_target, food_cnt_after_target_date, ROUND(SAFE_DIVIDE(food_cnt_after_target_date, SUM(food_cnt_after_target_date) OVER()), 4) AS rate_after FROM total_cnt ORDER BY rate_target DESC 세 기간동안 사람들이 주문한 food_id와 그 횟수, 비율을 구해보았습니다. 각 음식의 비율은 매우 적지만 target_date를 기준으로 정렬해 보았을 때, 확실히 해당 기간동안 주문량이 높고 나머지 기간에는 주문량이 낮은 음식들이 존재 했습니다(ex. 1438, 1516 등). 하지만, 이것이 4주전 주문자들의 실제 재주문 결과인지, 단지 해당기간 동안 신규 유저가 늘었기 때문인지 확실하지 않다고 생각문자와 그 외 기간동안의 신규 주문자를 비교해 보았습니다. SELECT new_user_target_date, COUNT(*) AS new_user_cnt FROM( SELECT CASE WHEN first_week BETWEEN '2022-10-31' AND '2023-01-09' THEN 1 ELSE 0 END AS new_user_target_date FROM payment_first_week ) GROUP BY new_user_target_date 해당 기간의 신규 유저수는(new_user_target_date = 1) 6261명, 그 외 기간의 신규 유저수는 6898명으로, 전체 기간 대비 해당 기간의 비율을 고려해보았을 때, 신규 유저가 크게 늘었다고 보기 어려웠습니다. 따라서, 2022~10-03 ~ 2022-12-12일이 포함된 주차에 첫 주문을 한 고객들의 만족도가 높았거나, 그 당시에 시행했던 첫 주문 관련 마케팅, 광고 효과가 좋았을 것 이라고 판단할 수 있었습니다. ** 앱 로그 데이터를 처음 접했기 때문에 코드 오류나 논리적인 비약이 있을 것이라고 생각하지만, 단지 Weekly Retention에 WHERE 조건을 추가하는 것에 그치기 아쉬워서(?) 의식의 흐름대로 분석해보았습니다.
Weekly 리텐션 분석 #Weekly 리텐션 with base as ( select distinct user_id, user_pseudo_id, event_name, date(datetime(timestamp_micros(event_timestamp), "Asia/Seoul")) as event_date, datetime(timestamp_micros(event_timestamp), "Asia/Seoul") as event_datetime, from `advanced.app_logs` where event_date between "2022-08-01" and "2022-11-03" ), retain_base as ( select user_pseudo_id, event_week, first_week, date_diff(event_week, first_week, week) as diff_of_week from ( select distinct user_pseudo_id, date_trunc(event_date, week(monday)) as event_week, date_trunc(min(event_date) over(partition by user_pseudo_id),week(monday)) as first_week, -- 유저의 첫 접속 주차 from base ) ) select diff_of_week, user_cnt, first_value(user_cnt) over(order by diff_of_week) as first_user_cnt from ( select diff_of_week, count(distinct user_pseudo_id) as user_cnt from retain_base group by diff_of_week ) order by diff_of_week Montly 리텐션 분석 #Montly 리텐션 with base as ( select distinct user_id, user_pseudo_id, event_name, date(datetime(timestamp_micros(event_timestamp), "Asia/Seoul")) as event_date, datetime(timestamp_micros(event_timestamp), "Asia/Seoul") as event_datetime, from `advanced.app_logs` where event_date between "2022-08-01" and "2022-11-03" ), retain_base as ( select user_pseudo_id, event_month, first_month, date_diff(event_month, first_month, month) as diff_of_month from ( select distinct user_pseudo_id, date_trunc(event_date, month) as event_month, date_trunc(min(event_date) over(partition by user_pseudo_id),month) as first_month, -- 유저의 첫 접속 주차 from base ) ), month_retain as ( select diff_of_month, user_cnt, first_value(user_cnt) over(order by diff_of_month) as first_user_cnt from ( select diff_of_month, count(distinct user_pseudo_id) as user_cnt from retain_base group by diff_of_month ) ) select diff_of_month, user_cnt, first_user_cnt, round(safe_divide(user_cnt, first_user_cnt),3) as retention_rate from month_retain order by diff_of_month 위클리랑 먼슬리 리텐션 쿼리 작성했습니다~
문제풀이를 해가던 도중 마지막 ROWS BETWEEN을 이용한 5가지 AMOUNT의 AVG를 구할때 소수점이 나오는데 SELECT절에 적힌 WINDOW 함수 쿼리를 ROUND, FLOOR 을 사용해서 감싸줘도 작동은 하지만 소수점이 사라지지 않는데 혹시 그 이유가 뭔가요???
질문을 남겨주실 때 아래 부분을 같이 남겨주시면 더 빠르게 상황을 파악하고 답변드릴 수 있어요 🙂 어떤 강의를 들으면서 발생했나요? BigQuery 활용편 어떤 문제가 생겼나요? 디스코드 접속 불가 어떤 시도를 해보셨나요? 강의 중 제공되는 링크로 디스코드 접속시 무한 로딩 발생 어떤 쿼리를 사용했나요? - 환경이 어떻게 되나요?(윈도우, 맥 중 택1, 크롬 같은 웹브라우저 환경) 윈도우
안녕하세요. 강의에서는 LEFT JOIN ... ON ... 하고 나서 WHERE type1='Grass'으로 먼저 필터링을 하신 것 같은데요! 아래 처럼 GROUP BY - HAVING으로 해도 결과는 동일한데, GROUP BY 보다 WHERE를 먼저 했을 때 수행 속도 등의 이득이 있는지 궁금합니다! SELECT type1, COUNT(type1) AS pokemon_cnt FROM ( SELECT id, trainer_id, pokemon_id, status FROM `basic.trainer_pokemon` WHERE status IN ('Active', 'Training') ) AS tp LEFT JOIN `basic.pokemon` AS p ON tp.pokemon_id = p.id GROUP BY type1 HAVING type1 = 'Grass'
안녕하세요! 기본편에 이어 활용편을 열심히 듣고 있는 학생입니다. 제 브라우저나 pc의 문제인지는 모르겠지만, 강의 중간에 쿼리를 복사 붙여넣기 해야 되는 부분들이 있을 때, 인프런 플레이어에서 문자인식 후 복사하는 기능을 제공하지 않습니다.. 그래서 따로 캡쳐를 따서 문자를 인식한 후, 복사 붙여넣기를 해야 하는데요. 이럴 때도 정확도가 좋지 않아서 일일이 수기로 다 수정을 해야합니다. 혹시 강의 중간에 복사 붙여넣어 사용해야할 쿼리가 있다면 혹시 해당 강의에 강의 자료 부분에 텍스트로 올려놔주실 수 있을까요? 그렇다면 많은 학생들이 쿼리를 쉽게 복사, 붙여넣어 강의를 빠르게 들을 수 있을 것 같습니다. 항상 열심히 강의 해주시고 답변해주셔서 감사합니다!
연습문제 풀던 중에 궁금한 게 생겨서 여쭤봅니다! 12번에서 단일 포켓몬 중 많은 타입 1을 구할 때, 지금 데이터에선 type2가 null인 경우만 있지만, type1도 null인 경우도 있다고 가정하면, where (type2 is null) or (type1 is null) 처럼 or 조건으로 표현하면 될까요?
Weekly Retention은 diff_of_week 을 활용하여, 시간의 경과에 따른 리텐션 변화를 구합니다. 따라서, 제가 생각했을 때, Weekly Retention을 구하는 쿼리에서는 다음과 같은 가정을 하고 진행한 것이 아닐까? 라는 생각이 들었습니다. 주차별로(시간에 따라) 활동 중인 사용자 수는 달라질 것이다. user_type에 따라 활동 중인 사용자 수는 차이가 있을 것이다. 2.의 경우는 만약의 신규/복귀... 유저를 구분한다면, 해당 가설을 기반으로, Weekly Retention을 구하는 행위를 한 것이 아닐까? 라는 생각이 들었습니다. (추가 궁금증) Retention에 영향을 주는 인자를 분석하는 경우도 있을까요? (실무에서) 저는 Retention을 분석하기 전에, Retention과 관련이 높은 것이 무엇일지, 가설을 세우고 검정을 해보았습니다. 가설 : 방문일수는 Retention에 높은 상관관계를 가진다. 데이터 범위: 2022-08-01 ~ 2022-11-01 D7_retention : (bool) 사용자의 첫번째 이벤트 시점 ~ 7일 이후에도 활동을 하면, 1 아니면 0 D30_retention : (bool) 사용자의 첫번째 이벤트 시점 ~ 30일 이후에도 활동을 하면, 1아니면 0 import os from google.cloud import bigquery from google.oauth2 import service_account import pandas as pd import statsmodels.api as sm from scipy.stats import pointbiserialr import numpy as np os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './service_account.json' key_path = './service_account.json' credentials = service_account.Credentials.from_service_account_file( key_path, scopes = ["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client(credentials=credentials, project=credentials.project_id, location="US") query = """ WITH user_visits AS ( SELECT user_pseudo_id, COUNT(DISTINCT event_date) AS visit_days FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-01' GROUP BY user_pseudo_id ), retention_data AS ( SELECT user_pseudo_id, MIN(event_date) AS first_event_date, MAX(event_date) AS last_event_date, CASE WHEN MAX(event_date) >= DATE_ADD(MIN(event_date), INTERVAL 7 DAY) THEN 1 ELSE 0 END AS D7_retention, CASE WHEN MAX(event_date) >= DATE_ADD(MIN(event_date), INTERVAL 30 DAY) THEN 1 ELSE 0 END AS D30_retention FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-01' GROUP BY user_pseudo_id ), combined_data AS ( SELECT v.user_pseudo_id, v.visit_days, r.D7_retention, r.D30_retention FROM user_visits v JOIN retention_data r ON v.user_pseudo_id = r.user_pseudo_id ) SELECT * FROM combined_data; """ df = client.query(query).to_dataframe() df['visit_days'] = pd.to_numeric(df['visit_days'], errors='coerce').astype(np.float64) df['D7_retention'] = pd.to_numeric(df['D7_retention'], errors='coerce').astype(np.float64) df['D30_retention'] = pd.to_numeric(df['D30_retention'], errors='coerce').astype(np.float64) # 결측치가 있는지 확인하고 제거 df = df.dropna(subset=['visit_days', 'D7_retention', 'D30_retention']) # 상수항 추가 X = sm.add_constant(df[['visit_days']]) # D7_retention에 대한 로지스틱 회귀 모델 적합 y_D7 = df['D7_retention'] logit_model_D7 = sm.Logit(y_D7, X).fit() print(logit_model_D7.summary()) # D30_retention에 대한 로지스틱 회귀 모델 적합 y_D30 = df['D30_retention'] logit_model_D30 = sm.Logit(y_D30, X).fit() print(logit_model_D30.summary()) visit_days_range = np.linspace(df['visit_days'].min(), df['visit_days'].max(), 100) prob_D7 = logit_model_D7.predict(sm.add_constant(visit_days_range)) prob_D30 = logit_model_D30.predict(sm.add_constant(visit_days_range)) plt.plot(visit_days_range, prob_D7, label='D7 Retention Probability') plt.plot(visit_days_range, prob_D30, label='D30 Retention Probability', linestyle='--') plt.xlabel('Visit Days') plt.ylabel('Retention Probability') plt.title('Retention Probability vs Visit Days') plt.legend() plt.show() 따라서, 방문일수는 Retention과 상관성을 보인다. 가설2. 방문일 수는 user_type에 따라 각기 다른 상관성을 보일 것이다. 결론: user_type은 new_user, current_user는 통계적으로 유의하며, 높은 상관성을 가지나, 휴면 유저, 복귀 유저는 통계적으로 유의미하지 않으며, 낮은 상관성을 띈다. 이렇게 결론을 내놓는 방식이, 적합한 방식인지 궁금합니다.
안녕하세요 카일스쿨님! 총 정리 문제 2번의 쿼리 결과를 보고 문뜩 궁금증이 생겨 질문 남깁니다. 이렇게 쿼리를 짜서 카일스쿨님이 문제 풀이해주신 것처럼 같은 결과가 나왔습니다. 다만, 현재 GROUP BY가 타입1과 한글 이름 두 가지로 기준을 잡고 있어서 저는 타입 1을 기준으로 가장 많은 포켓몬 수가 나올지, 아니면 한글이름 기준으로 가장 많은 포켓몬 수가 나올지 궁금해 했었는데, 한글 이름 기준으로 가장 많은 포켓몬 수가 나오더라구요! 이러한 경우에는 왜 타입1과 한글 이름 두 가지가 다 기준으로 잡혀있는데, 타입1 기준이 아니라 한글 이름 기준으로 가장 많은 포켓몬 수가 출력되는 걸까요? 참고로 타입 1 기준으로 가장 많은 포켓몬 수를 구해봤더니 1순위는 Water로 나왔습니다.
카일님, 안녕하세요! LEFT JOIN 기준이 헷갈려 이에 대한 질문 드립니다. LEFT JOIN 시 테이블의 데이터가 더 많은 테이블을 제일 왼쪽으로 두면 된다고 이해하면 될까요? 또한, 예외도 있다고 하셨는데, 어떤 경우인지도 알 수 있을까요? 마지막으로 LEFT말고 다른 JOIN할 때도 테이블의 데이터가 더 많은 테이블을 기준으로 잡아도 괜찮나요? 감사합니다!
pdf파일 158페이지, 159페이지에 있는 where과 having 쿼리 예시문을 보다가 궁금한 게 생겨서요. where 쿼리문 : select 컬럼 1, 컬럼2라고 돼 있는데 group by 컬럼1, 컬럼2 라고 하지 않아도 실행이 가능한가요? having 쿼리문 : group by하는(그룹화 기준) 컬럼과 count 컬럼이 같을 수도 있나요? count (컬럼3)이어야 하지 않는지 궁금합니다!
최근에, 면접이 있어서 공부를 제대로 못했네요. 오늘부터 매일 매일 못했던 공부를 다시 해보려고 합니다. 문제: core_event를 'click_payment'라고 설정하고, Weekly Retention을 구하여라. 저는 click_payment 까지의 사용자 여정 별로 세그먼트를 나누려는 시도를 해보았습니다. 문제에 대한 고민사항은 길어지니, 최종적으로 나누었던 세그먼트를 먼저 소개해드리겠습니다. click_payment 까지의 여정은 크게 다음과 같이 구성할 수 있습니다. click_search(검색) -> click_payment click_banner(배너 클릭) -> click_payment click_food_category(음식 카테고리 클릭) -> click_payment click_restaurant_nearby( 내 위치 기반 주변 레스토랑) -> click_payment click_recommend_food(추천) -> click_payment 크게 다음과 같이 5개의 세그먼트로 나누어보고, 각 세그먼트 별로, Count를 해보았습니다. 이 때, click_login -> click_search -> .... -> click_payment -> click_search .... 이런 경우를 대비해서, 각 이벤트마다 제일 처음 이벤트가 발생한 시간 을 기준으로 구분하도록 하였습니다. 이제 각 퍼널별로 리텐션을 구해보겠습니다. WITH user_events AS ( SELECT user_pseudo_id, event_name, event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id, event_name ORDER BY event_timestamp) AS r_num FROM advanced.app_logs WHERE event_name IN ('click_search', 'click_banner', 'click_food_category', 'click_restaurant_nearby', 'click_recommend_food', 'click_payment') AND event_name NOT IN ('screen_view', 'click_login') ), FIRST_EVENTS AS ( SELECT *, DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week FROM ( SELECT user_pseudo_id, event_date, DATE_TRUNC(MIN(event_date) OVER (PARTITION BY user_pseudo_id), WEEK(MONDAY)) AS first_week, DATE_TRUNC(event_date, WEEK(MONDAY)) AS event_week, MIN(CASE WHEN event_name = 'click_search' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_search_time, MIN(CASE WHEN event_name = 'click_payment' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_payment_time, MIN(CASE WHEN event_name = 'click_banner' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_banner_time, MIN(CASE WHEN event_name = 'click_food_category' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_food_category_time, MIN(CASE WHEN event_name = 'click_restaurant_nearby' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_restaurant_nearby_time, MIN(CASE WHEN event_name = 'click_recommend_food' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_recommend_food_time FROM user_events ) ), FUNNEL_CLASSIFICATION AS ( SELECT DISTINCT *, CASE WHEN first_search_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_search_time < first_payment_time THEN 'click_search -> click_payment' WHEN first_banner_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_banner_time < first_payment_time THEN 'click_banner -> click_payment' WHEN first_food_category_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_food_category_time < first_payment_time THEN 'click_food_category -> click_payment' WHEN first_restaurant_nearby_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_restaurant_nearby_time < first_payment_time THEN 'click_restaurant_nearby -> click_payment' WHEN first_recommend_food_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_recommend_food_time < first_payment_time THEN 'click_recommend_food -> click_payment' ELSE 'Other' END AS funnel FROM FIRST_EVENTS WHERE first_payment_time IS NOT NULL ), PIVOTED_ANALYSIS AS ( SELECT diff_of_week, COUNT(DISTINCT CASE WHEN funnel = 'click_search -> click_payment' THEN user_pseudo_id END) AS user_cnt_search_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_banner -> click_payment' THEN user_pseudo_id END) AS user_cnt_banner_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_food_category -> click_payment' THEN user_pseudo_id END) AS user_cnt_food_category_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_restaurant_nearby -> click_payment' THEN user_pseudo_id END) AS user_cnt_restaurant_nearby_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_recommend_food -> click_payment' THEN user_pseudo_id END) AS user_cnt_recommend_food_payment FROM FUNNEL_CLASSIFICATION GROUP BY diff_of_week ), INITIAL_USERS AS ( SELECT FIRST_VALUE(user_cnt_search_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_search_payment, FIRST_VALUE(user_cnt_banner_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_banner_payment, FIRST_VALUE(user_cnt_food_category_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_food_category_payment, FIRST_VALUE(user_cnt_restaurant_nearby_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_restaurant_nearby_payment, FIRST_VALUE(user_cnt_recommend_food_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_recommend_food_payment FROM PIVOTED_ANALYSIS LIMIT 1 ) SELECT pa.diff_of_week, pa.user_cnt_search_payment AS search_active_user, pa.user_cnt_banner_payment AS banner_active_user, pa.user_cnt_food_category_payment AS category_active_user, pa.user_cnt_restaurant_nearby_payment AS nearby_active_user, pa.user_cnt_recommend_food_payment AS recommend_active_user, iu.first_user_cnt_search_payment AS search_cohort_user, iu.first_user_cnt_banner_payment AS banner_cohort_user, iu.first_user_cnt_food_category_payment AS category_cohort_user, iu.first_user_cnt_restaurant_nearby_payment AS nearby_cohort_user, iu.first_user_cnt_recommend_food_payment AS recommend_cohort_user, ROUND(SAFE_DIVIDE(pa.user_cnt_search_payment, iu.first_user_cnt_search_payment), 3) AS retention_week_rate_search_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_banner_payment, iu.first_user_cnt_banner_payment), 3) AS retention_week_rate_banner_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_food_category_payment, iu.first_user_cnt_food_category_payment), 3) AS retention_week_rate_food_category_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_restaurant_nearby_payment, iu.first_user_cnt_restaurant_nearby_payment), 3) AS retention_week_rate_restaurant_nearby_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_recommend_food_payment, iu.first_user_cnt_recommend_food_payment), 3) AS retention_week_rate_recommend_food_payment FROM PIVOTED_ANALYSIS pa, INITIAL_USERS iu ORDER BY pa.diff_of_week; 1000자 이내로 작성해야 게시글 하나를 쓸 수 있음.. screen_view -> screen_view -> click_login의 경우, 사용자가 로그인을 하지 않고, 앱이 잠시 백그라운드 상에 동작중인 상태에서, 다시 앱을 켰을 때, screen_view 로그가 찍히는 것을 확인했습니다. 현재 집중해야 할 부분은 사용자가 상품을 들여다보는 시간이나, item을 찾을 때, UI/UX 적으로 개선할 부분이 있는지 찾기보다, click_payment를 하기까지의 주요 이벤트 여정을 세그먼트로 분류하는 작업을 하고 있기 때문에, screen_view 이벤트는 제외해야겠다는 생각을 했습니다. 또한, screen_view -> click_login -> screen_view -> screen_view -> click_login 처럼, 이전 이벤트와 현재 이벤트가 같은 경우를 제외한 다른 경우만을 필터링해서, 굵직한 이벤트만을 필터링해보자! 라는 생각을 가졌었습니다. --- 1000자 이내로 작성해야 글이 올ㅠ 퍼널의 수가 열 몇개로 나오지 않을까? 하는 예상과 다르게 총 433개의 단계가 나왔습니다. click_login -> click_food_category -> click_restaurant -> click_food -> click_cart -> view_recommend_extra_food -> click_payment 와 같이 click_login ----- > click_payment까지의 여정의 가짓수가 너무 많아, 단계를 단순화할 필요성이 있어보였습니다. 그래서, 위의 결과와 같이 총 5개로 퍼널을 나누었습니다.
안녕하세요 카일스쿨님! 카일스쿨님께서 만들어주신 해당 교육 너무 너무 잘 듣고 있습니다. 설명도 알기 쉽게 너무 잘 해주시고, 이론적으로 배웠던 부분 실무에서도 사용할 수 있도록 예제 출제와 풀이도 같이 해주셔서 정말 탄탄하게 학습하고 있는 것 같습니다. 다만, 제가 공부하면서 궁금한 지점이 있어 이렇게 질문 글을 남깁니다. 저는 SQL공부를 위해서 해당 강의를 시청하고 있는데요! 혹시 빅쿼리에서 사용하는 SQL문법이나 SQL작성방법이 MYSQL이나 ORACLE에서는 많이 다를까요? 아니면 서로 범용할 수 있을까요? SQL 공부를 하기 위해 해당 강의를 듣고 있는데, 혹시나 빅쿼리에 한정된 공부를 하고 있는지 궁금해서 질문 남깁니다. 좋은 강의자료와 강의, 그리고 질문에 대한 빠른 답변 항상 정말 감사합니다. 날씨 더운데 건강 조심하시고, 저도 빠르게 기본강의 듣고 활용으로 넘어가고 싶습니다! 감사합니다!
SELECT, FROM, ORDER BY, LIMIT 실습 파트 중입니다! mbtitest 테이블을 mbti_test로 다르게 설정하기는 했지만 다른 부분에 있어서는 동일하게 진행했습니다! 그런데 컬럼 추출하려니까 에러가 떠서요 그레이브를 잘 못 입력했나 싶기는 한데... 한 번 봐주시면 감사드리겠습니다! 🙏 T, F, ORDER BY, LIMIT 실습T,M, ORDER BY, LIMIT 실 습
카일님, 안녕하세요! 시간 데이터 3번 문제 궁금한게 있어 질문드립니다. 저는 해당 문제를 서브쿼리 없이 작성했는데, 서브쿼리 없이 작성해도 문제가 없나요? 그리고 서브쿼리는 어떤 경우에 사용해야 하는지 기준이 있을까요?? SELECT trainer_id, FORMAT_DATE("%d/%m/%Y", MIN(DATE(catch_datetime, 'Asia/Seoul'))) as first_date FROM basic.trainer_pokemon GROUP BY trainer_id ORDER BY trainer_id;