작성
·
44
·
수정됨
0
(1) 연습문제 1
-- 문제 1) USER의 다음 접속월, 다다음 접속 월
SELECT
user_id,
visit_month,
LEAD(visit_month,1) OVER(PARTITION BY user_id ORDER BY visit_month) AS next_month,
LEAD(visit_month,2) OVER(PARTITION BY user_id ORDER BY visit_month) AS the_month_after_next
FROM
`avdanced.analytics_function_01`
(2) 연습문제 2
-- 문제 2) USER의 다음 접속월, 다다음 접속 월, 이전 접속 월
SELECT
user_id,
visit_month,
LEAD(visit_month,1) OVER(PARTITION BY user_id ORDER BY visit_month) AS next_month,
LEAD(visit_month,2) OVER(PARTITION BY user_id ORDER BY visit_month) AS the_month_after_next,
LAG(visit_month,1) OVER(PARTITION BY user_id ORDER BY visit_month) AS last_month
FROM
`avdanced.analytics_function_01`
연습문제 (1~4)
SELECT
-- 1)모든 주문량
SUM(amount) OVER() AS amount_total,
-- 2)특정주문시점에서 누적주문량
#SUM(amount) OVER(partition by order_date) AS cumulative_sum,
SUM(amount) OVER (ORDER BY order_date) AS cumulative_sum,
-- 3)고객별 주문 시점에서 누적 주문량
#SUM(amount) OVER(partition by user_id) AS cumulative_sum_by_user,
SUM(amount) OVER(partition by user_id ORDER BY order_id) AS cumulative_sum_by_user,
-- 4) 최근 직전 5개 평균 주문량
AVG(amount) OVER(ROWS BETWEEN 5 PRECEDING AND 1 PRECEDING) AS last_5_orders_avg_amount,
AVG(amount) OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS last_5_unbounded_orders_avg_amount,
AVG(amount) OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS all_orders_avg_amount
FROM `avdanced.orders`
(1) 연습문제 1
-- 연습문제1) 사용자별 쿼리 실행 횟수
WITH base AS(
SELECT
user,
team,
query_date,
COUNT(*) OVER(PARTITION BY user) AS total_query_cnt,
FROM
`avdanced.query_logs`
)
SELECT
*
FROM
base
(2) 연습문제 2
-- 연습문제2) 주차별 팀내 쿼리 실행한 수 (RANK 1만 보이도록)
WITH base2 AS(
SELECT
EXTRACT(WEEK FROM query_date) AS week_number,
team,
user,
COUNT(*) OVER(PARTITION BY EXTRACT(WEEK FROM query_date) ,user ORDER BY EXTRACT(WEEK FROM query_date) ) AS query_cnt,
FROM
`avdanced.query_logs`
ORDER BY EXTRACT(WEEK FROM query_date)
)
SELECT
DISTINCT *,
RANK() OVER(PARTITION BY team,week_number ORDER BY total_query_cnt DESC) AS team_rank
FROM base2
QUALIFY team_rank = 1
ORDER BY week_number, team
강의자료의 코드
-- 2) 주차별로 팀 내에서 쿼리를 많이 실행한 수를 구한 후, 실행한 수를 활용해 랭킹을 구해주세요. 단, 랭킹이 1등인 사람만 결과가 보이도록 해주세요
-- 주차별로 개인당 실행한 쿼리 횟수
-- 위 쿼리 횟수를 기반으로 랭킹
-- 랭킹을 기반으로 필터링(랭킹=1)
-- 문제의 의도 : 원본 데이터 => 1 row마다 데이터가 있고, 그걸 집계해서 사용. GROUP BY => 윈도우 함수
WITH query_cnt_by_team AS (
SELECT
EXTRACT(WEEK FROM query_date) AS week_number,
team,
user,
COUNT(user) AS query_cnt
FROM advanced.query_logs
GROUP BY ALL
)
SELECT
*,
RANK() OVER(PARTITION BY week_number, team ORDER BY query_cnt DESC) AS rk
FROM query_cnt_by_team
-- QUALIFY : 윈도우 함수의 조건을 설정할 때 사용
QUALIFY rk = 1
ORDER BY week_number, team, query_cnt DESC
COUNT의 윈도우 함수 대신에 GROUP BY를 사용하는 풀이도 있다는 것을 알게 되었다! 너무 어렵게 생각하지 말기!
(3) 연습문제 3
WITH base2 AS(
SELECT
EXTRACT(WEEK FROM query_date) AS week_number,
team,
user,
COUNT(*) OVER(PARTITION BY EXTRACT(WEEK FROM query_date) ,user ORDER BY EXTRACT(WEEK FROM query_date) ) AS query_cnt,
FROM
`avdanced.query_logs`
#QUALIFY team_rank = 1
ORDER BY EXTRACT(WEEK FROM query_date)
), base3 AS(
SELECT
DISTINCT *,
RANK() OVER(PARTITION BY team,week_number ORDER BY query_cnt DESC) AS team_rank
FROM base2
QUALIFY team_rank = 1
ORDER BY week_number, team
)
-- 연습문제3) 쿼리 실행 시점 1주전 쿼리 실행
SELECT
DISTINCT *,
LAG(query_cnt,1) OVER(PARTITION BY user ORDER BY week_number) AS prev_week_query_count
FROM
base2
GROUP BY ALL
ORDER BY user, week_number
(4) 연습문제 4
--연습문제4)
SELECT
*,
SUM(query_count) OVER(PARTITION BY user ORDER BY query_date) AS culmulative_query_count,
SUM(query_cnt) OVER(PARTITION BY user ORDER BY query_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_sum2
FROM(
SELECT
DISTINCT *,
COUNT(user) OVER(PARTITION BY query_date, user) AS query_count,
FROM `avdanced.query_logs`
)
ORDER BY user,query_date
QUALIFY 로 조건설정을 하여 두 값이 같은 지 비교하는 법이 인상깊었던 문제 (QUALIFY cumulative_sum != cumulative_sum2)
(5) 연습문제 5
나의 풀이
WITH raw_data AS (
SELECT DATE '2024-05-01' AS date, 15 AS number_of_orders UNION ALL
SELECT DATE '2024-05-02', 13 UNION ALL
SELECT DATE '2024-05-03', NULL UNION ALL
SELECT DATE '2024-05-04', 16 UNION ALL
SELECT DATE '2024-05-05', NULL UNION ALL
SELECT DATE '2024-05-06', 18 UNION ALL
SELECT DATE '2024-05-07', 20 UNION ALL
SELECT DATE '2024-05-08', NULL UNION ALL
SELECT DATE '2024-05-09', 13 UNION ALL
SELECT DATE '2024-05-10', 14 UNION ALL
SELECT DATE '2024-05-11', NULL UNION ALL
SELECT DATE '2024-05-12', NULL
)
--연습문제 5) null에 이전 값 삽입
SELECT
raw_data.date,
IF(raw_data.number_of_orders IS NULL, LAG(raw_data.number_of_orders,1) OVER(ORDER BY date), raw_data.number_of_orders)
FROM raw_data
강의풀이
WITH raw_data AS (
SELECT DATE '2024-05-01' AS date, 15 AS number_of_orders UNION ALL
SELECT DATE '2024-05-02', 13 UNION ALL
SELECT DATE '2024-05-03', NULL UNION ALL
SELECT DATE '2024-05-04', 16 UNION ALL
SELECT DATE '2024-05-05', NULL UNION ALL
SELECT DATE '2024-05-06', 18 UNION ALL
SELECT DATE '2024-05-07', 20 UNION ALL
SELECT DATE '2024-05-08', NULL UNION ALL
SELECT DATE '2024-05-09', 13 UNION ALL
SELECT DATE '2024-05-10', 14 UNION ALL
SELECT DATE '2024-05-11', NULL UNION ALL
SELECT DATE '2024-05-12', NULL
),
-- LAG로 직전 값 가져오면 되지 않을까?
-- number_of_orders가 null이면, before_number_of_orders를 가져와라!
-- 아래 쿼리는 어려운 방법
-- 그 다음 방법 : LAST VALUE를 쓰자! => 값이 없으면 NULL이 뜬다!
-- FIRST_VALUE, LAST_VALUE => NULL을 포함해서 연산
-- 출제 의도 : NULL을 제외해서 연산하고 싶으면 IGNORE NULLS을 쓰면 된다!
-- SELECT
-- *,
-- IF(number_of_orders IS NULL, before_number_of_orders, number_of_orders) AS filled_orders
-- -- Number of arguments does not match for function IF. Supported signature: IF(BOOL, ANY, ANY) at [89:3]
-- -- False일 때 인자를 추가하지 않아서 생긴 오류
-- FROM (
-- SELECT
-- *,
-- LAG(number_of_orders) OVER(ORDER BY date) AS before_number_of_orders
-- FROM raw_data
-- )
filled_data AS (
SELECT
* EXCEPT(number_of_orders),
LAST_VALUE(number_of_orders IGNORE NULLS) OVER(ORDER BY date) AS number_of_orders
FROM raw_data
-- Syntax error: Expected keyword DEPTH but got identifier "filled_data" at [104:6] : WITH문을 두개 작성했는데 WITH 쉼표 쓰고 구분!
)
LAST_VALUE, FIRST_VALUE를 사용하는 풀이 법에 대하여 알게 됨
(6) 연습문제 6
WITH raw_data AS (
SELECT DATE '2024-05-01' AS date, 15 AS number_of_orders UNION ALL
SELECT DATE '2024-05-02', 13 UNION ALL
SELECT DATE '2024-05-03', NULL UNION ALL
SELECT DATE '2024-05-04', 16 UNION ALL
SELECT DATE '2024-05-05', NULL UNION ALL
SELECT DATE '2024-05-06', 18 UNION ALL
SELECT DATE '2024-05-07', 20 UNION ALL
SELECT DATE '2024-05-08', NULL UNION ALL
SELECT DATE '2024-05-09', 13 UNION ALL
SELECT DATE '2024-05-10', 14 UNION ALL
SELECT DATE '2024-05-11', NULL UNION ALL
SELECT DATE '2024-05-12', NULL
), null_is_lag AS(
--연습문제 5) null에 이전 값 삽입
SELECT
raw_data.date,
IF(raw_data.number_of_orders IS NULL, LAG(raw_data.number_of_orders,1) OVER(ORDER BY date), raw_data.number_of_orders) AS number_of_orders
FROM raw_data
)
-- 연습문제 6) 이동평균
SELECT
*,
AVG(nl.number_of_orders) OVER(ORDER BY nl.date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM null_is_lag AS nl
FRAME절을 사용할 때, AND를 기준으로 앞에는 뒤의 값보다 반드시 이전 행을 가리키는 구문이 와야 함!
(7) 연습문제 7
-- 1. TIMESTAMP → DATETIME
-- 2. SECOND_DIFF 생성 : uSER로 묶어서 -
-- 3. SESSION_START생성 : USER로 묶어서 LAG(DATA,1)이 NULL이면 1, SECOND_DIFF가 20이상이면 +1
-- 4. SESSION_ID생성: SESSION_START가 1일 경우 SESSION_ID +1, NULL일 경우 LAG(DATA,1)
WITH add_date AS (
-- 1. TIMESTAMP → DATETIME
SELECT
event_date,
event_timestamp,
DATETIME(TIMESTAMP_MICROS(event_timestamp)) AS event_datetime,
event_name,
user_id,
user_pseudo_id,
LAG(DATETIME(TIMESTAMP_MICROS(event_timestamp))) OVER(PARTITION BY user_pseudo_id ORDER BY event_timestamp) AS before_event_datetime
FROM
`avdanced.app_logs_temp`
--,UNNEST(event_params) AS param -- FROM 절 안에서 UNNEST를 사용
WHERE event_date ="2022-08-18" AND user_pseudo_id = "1997494153.8491999091"
), add_diff AS (
-- 2. SECOND_DIFF 생성 : uSER로 묶어서 -
SELECT
*,
DATE_DIFF(event_datetime, before_event_datetime,SECOND) AS second_diff,
FROM add_date
), add_session AS(
-- 3. SESSION_START생성 : USER로 묶어서 LAG(DATA,1)이 NULL이면 1, SECOND_DIFF가 20이상이면 +1
SELECT
*,
IF(second_diff IS NULL OR second_diff >=20, 1, NULL) AS session_start
FROM
add_diff
)
-- 4. SESSION_ID생성
*,
SUM(session_start) OVER(PARTITION BY user_pseudo_id ORDER BY event_datetime) AS session_num
FROM add_session
ORDER BY event_datetime
user_id와 user_pseudo_id는 다르다. (계정이 존재하면 USER_ID, 없어도 USER_PSEUDO_ID를 통해 활동기록이 남는다.)
IF문은 행 단위로 작동, SUM은 특정 파티션에 대한 누적합을 계산함으로 아래 코드가 작동하지 않음
SUM(session_start) OVER(PARTITION BY user_pseudo_id ORDER BY event_datetime) AS session_num
CTE를 사용하는 것과 서브쿼리를 사용하는 방법 중 상황에 맞게 적절히 혼용할 수 있다는 것을 알게 됨!
답변 1
0
안녕하세요, 인프런 AI 인턴입니다. 망고맛있어님.
BigQuery 윈도우 함수와 관련하여 많은 연습 문제를 풀어보셨군요. 여러 SQL 쿼리를 직접 작성하면서 윈도우 함수의 다양한 활용 방식을 이해하시고 적용하신 것 같습니다.
각 연습 문제에서 사용한 LEAD
, LAG
, SUM
등의 윈도우 함수를 통해 데이터의 시계열 분석이나 이동 평균 등을 구할 때 유용하게 사용할 수 있습니다. 또한, PARTITION BY
와 ORDER BY
를 통해 데이터 그룹을 나누고 정렬하여 쿼리의 목적에 맞는 결과를 얻으셨습니다.
다음은 윈도우 함수와 관련된 유사한 질문과 답변을 확인할 수 있는 URL입니다. 이를 통해 추가적인 힌트나 팁을 얻으실 수 있을 것입니다.
윈도우 함수 기본 개념과 활용
이 질문은 윈도우 함수의 기본 개념과 활용 사례를 잘 설명해 줍니다. 다양한 사례를 통해 윈도우 함수에 대한 이해도를 높일 수 있습니다.
윈도우 함수 활용법 및 자주 하는 실수
윈도우 함수를 사용하면서 자주 저지르는 실수와 이를 방지하는 방법에 대해 배워볼 수 있습니다. 예시를 통해 문제 해결 능력을 기르실 수 있습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.