거래 한 건씩 더하려면 거래 순서를 고정해야 합니다. 다음 예제는 날짜·ID로 정렬한 ROWS 프레임으로 행별 누적액을 계산합니다. 같은 날짜의 거래를 한꺼번에 포함하려면 날짜를 정렬 기준으로 둔 RANGE 프레임을 사용할 수 있습니다. CURRENT ROW라는 같은 표현도 두 프레임에서 경계를 잡는 방식이 다릅니다.

PostgreSQL 18의 누적 합계를 직접 만든 거래 표로 비교합니다. 시간 간격을 더하거나 빼는 RANGE 오프셋이 아니라, 처음부터 현재 경계까지 포함하는 경우에 한정합니다.

누적의 단위를 먼저 정합니다

거래가 입력될 때마다 잔액을 보고 싶은 경우와 하루가 끝난 시점의 누적액을 모든 거래 옆에 표시하려는 경우는 다릅니다. 첫 요구는 같은 날의 첫 거래와 두 번째 거래를 구별합니다. 두 번째 요구는 그날의 거래를 함께 포함합니다. SQL을 고치기 전에 어느 결과가 필요한지 한 문장으로 적어 보세요.

일반 GROUP BY는 날짜마다 한 행으로 묶을 수 있지만 창 함수는 거래 행을 유지하면서 옆에 계산 결과를 붙입니다. 따라서 같은 날짜의 두 행이 남아 있고 누적액도 같다고 해서 집계가 중복 실행된 오류는 아닙니다. 행을 유지할 것인지부터 확인하면 창 함수와 그룹 집계를 혼동하지 않을 수 있습니다.

거래 네 건을 나란히 계산합니다

