윈도우 함수(Window Function)은 분석 함수(Analytics Function)로 불리기도 함
윈도우 함수는 다음과 같은 경우에 유용한 함수
이런 경우, 윈도우 함수를 사용하지 않으면 서브쿼리나 JOIN 등을 사용해서 구해야 함
윈도우 함수 이름(컬럼, 순서) OVER (PARTITION BY 파티션_컬럼 ORDER BY 정렬__컬럼 윈도우 프레임)
윈도우 함수
- 유저 별로 방문 주기를 파악하고 싶을 때

1) 유저 별 다음 방문 월은?

2) 유저 별 방문 이후 두번째 방문 월은?

LEAD(visit_month) OVER (PARTITION BY user_id ORDER BY visit_month)
LEAD(visit_month, 2) OVER (PARTITION BY user_id ORDER BY visit_month)
LEAD함수의 반환된 값이 NULL = 더 이상 방문한 월이 없다 = 해당 visit_month가 유저의 마지막 방문 월이다
- 유저 별 이전 방문 월은?

LAG(visit_month) OVER (PARTITION BY user_id ORDER BY visit_month)
LAG함수의 반환된 값이 NULL = 이전에 방문한 월이 없다 = 해당 visit_month가 유저의 첫 방문 월이다
LEAD, LAG 함수는 숫자를 지정해서 바로 앞, 뒤, 2번째 앞, 3번째 뒤 등을 지정할 수 있음
- 유저들의 첫 방문 월은?

- 유저들의 마지막 방문 월은?

FIRST_VALUE와 LAST_VALUE의 NULL 처리
![]() | ![]() |
|---|


탐색 함수 활용 예시
유저 A가 앱에 접속한 후, 어떤 화면으로 이동했는지 알 수 있음
SELECT user_id, event_timestamp, event_name, event_parameter, LEAD(event_name) OVER (PARTITION BY user_id ORDER BY event_timestamp) AS next_page FROM app_logs WHERE user_id = "A"
유저 A의 앱 로그 상에서 같은 page를 연속으로 접근한 경우 하나로 처리해서 퍼널을 구하라
리텐션 쿼리를 작성할 때 기준점을 만들 수 있음
1) RANK
2) ROW_NUMBER
값에 중복이 있으면 랜덤으로 순위 부여.
랜덤이기 때문에 순위가 변경될 수 있음
=> 고정된 순위를 얻고 싶다면 ORDER BY에 id 기준을 추가
SELECT *, ROW_NUMBER() OVER (PARTITION BY product_type ORDER BY revenue DESC, id) AS row_num FROM Table
- ORDER BY revenue DESC, id : 값에 중복이 생기면 id로 정렬하겠다. id는 보통 유니크한 값이기 때문
- 매출 데이터에서 product_type 별로 매출 순위를 알고 싶은 경우

RANK, ROW_NUMBER 선택 방법
![]() | ![]() |
|---|
- product_type 별로 평균 매출을 구하려면?

- product_type 별로 매출 합을 구하려면?

Frame 설정 방법
1) ROWS

2) RANGE

Frame의 시작과 끝 지점 명시하기

Frame 쿼리 작성
현재 행과 이전 행 1개, 이후 행 3개를 포함해서 평균
- AVG(컬럼) OVER (PARTITION BY product_type ORDER BY event_timestamp ROWS BETWEEN 1 PRECEDING AND 3 FOLLOWING)
파티션의 처음부터 현재 행을 포함한 평균
- AVG(컬럼) OVER (PARTITION BY product_type ORDER BY event_timestamp ROWS UNBOUNDED PRECEDING AND CURRENT ROW)
Frame 예시)

1) 우리 회사의 모든 주문량은?
2) 특정 주문 시점에서 누적 주문량은?
3) 고객 별 주문 시점에서 누적 주문량은?
4) 최근 직전 5개의 평균 주문량은?

SUM(amount) OVER () AS amount_total
SUM(amount) OVER (ORDER BY order_id) AS cumulative_sum

SUM(amount) OVER (PARTITION BY user_id ORDER BY order_id) AS cumulative_sum_by_user
AVG(amount) OVER (ORDER BY order_id ROWS BETWEEN 5 PRECEDING AND 1 PRECEDING) AS last_5_orders_avg_amount
QUALIFY를 통해 윈도우 함수로 만든 컬럼에 바로 조건 설정이 가능함
2021.04 까지만 해도
![]() | ![]() |
|---|
![]() | ![]() |
|---|
데이터 1.

