연속된 달력 날짜 감지하기
날짜 산술과 행 번호를 사용해 끊기지 않은 날짜 구간을 찾습니다.
연속된 달력 날짜 감지하기은(는) CoddyKit의 무료 Coding Interview Prep 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Coding Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Coding Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
면접 문제의 설정
면접관은 윈도 함수와 날짜 산술을 실제로 이해하고 있는지 확인할 수 있기 때문에 연속 구간 문제를 좋아합니다. 전형적인 문제는 다음과 같습니다. “사용자 로그인 날짜 테이블이 주어졌을 때, 연속된 달력 날짜로 이루어진 각 끊기지 않은 구간을 찾으세요.”
보통은 모든 행을 다음 행과 비교하는 자체 조인을 먼저 떠올리지만, 큰 테이블에서는 처리량이 폭발하고 표현하기도 불편합니다. 전문적인 답은 공백과 구간 기법을 사용하는 것입니다. 이 과정에서는 행 번호와 날짜 뺄셈을 사용해 연속된 날짜를 깔끔하게 감지하는 방법을 배웁니다.
예시 데이터
이 과정에서는 사용자가 활동한 날마다 사용자별로 한 행이 있는 logins 테이블을 사용합니다. 중복은 이미 제거되어 있다고 가정합니다. 즉, 달력 날짜마다 로그인이 하나씩만 있습니다.
user_id— 로그인한 사용자login_date— DATE 값
사용자 1의 날짜는 1월 1일, 2일, 3일, 그다음 공백, 그리고 1월 6일과 7일입니다. 따라서 3일 구간과 2일 구간, 총 두 개의 연속 구간이 나올 것으로 예상합니다.
SELECT * FROM logins ORDER BY user_id, login_date;
-- user_id | login_date
-- 1 | 2024-01-01
-- 1 | 2024-01-02
-- 1 | 2024-01-03
-- 1 | 2024-01-06
-- 1 | 2024-01-07핵심 통찰
이것이 모든 연속 날짜 문제를 푸는 핵심 요령입니다. 날짜순으로 행을 정렬하고 각 행에 순차적인 행 번호를 부여하면, 연속된 날짜로 이루어진 어떤 구간에서도 날짜와 행 번호의 차이가 일정하게 유지됩니다.
왜 그럴까요? 연속된 날짜에서는 날짜와 행 번호가 각각 정확히 1씩 증가하므로 둘의 차이가 변하지 않습니다. 공백이 생기면 날짜는 건너뛰지만 행 번호는 그렇지 않으므로, 일정한 값이 깨지고 새 그룹이 시작됩니다.
차이 확인하기
사용자 1의 데이터를 손으로 따라가 보겠습니다. ROW_NUMBER는 1, 2, 3, 4, 5를 셉니다. 행 번호를 일수로 보고 날짜에서 빼면서 결과를 확인하세요.
- 1월 1일 − 1 = 12월 31일
- 1월 2일 − 2 = 12월 31일
- 1월 3일 − 3 = 12월 31일
- 1월 6일 − 4 = 1월 2일
- 1월 7일 − 5 = 1월 2일
처음 세 행은 12월 31일을 공유하고, 마지막 두 행은 1월 2일을 공유합니다. 이처럼 공유되는 기준값이 우리의 그룹 키입니다.
ROW_NUMBER 추가
첫 번째 구체적인 단계는 사용자별로 행 번호를 추가하는 것입니다. 사용자별로 파티션을 나누므로 연속 구간이 사용자 경계를 넘지 않으며, 날짜순으로 정렬합니다.
PARTITION BY user_id는 사용자마다 카운터를 다시 시작하고, ORDER BY login_date는 순서가 달력을 따르도록 보장합니다.
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY login_date
) AS rn
FROM logins;그룹 기준값 계산
이제 login_date에서 rn일을 뺍니다. PostgreSQL에서는 날짜에서 정수 일수를 직접 뺄 수 있습니다. 그 결과 각 구간을 식별하는 일정한 기준값이 나옵니다.
같은 SELECT에서 정의한 별칭 rn을 참조할 수는 없습니다. 따라서 먼저 이전 쿼리를 CTE나 하위 쿼리로 감싸야 합니다.
WITH numbered AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM logins
)
SELECT
user_id,
login_date,
login_date - rn AS grp
FROM numbered;구간 그룹화
기준값을 구했으므로 모든 연속 구간은 동일한 grp 값을 공유합니다. user_id와 grp를 기준으로 그룹화한 다음 집계하여 각 구간의 시작, 끝, 길이를 구합니다.
MIN(login_date)— 연속 구간의 첫날MAX(login_date)— 연속 구간의 마지막 날COUNT(*)— 연속 구간의 날짜 수
WITH numbered AS (
SELECT user_id, login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM logins
)
SELECT
user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS streak_len
FROM numbered
GROUP BY user_id, login_date - rn
ORDER BY user_id, streak_start;SQL 방언별 차이
날짜 산술의 문법은 SQL 방언마다 다릅니다. 면접에서 이 점을 언급하면 폭넓은 이해를 보여 줄 수 있습니다.
- PostgreSQL:
login_date - rn(날짜에서 정수 일수를 뺌) - MySQL:
DATE_SUB(login_date, INTERVAL rn DAY) - SQL 서버:
DATEADD(day, -rn, login_date)
논리는 동일하고 함수 이름만 다릅니다. 이식 가능한 사고방식은 “각 날짜를 자신의 위치만큼 과거로 이동하면, 연속 구간이 하나의 일정한 값으로 통합된다”는 것입니다.
-- SQL Server version of the anchor
DATEADD(day, -1 * rn, login_date) AS grp왜 자체 조인이 아닌가
면접관은 왜 l1.login_date = l2.login_date + 1과 같은 자체 조인을 사용하지 않았는지 물을 수 있습니다. 다음과 같이 답할 수 있습니다.
- 자체 조인은 전체 구간이 아니라 인접성만 검사합니다. 완전한 연속 구간을 구성하려면 여전히 그룹화가 필요합니다.
- 좋은 인덱스가 없으면 행이 확장되어 O(n²)이 될 수 있습니다.
- 행 번호 방식은 정렬된 데이터를 한 번만 처리하므로 훨씬 확장성이 좋습니다.
윈도 함수는 이러한 문제에서 현대적이고 기대되는 답입니다.
중복에 대비하기
이 기법은 사용자별로 하루에 한 행이라는 가정을 전제로 합니다. 원본에 하루에 여러 번 로그인한 기록이 있으면 같은 날짜의 두 행이 서로 다른 행 번호를 받아 기준값이 망가집니다.
먼저 중복을 제거해 문제를 방지하세요. 타임스탬프를 날짜로 변환한 뒤 DISTINCT를 적용하거나, 같은 날짜가 같은 번호를 갖도록 DENSE_RANK를 ROW_NUMBER 대신 사용하면 됩니다.
WITH days AS (
SELECT DISTINCT user_id, login_ts::date AS login_date
FROM raw_logins
)
SELECT * FROM days;전체 해법
모든 요소를 합치면 각 연속 날짜 구간의 시작, 끝, 길이를 보여 주는 깔끔하고 면접에 바로 쓸 수 있는 답이 됩니다.
중복 제거, 번호 부여, 뺄셈, 그룹화라는 동일한 뼈대가 거의 모든 “연속” 문제를 해결합니다.
WITH days AS (
SELECT DISTINCT user_id, login_ts::date AS login_date
FROM raw_logins
),
numbered AS (
SELECT user_id, login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS rn
FROM days
)
SELECT user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS streak_len
FROM numbered
GROUP BY user_id, login_date - rn
ORDER BY user_id, streak_start;빠른 확인
핵심 요령을 이해했는지 확인합니다.
복습
연속 날짜 문제의 기본 패턴을 배웠습니다.
- 중복 제거로 사용자별 하루 한 행을 만듭니다.
- 날짜순으로 정렬하고 사용자별로 파티션을 나눈 ROW_NUMBER를 사용합니다.
- 행 번호를 날짜에서 뺄셈하여 각 구간의 일정한 기준값을 구합니다.
- 기준값으로 GROUP BY를 수행하고 집계하여 시작, 끝, 길이를 구합니다.
이 공백과 구간 뼈대는 한 번의 처리로 확장 가능하며 자체 조인보다 효율적입니다. 다음에는 이 방법으로 사용자별 최장 연속 구간을 계산합니다.
자주 묻는 질문
“연속된 달력 날짜 감지하기” 강의는 무료인가요?
네 — “연속된 달력 날짜 감지하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Coding Interview Prep 강의 전체를 잠금 해제할 수 있습니다. Coding Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
“연속된 달력 날짜 감지하기”에서 뭘 배우나요?
날짜 산술과 행 번호를 사용해 끊기지 않은 날짜 구간을 찾습니다. 브라우저에서 직접 실행하는 실습 코드로 Coding Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Coding Interview Prep을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Coding Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“연속된 달력 날짜 감지하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Coding Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Coding Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 연속된 달력 날짜 감지하기
- 사용자별 최장 연속 기록
- 조건을 충족하는 N개의 연속 행
- 오늘 기준 현재 활성 연속 기록