LAG로 구한 값이 전날 매출과 다르다면 날짜가 아니라 이전 행을 읽고 있는지 확인하세요. 날짜가 빠진 테이블에서 LAG(sales)는 며칠 전 기록도 가져옵니다. 전일 비교가 목적이면 이전 날짜가 하루 전인지 검사하거나 하루 전 키로 직접 조인합니다.
SQLite 3.53.4에서 실행한 가상 매출 데이터입니다. 날짜는 ISO 형식의 일 단위 문자열이며 한 매장·하루에 최대 한 행만 존재하도록 정의했습니다. 아래 date 함수 문법은 SQLite 기준입니다.

1. 날짜 누락과 매출 결측을 따로 만듭니다
CREATE TABLE daily_sales (
shop TEXT NOT NULL,
day TEXT NOT NULL,
sales INTEGER,
PRIMARY KEY (shop, day)
);
INSERT INTO daily_sales VALUES
('A','2026-10-01',100),
('A','2026-10-03',130),
('A','2026-10-04',160),
('B','2026-10-01',200),
('B','2026-10-02',NULL),
('B','2026-10-03',260);
테이블 생성·6행 입력 완료
A 매장은 10월 2일 행 자체가 없습니다. B 매장은 10월 2일 행이 있지만 매출 값이 NULL입니다. “기록 없음”과 “기록은 있지만 값이 결측”은 같은 상태가 아닙니다. 예제의 기본키는 조인할 전일 기록이 여러 행으로 늘어나는 문제도 막습니다.
2. LAG는 정렬된 이전 기록을 반환합니다
SELECT shop, day, sales,
LAG(day) OVER (
PARTITION BY shop ORDER BY day
) AS prev_day,
LAG(sales) OVER (
PARTITION BY shop ORDER BY day
) AS prev_sales
FROM daily_sales
ORDER BY shop, day;
shop | day | sales | prev_day | prev_sales
A | 2026-10-01 | 100 | NULL | NULL
A | 2026-10-03 | 130 | 2026-10-01 | 100
A | 2026-10-04 | 160 | 2026-10-03 | 130
B | 2026-10-01 | 200 | NULL | NULL
B | 2026-10-02 | NULL | 2026-10-01 | 200
B | 2026-10-03 | 260 | 2026-10-02 | NULL
A의 10월 3일에는 prev_day가 10월 1일, prev_sales가 100입니다. 2일이 비어 있어 이전 기록까지 이틀을 건너뜁니다. 이 값과의 차이 30을 전일 증감으로 이름 붙이면 보고서가 잘못됩니다.
PARTITION BY shop은 매장끼리 기록이 섞이지 않게 합니다. 날짜를 유일하게 정렬할 수 없는 이벤트 데이터라면 동일 시각 안의 순서를 정할 고유 키도 필요합니다. 하루 단위 비교를 할 때는 원본 이벤트를 하루·매장 단위로 먼저 집계하는 방법을 검토하세요.
3. 이전 날짜가 실제 하루 전일 때만 계산합니다
WITH compared AS (
SELECT shop, day, sales,
LAG(day) OVER (
PARTITION BY shop ORDER BY day
) AS prev_day,
LAG(sales) OVER (
PARTITION BY shop ORDER BY day
) AS prev_sales
FROM daily_sales
)
SELECT shop, day,
CASE
WHEN prev_day = date(day, '-1 day')
THEN sales - prev_sales
ELSE NULL
END AS day_change
FROM compared
ORDER BY shop, day;
shop | day | day_change
A | 2026-10-01 | NULL
A | 2026-10-03 | NULL
A | 2026-10-04 | 30
B | 2026-10-01 | NULL
B | 2026-10-02 | NULL
B | 2026-10-03 | NULL
A의 10월 3일은 하루 전 기록이 없으므로 NULL, 10월 4일은 전날 130이 있어 160−130=30입니다. B의 10월 3일은 하루 전 행이 있어도 매출이 NULL이어서 차이 역시 NULL입니다. 날짜 연속성만으로 값이 유효하다고 보장되지는 않습니다.