유저가 접속했을 때, 다음 접속까지의 간격을 구하시오.
- 기대하는 ouput :
user_id | visit_month | after_visit_month | diff_month
유저의 다음 접속까지의 간격을 나타내는 diff_month를 구하기 위해선
다음 접속 시기를 나타내는 after_visit_month에서 기존 접속 시기 visit_month를 빼는(-) 연산이 필요해보임
연산을 위한 쿼리를 생각해보면
1) select 절에서 윈도우 함수 alias를 직접 연산하는 경우
=> 오류
=> select 절은 제일 마지막에 실행됨 -> after_visit_month - visit_month를 실행할 때 after_visit_month를 인식하지 못함

2) 중복된 윈도우 함수 쿼리를 연산하는 경우
=> 중복 쿼리는 최대한 줄이는 것이 좋음
=> 나중에 쿼리를 수정할 상황이 생길 수 있음 -> 중복된 쿼리가 있으면 해당 쿼리를 수정할 때 -> 2번 수정해야 함
=> 이런 중복 쿼리가 많아지면 쿼리가 복잡해지고 실수하기 쉬워짐

3) 서브 쿼리 이용해서 연산
- 서브 쿼리 이용할 때, 쿼리가 길어지는 것을 걱정하곤 함
- 그러나 쿼리가 길어지는 것을 무서워하지 말고, 쿼리를 덜 수정할 수 있는 구조를 만드는 것이 중요함

데이터 2.
- 기대하는 output :
user | team | query_date | total_query_cnt
![]() | ![]() |
|---|
- 기대하는 output :
week_number | team | user | query_cnt | team_rank
주차 별 => 주차별 정보를 나타내는 컬럼이 필요해보임 => EXTRACT 함수 사용

주차별로 팀 내에서 쿼리를 많이 실행한 수
=> 먼저 각 유저들이 주차별로 쿼리를 실행한 횟수를 구해보면

이제 주차별로 팀 내에서 각 유저들이 쿼리를 실행한 횟수를 구해보면
=> GROUP BY 기준에 컬럼 team을 추가하면
=> 1) 주차별 2) 팀 별 3) 유저 별
=> 쿼리 실행 횟수를 나타낼 수 있음

주차별로 팀 내에서 쿼리를 실행한 횟수에 대한 랭킹을 구한 후, 랭킹 1등만 나타내기
![]() | ![]() |
|---|
![]() | ![]() |
|---|
위에서 with문으로 정의한 query_cnt_by_week 사용
쿼리 실행 시점 1주 전 쿼리 실행 수 => LAG 함수 사용
=> LAG함수를 적용할 컬럼은 query_cnt
![]() | ![]() |
|---|
누적 쿼리 수 => 윈도우 집계 함수 SUM + 프레임
- 쿼리 실행 수에 대한 누적 합을 구해야 함
=> 쿼리 실행 수를 나타내는 컬럼은 COUNT 함수를 사용해 새로 만들어야 함

query_cnt를 구한 쿼리 -> 서브 쿼리로 사용 or WITH문으로 정의
=> 해당 문제처럼 연산할 게 하나정도 있다면, 서브 쿼리로 사용하는 것이 나음
시간의 흐름에 따라 일자 별로 유저가 실행한 누적 쿼리 수

OVER 절에 프레임을 쓰지 않아도 동일한 결과가 나옴
=> Default Frame : UNBOUNDED PRECEDING AND CURRENT ROW

누적 쿼리 수를 구할 때 COUNT가 아닌 SUM을 사용하는 이유

데이터 3.

NULL값을 이전 값으로 채우기
=> 떠올릴 수 있는 방법 : 1) LAG 2) LAST_VALUE + IGNORE NULLS
1) LAG 함수로 직전 날짜의 값 가져오기
=> NULL값이 연속인 경우에 대해 또 따로 작성해줘야 함

2) LAST_VALUE + IGNORE NULLS


데이터 4.

앱 로그 테이블에서 Custom Session을 만들어주세요. 이전 이벤트 로그와 20초가 지나면 새로운 Session을 만들어주세요. Session은 숫자로 표시해도 됩니다.
예를 들어 2022-08-18의 user_pseudo_id(1997494153.8491999091)은 새로운 session_id가 4까지 나옵니다.
- 기대하는 output :
event_date | event_timestamp | event_datetime | event_name | user_id | user_pseudo_id | prev_event_datetime | second_diff | session_start | session_id
1) 데이터 탐색

2) event_timestamp 변환

3) 새로운 Session 만들기
3-1) 직전 이벤트 로그 시점을 나타내는 컬럼 만들기







해당 문제는 세션 별 집계를 할 때
seesion_id기 없는 경우
=> 유저 로그 기반으로 세션을 직접 생성한 것
=> 유저의 미활동 시간을 '20초 이상'으로 임의 설정
세션을 직접 만들면 해당 쿼리처럼, 쿼리가 복잡해지기 때문에
=> 해결하려는 문제의 목적에 따라
=> 유저 별 집계를 할지, 세셜 별 집계를 할지를 정하는 것이 좋음