데이터샤우츠
[2026-07-31 | DEV-LAB] SQLite 윈도 함수로 월별 매출 누적합과 전월 대비 증감 구하기 본문
월별 매출 데이터를 분석할 때 실무자가 흔히 범하는 실수는 원본 행을 유지하면서 누적합이나 전월 대비 증감을 구하기 위해 테이블을 자기 자신과 조인하거나 복잡한 서브쿼리를 중첩하는 것이다. 이러한 방식은 쿼리가 길어질수록 정렬 기준이 파편화되고 계산 의도가 여러 위치로 분산되어 유지보수가 어려워진다.
이번 실습에서는 SQLite의 윈도 함수를 활용하여 단일 쿼리 내에서 월별 12행의 구조를 그대로 보존한 채 누적 합계와 증감액을 한꺼번에 계산하는 방법을 학습한다. 이를 통해 복잡한 집계 로직을 단순화하고 데이터의 흐름을 한눈에 파악하는 올바른 SQL 작성 판단 기준을 세운다.
먼저 결론부터
윈도 함수는 월별 12행을 그대로 유지하면서 누적합과 전월 차이를 각 행 옆에 붙인다. 계산할 때는 어떤 순서로 행을 읽는지와 어디까지 누적하는지를 SQL에 명확히 적어야 한다.
계산 결과를 외우는 것이 목표가 아니다. 아래 순서대로 입력값, 계산식, 출력값을 연결하면 같은 원리를 다른 데이터에도 적용할 수 있다.
실습 순서
- monthly_sales.csv의 month와 sales 열이 어떤 값을 담는지 확인한다.
- SQLite CLI 또는 DB Browser 중 한 가지 실행 방법을 선택한다.
- SUM과 LAG를 실행해 12월 누적합 2,370과 월별 증감을 검산한다.
- 동률 매출을 추가하고 RANK, DENSE_RANK, ROW_NUMBER의 차이를 설명한다.
실습 정보
- 난이도: 초급
- 예상 시간: 25분
- 도구: SQLite 윈도 함수
- 선행 지식: SELECT와 ORDER BY의 기본 문법 · SQLite 또는 DB Browser for SQLite
- 이번에 새로 배우는 표현: CAST(값 AS INTEGER) · OVER (ORDER BY month) · LAG(sales)
- 검증할 결과: month|sales|running_sales|change → 2026-01|100|100| 외 11행
실습 자료 다운로드
데이터만 빠르게 확인하려면 CSV 또는 XLSX를, 처음부터 끝까지 실습하려면 ZIP을 받으면 된다.
- monthly_sales.csv — 원본 형태를 확인하는 실습 입력 데이터
- practice-data.xlsx — 데이터와 열 설명을 함께 보는 엑셀 파일
- DEV-LAB-2026-07-31-sqlite-window-monthly.zip — 실행 코드·정답·기대 출력·슬라이드를 담은 전체 실습 묶음
ZIP 안에는 SQLite CLI용 starter.sql·solution.sql과 DB Browser용 browser-starter.sql·browser-solution.sql이 함께 들어 있다. 사용하는 환경에 맞는 두 파일만 선택하면 된다.
이제 실습 파일 안의 데이터부터 살펴본다. 코드를 실행하기 전에 각 열이 무엇을 뜻하는지 알아야 결과를 잘못 해석하지 않는다.
1단계. 데이터 이해하기
학습을 위해 만든 2026년 월별 매출 12행 데이터다. 개인정보와 실제 기업 정보는 포함하지 않는다.
데이터 구분: 학습용 더미데이터
| 열 이름 | 뜻 | 예시 |
|---|---|---|
| month | 매출이 집계된 월. 정렬 순서를 결정하는 기준 | 2026-01 |
| sales | 해당 월의 매출액 | 100, 140, 120 |
| running_sales | 1월부터 현재 월까지 sales를 더한 누적합 | 1월 100, 2월 240 |
| change | 현재 월 매출에서 바로 앞 행의 매출을 뺀 값 | 2월 +40, 3월 -20 |
monthly_sales.csv
month,sales
2026-01,100
2026-02,140
2026-03,120
2026-04,180
2026-05,160
2026-06,210
2026-07,190
2026-08,230
2026-09,220
2026-10,250
2026-11,270
2026-12,300
데이터의 열과 값이 무엇을 뜻하는지 확인했다면 실행 환경을 고른다. 아래 두 방법 가운데 하나만 선택하면 된다.
2단계. 실행 환경 준비하기
SQLite CLI
- 첨부 ZIP을 풀고 monthly_sales.csv, starter.sql, solution.sql이 같은 폴더에 있는지 확인한다.
- 터미널 또는 PowerShell에서 해당 폴더로 이동해 sqlite3 :memory:로 SQLite 콘솔을 연다.
- sqlite> 프롬프트에서 .read starter.sql을 실행하고 TODO를 완성한 뒤 다시 실행한다.
- 완성한 출력 전체를 expected-output.txt와 비교한다.
DB Browser for SQLite
- DB Browser for SQLite에서 새 데이터베이스를 만든다.
- SQL 실행 탭에서 browser-starter.sql을 열고 전체 구문을 실행한다.
- SELECT의 두 NULL을 앞에서 설명한 누적합과 전월 차이 수식으로 바꾼다.
- 결과 표의 12행을 browser-solution.sql 및 expected-output.txt와 비교한다.
DB Browser 초기화 SQL
ZIP을 열지 않고 글만 읽는 경우에도 아래 1구역을 SQL 실행 탭에 붙여 넣어 같은 sales 테이블과 6행을 만들 수 있다.
DROP TABLE IF EXISTS monthly;
CREATE TABLE monthly(month TEXT PRIMARY KEY, sales INTEGER NOT NULL);
INSERT INTO monthly(month, sales) VALUES
('2026-01', 100), ('2026-02', 140), ('2026-03', 120),
('2026-04', 180), ('2026-05', 160), ('2026-06', 210),
('2026-07', 190), ('2026-08', 230), ('2026-09', 220),
('2026-10', 250), ('2026-11', 270), ('2026-12', 300);
실행 환경별 주의사항
- PowerShell과 일반 터미널 모두 sqlite3 :memory:로 SQLite 콘솔을 연 뒤 sqlite> 프롬프트에서 .read starter.sql을 실행한다.
- 점(.)으로 시작하는 .mode, .import, .headers 명령은 SQLite CLI 전용이다. DB Browser에서는 browser-starter.sql에서 TODO를 먼저 완성한다.
환경을 준비했으면 코드가 해결하는 문제를 먼저 이해한 뒤 순서대로 실행한다.
3단계. 코드를 실행하며 원리 확인하기
SQLite의 윈도 함수는 결과 집합의 각 행에 대해 연관된 행들의 윈도를 정의하고 이를 바탕으로 집계나 위치 계산을 수행한다. 핵심 근거인 OVER 절 내에서 ORDER BY month를 지정하면 데이터의 논리적 연산 순서가 결정된다.
누적합 계산 시 사용하는 SUM 함수는 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 프레임을 명시하여 첫 행부터 현재 행까지의 누적 범위를 확정한다. 반면 LAG 함수는 별도의 집계 프레임 없이 현재 행을 기준으로 지정된 오프셋만큼 이전 행의 값을 가져오므로 전월 실적과의 비교를 수행할 때 매우 효율적이다.
윈도 함수는 일반적인 집계 함수와 달리 행의 정체성을 유지하며 계산 결과를 각 행 옆에 직접 붙여주므로 시계열 분석의 효율성을 극대화하며 쿼리 가독성을 획기적으로 개선한다.
코드를 읽기 전에 알아둘 표현
| 표현 | 쉬운 뜻 | 이 데이터에서의 결과 |
|---|---|---|
| CAST(값 AS INTEGER) | 문자열 형태의 숫자를 정수 계산이 가능한 값으로 바꾼다. | CAST(‘140’ AS INTEGER)는 140이다. |
| OVER (ORDER BY month) | 월 순서대로 행을 읽으며 윈도 계산을 수행한다. | 2026-01 다음에 2026-02를 계산한다. |
| LAG(sales) | 현재 행 바로 앞 행의 값을 가져온다. | 2월 행에서는 1월 매출 100을 가져온다. |
위 표현의 역할을 확인했으면 starter 파일에서 바꿀 두 수식을 먼저 살펴본다. 수식의 결과를 이해한 뒤 전체 코드를 실행하면 각 줄을 외우지 않아도 된다.
starter에서 완성할 수식
1. 현재 월까지 누적합 구하기
SUM(sales) OVER (
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
첫 달부터 현재 행까지 sales를 더한다. 2월 행에서는 1월 100과 2월 140을 더해 running_sales=240이 된다.
2. 전월 대비 증감 구하기
sales - LAG(sales) OVER (ORDER BY month)
현재 월 매출에서 바로 앞 월 매출을 뺀다. 2월에서는 140-100=40이고 첫 달은 앞 행이 없어 결과가 비어 있다.
1. starter 파일에서 시작하기
SQLite CLI 사용자는 텍스트 편집기로 starter.sql의 두 TODO를 수정·저장한 뒤 sqlite> 프롬프트에서 .read starter.sql을 다시 실행한다. DB Browser 사용자는 browser-starter.sql의 두 TODO를 수정한 뒤 스크립트 전체를 다시 실행한다.
-- TODO: month와 sales를 출력하고, 월 순서에 따른 누적합과 전월 대비 증감을 계산하세요.
2. CSV 데이터를 정수형으로 변환하여 적재한다
monthly_sales.csv 파일은 2026년 1월부터 12월까지의 매출 정보를 담고 있다. SQLite CLI에서 .import 명령을 통해 데이터를 가져올 때 CSV의 모든 값은 기본적으로 문자열로 취급될 수 있다.
따라서 산술 연산을 수행하기 위해 CAST(sales AS INTEGER) 구문을 사용하여 명시적으로 정수 변환을 수행한다. 데이터 적재 직후 전체 행 수가 12행인지 확인하는 과정은 이후의 누적합이나 순위 계산에서 누락된 월이 있는지 검증하는 첫 번째 단계가 된다.
이 기초 작업이 선행되어야만 윈도 함수의 연산 결과가 실제 비즈니스 수치와 일치함을 보장할 수 있다.
3. SUM과 LAG 함수로 핵심 지표를 산출한다
SELECT 문 내에서 SUM은 month 정렬 순서에 따라 1월부터 현재 월까지의 매출을 차례대로 합산하여 running_sales 열을 생성한다. 12월 행에 도달하면 1년 전체 매출의 합계인 2,370이 출력된다.
동시에 LAG 함수는 현재 행의 바로 이전 행 매출을 가져와 현재 매출과의 차이를 계산한다. 예를 들어 2월 행에서는 1월 매출 100을 가져와 140에서 뺀 40을 change 열에 기록하며 이전 데이터가 없는 1월은 결과값이 비어 있게 된다.
이러한 방식은 서브쿼리 없이도 한 줄의 SELECT 문으로 시계열의 누적 상태와 변동폭을 동시에 관찰할 수 있게 해준다.
4. 정답 코드로 비교하기
SELECT month, CAST(sales AS INTEGER) AS sales, SUM(CAST(sales AS INTEGER)) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_sales, CAST(sales AS INTEGER) - LAG(CAST(sales AS INTEGER)) OVER (ORDER BY month) AS change FROM monthly ORDER BY month;
5. 실제 실행 결과 확인하기
month|sales|running_sales|change
2026-01|100|100|
2026-02|140|240|40
2026-03|120|360|-20
2026-04|180|540|60
2026-05|160|700|-20
2026-06|210|910|50
2026-07|190|1100|-20
2026-08|230|1330|40
2026-09|220|1550|-10
2026-10|250|1800|30
2026-11|270|2070|20
2026-12|300|2370|30
출력이 나왔다면 정답과 같은지만 보지 말고, 각 숫자가 입력 데이터에서 어떻게 계산됐는지 거꾸로 확인한다.
4단계. 실행 결과 검산하기
| 확인할 값 | 결과 | 이렇게 읽는다 |
|---|---|---|
| 입력 행 수 | 12행 | 2026년 1월부터 12월까지 월별 한 행 |
| 2월 전월 차이 | 140 - 100 = +40 | 2월 매출이 1월보다 40 증가 |
| 3월 전월 차이 | 120 - 140 = -20 | 3월 매출이 2월보다 20 감소 |
| 12월 누적합 | 2,370 | 12개월 sales를 모두 더한 값과 일치 |
| 연간 순증감 | 300 - 100 = +200 | 월별 change를 모두 더한 값과 일치 |
출력된 데이터에서 12월의 running_sales 값인 2,370은 입력된 12개월 매출의 총합과 정확히 일치하며 이는 누적 계산 프레임이 정상적으로 작동했음을 의미한다. 2월의 change 값이 +40이고 3월이 -20인 수치는 전월 대비 매출의 단기적인 등락 방향을 명확히 나타낸다.
마지막으로 12월 매출 300에서 1월 매출 100을 뺀 값인 200은 모든 월의 change 값을 합산한 결과와 일치해야 정상적인 계산으로 판단한다. 실무적으로는 running_sales를 통해 연간 목표 달성률을 판단하고 change의 부호와 크기를 통해 계절성이나 특정 이벤트에 따른 매출 변동 강도를 분석한다.
특히 전월 대비 마이너스를 기록한 3월, 5월, 7월, 9월의 공통적인 원인을 파악하는 근거 데이터로 활용할 수 있다.
1. 누적값과 원자료 합계가 일치한다
계산 근거: 마지막 running_sales 2,370은 12개월 sales 합계 2,370과 같다.
해석: 누적 윈도의 정렬과 프레임이 의도대로 적용됐는지 확인하는 가장 직접적인 완결성 검사다.
2. 월별 증감은 순증가로 망원경처럼 합쳐진다
계산 근거: 상승분 합계는 +270, 감소분 합계는 -70, 순증감은 +200이다. 이는 마지막 값에서 첫 값을 뺀 +200과 같다.
해석: 각 월의 change를 모두 더한 결과와 시작·종료값 차이가 다르면 누락 월이나 중복 행을 먼저 의심한다.
3. 연간 절반은 하반기 초입에 넘어선다
계산 근거: 2026-07 누적 1,100은 연간의 46.4%, 2026-08 누적 1,330은 56.1%다.
해석: 50% 경계가 어느 시점에 통과되는지 보면 연간 실적이 앞쪽과 뒤쪽 중 어디에 집중되는지 빠르게 읽을 수 있다.
4. 하반기와 4분기에 규모가 집중된다
계산 근거: 상반기 합계는 910, 하반기는 1,460로 1.60배다. 분기 합계는 Q1 360, Q2 550, Q3 640, Q4 820다.
해석: 누적선의 기울기가 연말로 갈수록 커지는 이유를 반기·분기 합계로 분해해 설명할 수 있다.
5. 절대 증감과 변화율은 같은 질문이 아니다
계산 근거: 2026-10과 2026-12의 절대 증감은 모두 +30이지만, 전월 대비 변화율은 각각 13.6%와 11.1%다.
해석: 가장 긴 연속 상승은 3개월이다. 같은 절대 증가라도 기준값이 커지면 변화율은 작아지므로 두 지표를 함께 본다.
기본 결과를 설명할 수 있다면 같은 원리를 응용 문제에 적용한다.
5단계. 직접 풀어보기
2027-01의 매출 270을 추가한 동률 데이터에서 RANK(), DENSE_RANK(), ROW_NUMBER()를 모두 계산한다. 출력 열은 month, sales, sales_rank, dense_rank, row_number 순서로 만들고, 마지막 출력은 month 오름차순으로 정렬한다.
힌트
세 함수의 OVER 안에서 sales를 내림차순으로 정렬한다. ROW_NUMBER는 동률의 순서까지 결정되도록 month를 두 번째 정렬 기준으로 추가한다. CSV의 sales는 TEXT이므로 모든 정렬식에서 정수로 변환한다.
6단계. 모범 답안과 해설
SELECT month, CAST(sales AS INTEGER) AS sales, RANK() OVER (ORDER BY CAST(sales AS INTEGER) DESC) AS sales_rank, DENSE_RANK() OVER (ORDER BY CAST(sales AS INTEGER) DESC) AS dense_rank, ROW_NUMBER() OVER (ORDER BY CAST(sales AS INTEGER) DESC, month) AS row_number FROM monthly ORDER BY month;
실행 결과
month|sales|sales_rank|dense_rank|row_number
2026-01|100|13|12|13
2026-02|140|11|10|11
2026-03|120|12|11|12
2026-04|180|9|8|9
2026-05|160|10|9|10
2026-06|210|7|6|7
2026-07|190|8|7|8
2026-08|230|5|4|5
2026-09|220|6|5|6
2026-10|250|4|3|4
2026-11|270|2|2|2
2026-12|300|1|1|1
2027-01|270|2|2|3
해설
매출 300인 2026-12는 세 함수 모두 1이다. 매출 270인 2026-11과 2027-01은 RANK와 DENSE_RANK가 모두 2지만 ROW_NUMBER는 month 순서에 따라 2와 3으로 나뉜다.
다음 매출 250에서 RANK는 4로 건너뛰고 DENSE_RANK는 3을 사용한다. 이 동률 fixture 덕분에 세 함수의 차이를 출력에서 직접 관찰할 수 있다.
정답을 맞혔더라도 아래 항목까지 설명할 수 있어야 실무에서 같은 실수를 피할 수 있다.
마무리: 실무 체크리스트
주의 1. 월 문자열 정렬 오류로 인한 계산 왜곡 현상은 2026-10이 2026-2보다 앞에 오는 등 월 형식이 YYYY-MM 두 자리로 규격화되지 않았을 때 발생한다. LENGTH(month)를 조회하여 길이가 다른 행이 있는지 확인하고 정렬 키를 정규화하거나 날짜형 변환을 거쳐야만 정확한 시계열 순서를 보장할 수 있다.
주의 2. 중복 행 발생에 따른 누적합 중복 계산은 같은 월에 데이터가 두 행 이상 존재할 때 나타난다. ROWS 프레임은 각 행을 개별적으로 처리하지만 RANGE 프레임은 동일한 값을 한꺼번에 더하기 때문이다. COUNT(DISTINCT month)와 전체 행 수를 비교하여 월별 유일성을 먼저 검증하는 절차가 필요하다.
확장 1. RANK 및 DENSE_RANK를 활용한 매출 순위 분석을 확장할 수 있다. 매출이 동일한 달이 발생했을 때 순위를 건너뛸지 혹은 연속적인 번호를 부여할지에 따라 적절한 윈도 함수를 선택하여 성과 우수 월을 판별한다. 특히 동률 매출이 있는 fixture에서 세 함수의 출력 차이를 직접 대조하면 용도 구분이 명확해진다.
확장 2. 달력 테이블 연계를 통한 시계열 보간 작업은 데이터에 특정 월이 누락된 경우에 필수적이다. LAG는 달력상 전월이 아닌 물리적 직전 행을 가져오므로 중간에 빠진 달이 있다면 증감폭 계산이 왜곡된다. 외부 조인으로 빈 월을 0으로 채운 뒤 윈도 함수를 적용해야 정확한 월간 변동률을 산출할 수 있다.
공식 문서
문서 확인일: 2026-07-31
'데이터분석&시각화' 카테고리의 다른 글
| [2026-08-04 | DEV-LAB] SQLite 윈도 함수로 월별 매출 누적합과 전월 대비 증감 구하기 (0) | 2026.08.04 |
|---|---|
| [2026-08-03 | DEV-LAB] SQLite에서 NULL 평균의 분모를 직접 검증하기 (0) | 2026.08.03 |
| [2026-07-30 | DEV-LAB] SQLite에서 NULL 평균의 분모를 직접 검증하기 (0) | 2026.07.30 |
| [2026-07-29 | DEV-LAB] SQLite 윈도 함수로 월별 매출 누적합과 전월 대비 증감 구하기 (0) | 2026.07.29 |
| [2026-07-28 | DEV-LAB] SQLite에서 NULL 평균의 분모를 직접 검증하기 (0) | 2026.07.28 |
