0Pricing
Coding Interview Prep · 강의

연속된 달력 날짜 감지하기

날짜 산술과 행 번호를 사용해 끊기지 않은 날짜 구간을 찾습니다.

연속된 달력 날짜 감지하기은(는) 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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 연속된 달력 날짜 감지하기
  2. 사용자별 최장 연속 기록
  3. 조건을 충족하는 N개의 연속 행
  4. 오늘 기준 현재 활성 연속 기록
← Coding Interview Prep(으)로 돌아가기