윈도우 함수
윈도우 함수 (Window Functions)
윈도우 함수는 현재 행과 관련된 행 집합 전체에 걸쳐 계산을 수행하면서도 행을 하나로 뭉치지 않아요. 입력 행마다 대응하는 출력 행이 나오고, 그 옆에 윈도우 함수 결과가 붙어요.
출처: 문서
본문
윈도우 함수는 현재 행과 관련된 행 집합 전체에 걸쳐 계산을 수행해요. GROUP BY를 쓰는 집계 함수와 달리, 윈도우 함수는 행들을 하나의 출력 행으로 뭉치지 않아요. 모든 입력 행은 대응하는 출력 행을 만들고, 그 결과에 윈도우 함수 결과가 덧붙여져요.
Turso는 기본 프레임 정의를 쓰는 집계 함수의 윈도우 함수 사용, `row_number()` 랭킹 함수, `FILTER (WHERE ...)` 절을 지원해요. 나머지 전용 윈도우 함수(`rank`, `dense_rank`, `ntile`, `lag`, `lead`, `first_value`, `last_value`, `nth_value`)와 커스텀 프레임 지정(명시적 경계를 갖는 ROWS, RANGE, GROUPS)은 아직 지원되지 않아요.문법
aggregate_function(expression) OVER (
[PARTITION BY expression [, ...]]
[ORDER BY expression [ASC | DESC] [, ...]]
)
| 절 | 설명 |
|---|---|
aggregate_function |
지원되는 아무 집계 함수: count, sum, avg, min, max, total, group_concat |
OVER (...) |
함수가 동작할 윈도우를 정의해요 |
PARTITION BY |
결과 집합을 파티션으로 나눠요. 함수는 파티션 안에서 독립적으로 적용돼요. 생략하면 전체 결과 집합이 하나의 파티션이 돼요 |
ORDER BY |
파티션 안에서 행의 순서를 정의해요. 이 순서가 각 계산에 어떤 행이 프레임에 포함되는지 결정해요 |
기본 프레임
ORDER BY를 지정하면 기본 프레임은 이래요:
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
즉, 함수는 파티션 시작부터 현재 행까지(그리고 ORDER BY 값이 같은 행들까지 — 프레임 모드가 RANGE라서) 모든 행을 고려해요.
ORDER BY를 생략하면 기본 프레임이 파티션 전체를 덮어요.
윈도우 함수로 쓰는 집계 함수
아무 집계 함수든 OVER 절을 붙이면 윈도우 함수로 쓸 수 있어요.
| 함수 | 설명 |
|---|---|
count(*) |
프레임 안의 행 수 |
count(expression) |
프레임 안의 NULL이 아닌 값의 개수 |
sum(expression) |
프레임 안의 NULL이 아닌 값의 합 |
avg(expression) |
프레임 안의 NULL이 아닌 값의 평균 |
min(expression) |
프레임 안의 최솟값 |
max(expression) |
프레임 안의 최댓값 |
total(expression) |
REAL로 합산 (빈 프레임에서 NULL 대신 0.0을 반환해요) |
group_concat(expression, separator) |
프레임 안 값들의 이어 붙이기 |
랭킹 함수
row_number()
윈도우의 ORDER BY가 정의한 순서대로, 파티션 안의 각 행에 1부터 시작하는 연속 정수를 매겨요. Turso가 지원하는 유일한 전용 랭킹 함수예요.
SELECT
department,
name,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept
FROM employees;
| department | name | salary | rank_in_dept |
|---|---|---|---|
| Engineering | Alice | 90000 | 1 |
| Engineering | Bob | 85000 | 2 |
| Engineering | Carol | 75000 | 3 |
| Marketing | Dave | 70000 | 1 |
| Marketing | Eve | 60000 | 2 |
FILTER (WHERE ...)
FILTER (WHERE ...) 절은 집계 윈도우 함수가 어떤 행을 포함할지 좁혀주고, 쿼리가 반환하는 행에는 영향을 주지 않아요.
SELECT
department,
name,
salary,
SUM(salary) FILTER (WHERE salary > 70000) OVER (PARTITION BY department) AS high_earner_total
FROM employees;
PARTITION BY
PARTITION BY는 행들을 그룹으로 나눠요. 윈도우 함수는 파티션마다 독립적으로 초기화되고 다시 계산돼요.
SELECT
department,
name,
salary,
SUM(salary) OVER (PARTITION BY department) AS dept_total
FROM employees;
| department | name | salary | dept_total |
|---|---|---|---|
| Engineering | Alice | 90000 | 250000 |
| Engineering | Bob | 85000 | 250000 |
| Engineering | Carol | 75000 | 250000 |
| Marketing | Dave | 70000 | 130000 |
| Marketing | Eve | 60000 | 130000 |
PARTITION BY가 없으면 함수는 전체 결과 집합을 하나의 파티션으로 취급해요:
SELECT
name,
salary,
SUM(salary) OVER () AS company_total
FROM employees;
ORDER BY
OVER 절 안의 ORDER BY는 각 파티션 안에서의 행 순서를 결정해요. 기본 프레임(RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)과 결합하면 누적 계산이 만들어져요.
SELECT
name,
salary,
SUM(salary) OVER (ORDER BY salary) AS running_total
FROM employees;
| name | salary | running_total |
|---|---|---|
| Eve | 60000 | 60000 |
| Dave | 70000 | 130000 |
| Carol | 75000 | 205000 |
| Bob | 85000 | 290000 |
| Alice | 90000 | 380000 |
PARTITION BY와 ORDER BY 함께 쓰기
두 절을 함께 쓰면 그룹 안에서의 누적 계산을 할 수 있어요:
SELECT
department,
name,
salary,
SUM(salary) OVER (
PARTITION BY department
ORDER BY salary
) AS dept_running_total
FROM employees;
| department | name | salary | dept_running_total |
|---|---|---|---|
| Engineering | Carol | 75000 | 75000 |
| Engineering | Bob | 85000 | 160000 |
| Engineering | Alice | 90000 | 250000 |
| Marketing | Eve | 60000 | 60000 |
| Marketing | Dave | 70000 | 130000 |
이름 있는 윈도우 (Named Windows)
WINDOW 절은 재사용 가능한 윈도우 정의를 만들어서, 같은 쿼리 안의 여러 윈도우 함수가 참조할 수 있게 해줘요. 같은 OVER 정의를 반복 쓰는 걸 피할 수 있어요.
SELECT
department,
name,
salary,
SUM(salary) OVER w AS running_total,
AVG(salary) OVER w AS running_avg,
COUNT(*) OVER w AS running_count
FROM employees
WINDOW w AS (PARTITION BY department ORDER BY salary)
ORDER BY department, salary;
이름 있는 윈도우는 여러 개 정의할 수도 있어요:
SELECT
department,
name,
salary,
SUM(salary) OVER dept AS dept_total,
SUM(salary) OVER company AS company_total
FROM employees
WINDOW
dept AS (PARTITION BY department),
company AS ()
ORDER BY department, name;
예제
누적 합계
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
그룹별 개수
-- Show each employee alongside their department headcount
SELECT
name,
department,
COUNT(*) OVER (PARTITION BY department) AS dept_size
FROM employees
ORDER BY department, name;
누적 평균
SELECT
date,
temperature,
AVG(temperature) OVER (ORDER BY date) AS running_avg_temp
FROM weather_readings
ORDER BY date;
전체에서 차지하는 비율
SELECT
product_name,
revenue,
ROUND(100.0 * revenue / SUM(revenue) OVER (), 2) AS pct_of_total
FROM products
ORDER BY revenue DESC;
한 쿼리에서 여러 윈도우 함수
SELECT
department,
name,
salary,
MIN(salary) OVER (PARTITION BY department) AS dept_min,
MAX(salary) OVER (PARTITION BY department) AS dept_max,
AVG(salary) OVER (PARTITION BY department) AS dept_avg,
salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg
FROM employees
ORDER BY department, salary DESC;
윈도우 위에서의 그룹 연결
SELECT
department,
name,
GROUP_CONCAT(name, ', ') OVER (PARTITION BY department ORDER BY name) AS names_so_far
FROM employees;
제한 사항
다음 윈도우 함수 기능은 Turso에서 아직 지원되지 않아요:
| 기능 | 상태 |
|---|---|
rank() |
Not supported |
dense_rank() |
Not supported |
ntile(N) |
Not supported |
lag(expr, offset, default) |
Not supported |
lead(expr, offset, default) |
Not supported |
first_value(expr) |
Not supported |
last_value(expr) |
Not supported |
nth_value(expr, N) |
Not supported |
cume_dist() |
Not supported |
percent_rank() |
Not supported |
Custom frame: ROWS BETWEEN ... |
Not supported |
Custom frame: RANGE BETWEEN ... AND ... |
Not supported |
Custom frame: GROUPS BETWEEN ... |
Not supported |
EXCLUDE clause |
Not supported |
row_number()와 FILTER (WHERE ...)는 지원돼요 — 위 섹션을 참고하세요.