02 데이터에서 id 3·4 는 같은 날짜(2024-01-07)입니다. ORDER BY ord_date 만 쓰고 프레임을 생략한 누적합을 봅니다.
SELECT
id
, ord_date
, amt
, SUM(amt) OVER (ORDER BY ord_date) AS default_frame
FROM orders
ORDER BY ord_date, id;ID | ORD_DATE | AMT | DEFAULT_FRAME
---+------------+-----+--------------
1 | 2024-01-05 | 100 | 100
2 | 2024-01-06 | 200 | 300
3 | 2024-01-07 | 300 | 1000
4 | 2024-01-07 | 400 | 1000
5 | 2024-01-08 | 500 | 1500
6 | 2024-01-09 | 600 | 2100
(6행)id 3 의 누적합이 600(100+200+300)이 아니라 1000 입니다. 기본 프레임이 RANGE 이므로 정렬 값이 같은 id 4 의 400 까지 한꺼번에 더했기 때문입니다.
주의오류도 경고도 없이 id 3 의 누적합만 "다음 행"까지 포함합니다.
ORDER BY만 쓴 누적합은 정렬 기준이 유일한지 확인하세요. 유일하지 않으면 동점 행이 같은 값을 받는 것이 표준 동작이고 Oracle·Tibero·MySQL·MSSQL 이 모두 같습니다.
SELECT
id
, ord_date
, amt
, SUM(amt) OVER (ORDER BY ord_date, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rows_frame
FROM orders
ORDER BY ord_date, id;ID | ORD_DATE | AMT | ROWS_FRAME
---+------------+-----+-----------
1 | 2024-01-05 | 100 | 100
2 | 2024-01-06 | 200 | 300
3 | 2024-01-07 | 300 | 600
4 | 2024-01-07 | 400 | 1000
5 | 2024-01-08 | 500 | 1500
6 | 2024-01-09 | 600 | 2100
(6행)이제 id 3 은 600, id 4 는 1000 으로 행마다 하나씩 쌓입니다. ORDER BY ord_date, id 로 정렬을 유일하게 만들었기 때문에 동점 행 사이의 순서(id 3 이 먼저)도 고정됩니다.
ROWS 프레임은 정렬이 유일하지 않으면 동점 행의 순서를 DB 가 임의로 정합니다. ROWS 를 쓸 때는 ORDER BY 를 유일하게 만드는 습관을 함께 둡니다.
SELECT
id
, ord_date
, amt
, SUM(amt) OVER (ORDER BY ord_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rows_sum
, SUM(amt) OVER (ORDER BY ord_date
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS range_sum
FROM orders
ORDER BY ord_date, id;ID | ORD_DATE | AMT | ROWS_SUM | RANGE_SUM
---+------------+-----+----------+----------
1 | 2024-01-05 | 100 | 100 | 100
2 | 2024-01-06 | 200 | 300 | 300
3 | 2024-01-07 | 300 | 600 | 1000
4 | 2024-01-07 | 400 | 1000 | 1000
5 | 2024-01-08 | 500 | 1500 | 1500
6 | 2024-01-09 | 600 | 2100 | 2100
(6행)동점이 없는 행(id 1·2·5·6)은 두 값이 같고, 같은 날짜인 id 3·4 에서만 RANGE 가 1000 을 함께 받습니다. 정렬 값이 모두 다르면 ROWS 와 RANGE 는 같은 결과입니다.
이 쿼리는 ORDER BY ord_date 만 썼으므로 ROWS_SUM 열의 id 3·4 순서는 실제 DB 에서 바뀔 수 있습니다. 비교용 예제이니 그대로 쓰지 말고 변형 2 처럼 id 를 덧붙입니다.
SELECT
id
, amt
, SUM(amt) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_amt
FROM orders
ORDER BY id;ID | AMT | TOTAL_AMT
---+-----+----------
1 | 100 | 2100
2 | 200 | 2100
3 | 300 | 2100
4 | 400 | 2100
5 | 500 | 2100
6 | 600 | 2100
(6행)프레임을 첫 행부터 마지막 행까지 넓히면 SUM(amt) OVER () 와 같은 전체 합입니다. 프레임의 양 끝을 직접 고르는 감각은 프레임에 영향을 받는 FIRST_VALUE·LAST_VALUE 를 다음 레슨에서 쓸 때 필요합니다.
팁정렬 기준에 유일한 값이 없는 누적합은
ORDER BY ord_date, id처럼id를 덧붙이는 방법과ROWS프레임 명시, 두 가지로 고칩니다. 실무 리포트에서는 둘을 함께 써서 프레임 의도를 코드에 남기는 편이 안전합니다.
누적합과 이동 평균은 세 DB 가 같은 문법입니다. 03 파일은 같은 문장을 SET MODE 별로 실행했고 결과가 세 모드에서 같았습니다.
-- Oracle · Tibero, MySQL 8.0, MSSQL 2012 이상 모두 같은 문장
SELECT
id
, amt
, SUM(amt) OVER (ORDER BY ord_date) AS running_total
, AVG(amt) OVER (ORDER BY ord_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg3
FROM orders
ORDER BY id;ID | AMT | RUNNING_TOTAL | MOVING_AVG3
---+-----+---------------+------------
1 | 100 | 100 | 100.0
2 | 200 | 300 | 150.0
3 | 300 | 600 | 200.0
4 | 400 | 1000 | 300.0
5 | 500 | 1500 | 400.0
(5행)H2 는 방언을 흉내 낼 뿐이라 이 결과가 실제 DB 동작의 근거는 아닙니다. 다만 위 문법은 2.4절의 버전 조건(MySQL 8.0, MSSQL 2012 이상)을 만족하면 세 DB 에서 같습니다.
MSSQL 2008 이하에서는 SUM(amt) OVER (ORDER BY ...) 자체가 오류이므로 상관 서브쿼리로 누적합을 만들어야 합니다.
Oracle·Tibero 전용 함수인 RATIO_TO_REPORT 는 "현재 값 / 전체 합"을 바로 돌려줍니다.
-- Oracle · Tibero 전용
SELECT
id
, amt
, ROUND(RATIO_TO_REPORT(amt) OVER () * 100, 1) AS pct
FROM orders
ORDER BY id;ID | AMT | PCT
---+-----+-----
1 | 100 | 6.7
2 | 200 | 13.3
3 | 300 | 20.0
4 | 400 | 26.7
5 | 500 | 33.3
(5행)MySQL 과 MSSQL 에는 이 함수가 없으니, 네 DB 공통인 SUM OVER () 방식을 씁니다. 결과는 위와 같습니다.
-- 공통(Oracle · Tibero · MySQL · MSSQL)
SELECT
id
, amt
, ROUND(amt * 100.0 / SUM(amt) OVER (), 1) AS pct
FROM orders
ORDER BY id;ID | AMT | PCT
---+-----+-----
1 | 100 | 6.7
2 | 200 | 13.3
3 | 300 | 20.0
4 | 400 | 26.7
5 | 500 | 33.3
(5행)H2 2.3.232 는 RATIO_TO_REPORT 를 실행하지만, 이 함수는 Oracle·Tibero 전용이므로 다른 DB 로 옮길 코드라면 공통 방식을 쓰세요.