교육용 입력: 날짜는 같은 형식의 문자열이며 ID는 거래 순서를 구별합니다.
ID날짜금액
12026-04-10120
22026-04-1080
32026-04-1150
42026-04-12-30
행별 순서는 날짜와 ID로 고정하고, 날짜별 범위는 날짜만으로 묶습니다.
WITH sales(id, day_key, amount) AS (
  VALUES (1, '2026-04-10', 120), (2, '2026-04-10', 80),
         (3, '2026-04-11', 50), (4, '2026-04-12', -30)
)
SELECT id, day_key, amount,
       SUM(amount) OVER (
         ORDER BY day_key, id
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS by_row,
       SUM(amount) OVER (
         ORDER BY day_key
         RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS by_day
FROM sales
ORDER BY day_key, id;
현재 경계까지 포함한 결과
IDROWS: 거래 순서 누적RANGE: 날짜 경계 누적
1120200
2200200
3250250
4220220

첫 거래의 ROWS 프레임에는 자기 자신만 들어 있어 120입니다. 두 번째 거래에서는 첫 두 행이 들어가 200이 됩니다. RANGE는 날짜가 같은 행을 동점 그룹으로 보므로 첫 거래에서도 4월 10일의 두 거래를 모두 포함합니다. 첫 행 옆에 200이 나타나는 이유는 뒤 날짜를 미리 더해서가 아니라 같은 날짜 그룹의 끝을 경계로 삼기 때문입니다.

4월 11일에는 이전 날짜의 200에 50을 더해 두 방식 모두 250이 됩니다. 마지막 거래는 환불을 뜻하는 음수 -30이라 220으로 줄어듭니다. 누적 합계가 반드시 증가한다는 생각도 버려야 합니다. 프레임에 포함되는 행이 늘어나더라도 더하는 값이 음수면 결과는 작아질 수 있습니다.

ROWS만 적어서는 동점 순서가 정해지지 않습니다

두 식의 ORDER BY가 다른 것은 의도한 선택입니다. 거래별 계산에는 같은 날짜 안에서도 먼저 처리할 거래를 정해야 하므로 ID를 추가했습니다. 날짜별 계산에는 같은 날짜가 동점으로 남아 있어야 하므로 ID를 넣지 않았습니다. 프레임의 종류뿐 아니라 어떤 열로 순서를 정하는지도 함께 읽으세요.

ROWS 식에서 ID를 빼고 날짜만 정렬하면 첫날 두 거래의 내부 순서는 지정되지 않습니다. 120이 먼저 오면 두 누적액은 120과 200이지만, 80이 먼저 오면 80과 200입니다. 최종 출력에서 ID 순으로 보이게 했다고 창 함수 계산 때의 동점 순서까지 정해지는 것은 아닙니다. 계산 기준은 창 내부에서 정해야 합니다.

반대로 RANGE 식에도 고유 ID를 추가하면 날짜가 같아도 정렬값 전체는 다릅니다. 이 예시의 누적 프레임에서는 각 거래가 별도 경계가 되어 120, 200, 250, 220이 나옵니다. 이 결과가 ROWS와 같다는 이유로 두 프레임이 항상 같다고 결론 내리면 안 됩니다. 고유한 정렬 기준과 이 프레임 범위에서 우연히 결과가 일치한 것입니다.

프레임을 생략한 식도 읽어야 합니다

PostgreSQL 18에서 이 식은 날짜 동점의 끝까지 포함하는 기본 프레임을 사용합니다.
SUM(amount) OVER (ORDER BY day_key)

ORDER BY는 있지만 프레임을 쓰지 않은 위 식은 이 예시에서 by_day와 같은 200, 200, 250, 220을 냅니다. 따라서 ‘프레임을 쓰지 않았으니 현재 한 행까지만 더한다’는 추측은 맞지 않습니다. 문제에 기본값이 등장하면 PostgreSQL 18처럼 제품과 버전을 확인한 뒤 해석해야 합니다.

OVER 안의 ORDER BY까지 없애 ‘SUM(amount) OVER ()’로 바꾸면 네 행 모두 전체 합계 220이 붙습니다. 창의 계산 범위와 최종 표시 순서는 별개라서 바깥 ORDER BY만 추가해도 누적 합계로 바뀌지 않습니다. 출력이 예상과 다를 때는 괄호 안팎을 나누어 읽는 것이 먼저입니다.

행을 포함하는 선을 그어 복습합니다

  1. 현재 행을 하나 고르고 프레임 시작과 끝을 표시합니다.
  2. ROWS라면 창 정렬에서 현재 위치가 어디인지, 동점 순서가 고정됐는지 확인합니다.
  3. RANGE의 CURRENT ROW 끝이라면 같은 정렬값을 가진 마지막 행까지 표시합니다.
  4. 표시된 행의 금액만 더하고 나머지 행으로 이동합니다.
  5. 날짜 정렬에 ID를 넣거나 빼서 동점 그룹이 달라지는지 다시 확인합니다.

변형 문제는 처음 날짜에 거래 한 건을 더 넣는 것부터 시작하세요. 날짜별 누적은 그 날짜의 세 행에 같은 결과를 붙이지만, 거래별 누적은 정해진 순서대로 세 번 달라집니다. 같은 숫자 결과를 외우기보다 프레임에 포함된 행을 설명할 수 있어야 다른 입력에서도 답을 찾을 수 있습니다.

정답을 가리고 확인해 보세요

이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.

같은 날짜에 ID 1의 금액 40과 ID 2의 금액 60이 있습니다. 처음부터 현재 경계까지 합할 때, 날짜·ID로 정렬한 ROWS의 첫 행 값과 날짜만 정렬한 RANGE의 첫 행 값은 각각 얼마인가요?

정답과 해설 확인ROWS의 첫 행은 40, RANGE의 첫 행은 100입니다.

ROWS는 고정한 거래 순서에서 현재 첫 행까지만 포함합니다. RANGE의 CURRENT ROW 끝은 날짜가 같은 마지막 동점 행까지 포함하므로 40과 60을 함께 더합니다.