ROWS & RANGE - 윈도우 함수의 범위 지정 방식
저번에 윈도우 함수에 대해서 공부했다. 그러다 ROWS 와 RANGE에 대해 궁금해져서 오늘은 그걸 공부해보려고 한다.
- ROWS vs RANGE 의 정의와 차이
- ROWS 와 RANGE의 차이 예시
먼저 RANGE의 예시이다.
예시 테이블 :
id | salesperson | amount |
1 | A | 100 |
2 | A | 100 |
3 | A | 200 |
4 | A | 300 |
-- 누적합 계산
SELECT
id,
amount,
SUM(amount) OVER (
ORDER BY amount
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS range_sum
FROM sales
출력 결과 :
id | amount | range_sum |
1 | 100 | 200 |
2 | 100 | 200 |
3 | 200 | 400 |
4 | 300 | 700 |
100이 2개로 중복된다. RANGE는 값이 같으면 같은 범위로 인식해서 둘 다 포함해서 계산한다.
다음은 ROWS의 예시이다.
예시 테이블 :
id | salesperson | amount |
1 | A | 100 |
2 | A | 100 |
3 | A | 200 |
4 | A | 300 |
-- 누적합 계산
SELECT
id,
amount,
SUM(amount) OVER (
ORDER BY amount
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS row_sum
FROM sales
출력 결과 :
id | amount | row_sum |
1 | 100 | 100 |
2 | 100 | 200 |
3 | 200 | 400 |
4 | 300 | 700 |
ROWS 는 정렬된 순서에서 몇 번째 행인지로 범위를 설정하기 때문에 중복값을 같은값으로 생각하지 않는다.
- 왜 사용하는 가?
- ROWS 는 정확한 순서 기준 누적/비교를 위해 사용된다. 예를들어 거래번호, 시간순이 있다.
- RANGE는 값 기준 누적/비교를 위해 사용된다. 예를들어 금액 누적 합계, 같은 값끼리 처리가 있다.
- 자주 쓰는 범위
- 실전 예제 문제
1. orders 테이블에서 고객(user_id)별로 날짜순으로 정렬했을 때, 자기 자신과 이전 2건의 주문까지의 평균 금액을 구하라.
orders 테이블 :
order_id | user_id | order_date | amount |
1 | A | 2024-01-01 | 100 |
2 | A | 2024-01-05 | 200 |
3 | A | 2024-01-10 | 300 |
4 | A | 2024-01-15 | 400 |
SELECT
user_id,
order_date,
amount,
AVG(amount) OVER (
PARTITION BY user_id
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS avg_amount
FROM orders
출력 결과 :
order_date | amount | avg_amount |
2024-01-01 | 100 | 100.0 |
2024-01-05 | 200 | 150.0 |
2024-01-10 | 300 | 200.0 |
2024-01-15 | 400 | 300.0 |
2. orders 테이블에서 고객(user_id)별로 주문금액(amount) 기준으로 정렬했을 때, 자기 금액 기준으로 50원 이내의 금액(자기 금액 - 50 ~ 자기 금액)을 가진 주문들의 평균 금액을 구하라.
orders 테이블 :
order_id | user_id | order_date | amount |
1 | A | 2024-01-01 | 100 |
2 | A | 2024-01-05 | 130 |
3 | A | 2024-01-10 | 140 |
4 | A | 2024-01-15 | 200 |
SELECT
user_id,
order_date,
amount,
AVG(amount) OVER (
PARTITION BY user_id,
ORDER BY amount
RANGE BETWEEN 50 PRECEDING AND CURRENT ROW
) AS avg_amount
FROM orders
출력 결과 :
order_date | amount | avg_amount |
2024-01-01 | 100 | 100.0 |
2024-01-05 | 130 | 115.0 |
2024-01-10 | 140 | 123.3 |
2024-01-15 | 200 | 200.0 |
- 실전 문제
이제 GPT를 활용해서 실전 문제를 만들고, 채점과 피드백을 요청했다.
🔸 문제 1.
CREATE TABLE orders (
order_id INT,
user_id INT,
amount INT
);
INSERT INTO orders VALUES
(1, 101, 200),
(2, 102, 500),
(3, 103, 150),
(4, 104, 300),
(5, 105, 400);
문제 설명
전체 주문의 평균 금액보다 큰 주문만 출력하라
사용 테이블: orders
출력 컬럼: order_id, user_id, amount
조건: 전체 주문의 평균 금액보다 큰 주문만
SELECT
order_id,
user_id,
amount
FROM ( # 서브쿼리
SELECT
*,
AVG(amount) OVER ( # 윈도우 함수
ORDER BY amount
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS avg_amount
FROM orders
) AS t
WHERE amount > avg_amount
🔸 문제 2.
CREATE TABLE scores (
student_id INT,
subject VARCHAR(10),
score INT
);
INSERT INTO scores VALUES
(1, 'math', 85),
(2, 'math', 90),
(3, 'math', 85),
(4, 'math', 95),
(5, 'math', 80),
(6, 'kor', 95),
(7, 'kor', 80);
문제 설명
수학 점수를 기준으로 등수를 매겨 상위 3등 이내 학생만 출력하라
사용 테이블: scores
출력 컬럼: student_id, score, rn
조건: subject = 'math', 등수 3위 이내
SELECT
student_id,
score,
RANK() OVER (
PARTITION BY subject
ORDER BY score DESC
) AS rank
FROM scores
WHERE rank > 3
AND subject = 'math'
🔸 문제 3.
CREATE TABLE user_logins (
login_id INT,
user_id INT,
login_date DATE
);
INSERT INTO user_logins VALUES
(1, 101, '2024-01-01'),
(2, 101, '2024-01-03'),
(3, 101, '2024-01-05'),
(4, 102, '2024-01-02'),
(5, 102, '2024-01-06');
문제 설명
유저별로 가장 최근 로그인 기록만 출력하라
사용 테이블: user_logins
출력 컬럼: user_id, login_date
조건: 유저마다 login_date가 가장 최신인 행 1건만
SELECT
user_id,
login_date
FROM (
SELECT
*,
MAX(login_date) OVER (
PARTITION BY user_id
ORDER BY login_date DESC
) AS new_login
FROM user_logins
) AS t
WHERE login_date = new_login
🔸 문제 4.
CREATE TABLE products (
product_id INT,
price INT
);
INSERT INTO products VALUES
(1, 300),
(2, 100),
(3, 400),
(4, 200),
(5, 500),
(6, 350);
문제 설명
가격을 기준으로 제품을 **3개 등급(분위)**으로 나누고 등급을 함께 출력하라
사용 테이블: products
출력 컬럼: product_id, price, tier
조건: 가격 기준 3분위(tier 값 1‒3)
SELECT
product_id,
price,
NTILE(3) OVER (
ORDER BY price DESC
) AS tier
FROM products
🔸 문제 5.
CREATE TABLE orders (
order_id INT,
user_id INT,
order_date DATE,
amount INT
);
INSERT INTO orders VALUES
(1, 101, '2024-01-01', 100),
(2, 101, '2024-01-03', 150),
(3, 101, '2024-01-05', 130),
(4, 102, '2024-01-02', 200),
(5, 102, '2024-01-04', 180);
문제 설명
각 유저의 주문별로 이전 주문 금액과 다음 주문 금액을 함께 출력하라
사용 테이블: orders
출력 컬럼: user_id, order_date, amount, prev_amount, next_amount
조건: prev_amount는 직전 주문 금액, next_amount는 다음 주문 금액
SELECT
user_id,
order_date,
amount,
LAG(amount) OVER (
PARTITION BY user_id
ORDER BY order_date
) AS prev_amount,
LEAD(amount) OVER (
PARTITION BY user_id
ORDER BY order_date
) AS next_amount
FROM orders
2번 문제를 틀렸다. 이유는 다음과 같다.
SQL 쿼리문은 엄연한 순서가 존재한다.
" FROM - WHERE - GROUP BY - HAVING - SELECT - ORDER BY " 이것이 바로 그 순서이다.
따라서 내가 앞에 작성한 코드는 SELECT 절에서 윈도우함수로 계산한 RANK 값을 WHERE 절에서 사용하지 못해 "UNKNOWN COLUMN" 이라는오류가 발생하게 된다. 그렇기 때문에 서브쿼리를 이용하여 새로운 테이블을 만든 뒤에 WHERE 절에서 RANK 값을 호출해야 한다. 수정한 코드는 아래와 같다.
SELECT
student_id,
subject,
score,
rn
FROM (
SELECT
*,
RANK() OVER (
PARTITION BY subject
ORDER BY score DESC
) AS rn
FROM scores
) AS t
WHERE rn <= 3 AND subject = 'math'
'SQL' 카테고리의 다른 글
날짜 함수 - MySQL (2) | 2025.08.05 |
---|---|
Common Table Expression (CTE) - MySQL (4) | 2025.08.03 |
Window Function (윈도우 함수) - MySQL (1) | 2025.07.29 |
Subquery (서브쿼리) - MySQL (1) | 2025.07.27 |
집계 함수 & GROUP BY & HAVING - MySQL (2) | 2025.07.25 |