4. 전일 키로 LEFT JOIN하면 빈 날짜를 직접 드러냅니다
SELECT d.shop, d.day, p.day AS prev_day,
p.sales AS prev_sales,
d.sales - p.sales AS day_change
FROM daily_sales AS d
LEFT JOIN daily_sales AS p
ON p.shop = d.shop
AND p.day = date(d.day, '-1 day')
ORDER BY d.shop, d.day;
shop | day | prev_day | prev_sales | day_change
A | 2026-10-01 | NULL | NULL | NULL
A | 2026-10-03 | NULL | NULL | NULL
A | 2026-10-04 | 2026-10-03 | 130 | 30
B | 2026-10-01 | NULL | NULL | NULL
B | 2026-10-02 | 2026-10-01 | 200 | NULL
B | 2026-10-03 | 2026-10-02 | NULL | NULL
A의 10월 3일에는 매칭되는 전일 행이 없어 prev_day도 NULL입니다. B의 10월 3일에는 prev_day=10월 2일이 남고 prev_sales만 NULL이므로 두 종류의 누락을 구분할 수 있습니다. 전날 행을 찾는 조건은 ON 안에 두어 현재 날짜의 행을 유지합니다.
이 조인은 누락된 날짜의 새 행을 자동 생성하지 않습니다. 모든 날짜를 보고서에 표시하려면 날짜 목록을 따로 만들고 매출과 연결해야 합니다. 영업일 기준 “직전 영업일”은 달력의 하루 전과 다르므로 영업일 달력 또는 명확한 업무 규칙이 필요합니다.
5. LAG의 기본값은 기존 NULL을 일괄 바꾸지 않습니다
SELECT shop, day,
LAG(sales, 1, 0) OVER (
PARTITION BY shop ORDER BY day
) AS prev_or_default
FROM daily_sales
ORDER BY shop, day;
shop | day | prev_or_default
A | 2026-10-01 | 0
A | 2026-10-03 | 100
A | 2026-10-04 | 130
B | 2026-10-01 | 0
B | 2026-10-02 | 200
B | 2026-10-03 | NULL
세 번째 인자 0은 지정한 이전 행이 없을 때 사용됩니다. B의 10월 3일처럼 이전 행은 존재하고 sales가 NULL이면 결과는 NULL입니다. COALESCE로 이를 0으로 채울 수는 있지만, 결측 매출을 실제 0매출로 해석해도 되는지 먼저 정해야 합니다.
SQLite의 LAG는 윈도우 프레임을 추가한다고 “하루 전” 탐색으로 바뀌지 않습니다. 데이터베이스마다 날짜 연산과 NULL 처리 옵션이 다르므로 이 예제를 다른 제품에 옮길 때 해당 문법을 확인하세요.
6. 핵심 정리
이전 기록과 비교하려면 LAG, 달력상 전일과 비교하려면 날짜 검증 또는 전일 키 조인을 사용합니다. 매장별 분리, 하루 단위의 유일성, 날짜 누락과 값 결측, 기본값의 의미를 함께 검사하세요.
날짜가 같은 행의 누적합 범위를 구분하는 문제는 SQL 누적합의 ROWS와 RANGE 실습에서 이어서 볼 수 있습니다.
'데이터베이스(SQL) > 데이터베이스 개념' 카테고리의 다른 글
| SQL 날짜 BETWEEN에서 마지막 날이 빠지는 이유: 반열림 구간으로 조회하기 (0) | 2026.10.11 |
|---|---|
| SQL OFFSET에서 페이지가 중복될 때: 동점 정렬과 키셋 페이지네이션 (0) | 2026.10.10 |
| SQL 상위 3명과 상위 3점수 구분: ROW_NUMBER·RANK·DENSE_RANK (0) | 2026.10.08 |
| SQL 조건부 집계가 전체 행을 세는 이유: COUNT·SUM·CASE 실습 (0) | 2026.10.07 |
| SQL 평균의 평균이 틀리는 이유: SUM·COUNT로 가중 평균 계산 (0) | 2026.10.06 |
댓글