← 개발일지

영업일 계산을 위한 비영업일 테이블 설계 (Oracle)


ℹ️ 이 글은 **Claude (Anthropic)**의 도움을 받아 작성되었습니다. 테이블 설계 논의와 DDL 초안 검토를 Claude와 진행한 뒤, 그 과정을 정리한 글입니다.

왜 비영업일 테이블이 필요한가

금융, 물류, 정산 등 다양한 도메인에서 "N 영업일 후"를 계산해야 하는 상황이 생깁니다. 단순히 캘린더상의 날짜에 N을 더하면 안 되고, 주말과 공휴일을 빼야 합니다. 매번 로직에서 요일 판단 + 공휴일 하드코딩을 하면 유지보수가 지옥이 되므로, 비영업일을 테이블로 관리하고 쿼리 한 번으로 판단하는 구조가 필요합니다.

데이터 소스로는 공공데이터 포털의 공휴일 API를 활용할 수 있습니다. XML로 해당 연도의 공휴일 목록을 내려주므로, 배치로 파싱해서 테이블에 INSERT하면 됩니다.

설계 시 핵심 결정 3가지

주말을 테이블에 넣을 것인가

두 가지 방법이 있습니다.

방법 A: 주말도 비영업일 테이블에 INSERT. 영업일 판단 시 NOT EXISTS 한 번이면 끝. 연간 약 130건(주말 104 + 공휴일 20 내외)으로 데이터량 부담 없음.

방법 B: 주말은 로직에서 요일 판단, 테이블은 공휴일만 관리. 데이터는 적지만 영업일 계산 로직이 복잡해짐.

방법 A를 선택했습니다. 연간 130건은 성능에 영향이 없고, 가장 큰 장점은 예외 처리가 깔끔하다는 점입니다. 토요일이지만 특근으로 영업일 처리해야 할 때, 해당 레코드의 사용 여부 플래그만 'N'으로 바꾸면 됩니다. 주말을 로직으로 처리하면 "영업일 예외" 테이블을 별도로 만들어야 합니다.

UNIQUE 제약 범위

처음에는 기준일자 단독으로 UNIQUE를 걸었지만, 같은 날짜에 복수 사유가 등록될 수 있습니다. 예를 들어 공공데이터 API로 들어온 공휴일과 회사 자체 휴무가 같은 날에 겹치는 경우입니다. 그래서 (기준일자 + 데이터출처코드) 복합 UNIQUE로 변경했습니다. 이렇게 하면 출처별로 데이터를 독립적으로 관리할 수 있어서, "API 데이터만 연간 갱신"같은 운영이 가능합니다.

업종별 비영업일 차이

공공데이터 API를 그대로 INSERT하면 안 되는 케이스가 있습니다. 대표적으로 노동절(5월 1일)은 근로기준법상 유급휴일이지만 관공서 공휴일은 아닙니다. 금융권이면 비영업일, 관공서면 영업일입니다. API 응답을 INSERT할 때 업종에 맞게 필터링하거나, 전부 넣고 사용 여부 플래그로 비활성화하는 방식을 선택해야 합니다.

테이블 구조

핵심 컬럼만 정리하면 이렇습니다.

기준일자(VARCHAR2(8), YYYYMMDD): 비영업일 날짜. DATE 타입 대신 문자열을 쓰는 건 한국 SI에서 사실상 표준이고, 범위 조회 시 문자열 비교로 충분합니다.

일유형코드: 공휴일(HOL), 대체공휴일(SUB), 토요일(SAT), 일요일(SUN), 회사휴무(CMP) 등을 구분합니다. 공통코드 테이블에 그룹코드를 잡아서 관리하는 게 좋습니다.

데이터출처코드: 공공데이터 API(API), 수동 등록(MAN), 배치 생성(BAT, 주말용) 등. 출처를 추적해야 연간 갱신 배치에서 기존 데이터를 정리할 수 있습니다.

사용여부: 기본값 'Y'. 특근일 등 예외 시 'N'으로 전환하면 영업일 계산에서 제외됩니다.

DDL 예시

CREATE TABLE NON_BSNS_DAY (
    NON_BSNS_DAY_SQ    NUMBER            NOT NULL,
    BASE_DT             VARCHAR2(8)       NOT NULL,
    DAY_TP_CD           VARCHAR2(10)      NOT NULL,
    HOLDY_NM            VARCHAR2(100),
    DATA_SRC_CD         VARCHAR2(10)      NOT NULL,
    USE_YN              VARCHAR2(1)       DEFAULT 'Y' NOT NULL,
    -- audit columns omitted for brevity
    CONSTRAINT PK_NON_BSNS_DAY PRIMARY KEY (NON_BSNS_DAY_SQ)
);

-- Prevent duplicate entries for the same date + source
CREATE UNIQUE INDEX UK_NON_BSNS_DAY_01
    ON NON_BSNS_DAY (BASE_DT, DATA_SRC_CD);

CREATE SEQUENCE SQ_NON_BSNS_DAY START WITH 1 INCREMENT BY 1 NOCACHE;

인덱스는 복합 UNIQUE 하나면 충분합니다. BASE_DT가 선두 컬럼이므로 날짜 기준 range scan도 이 인덱스로 커버됩니다. 연간 130건 수준에서 별도 인덱스를 추가하는 건 관리 부담만 늘립니다.

영업일 판단 쿼리

-- Check if a date is a business day
SELECT CASE
    WHEN EXISTS (
        SELECT 1 FROM NON_BSNS_DAY
        WHERE BASE_DT = :target_dt AND USE_YN = 'Y'
    ) THEN 'N'
    ELSE 'Y'
END AS IS_BSNS_DAY
FROM DUAL;

주말을 테이블에 포함했기 때문에 요일 판단 로직이 없습니다. 이 단순함이 주말 포함 방식의 핵심 이점입니다.

운영 시나리오

매년 연초에 세 가지 배치를 순서대로 실행합니다.

  1. 해당 연도 주말 데이터 자동 생성 (토/일 각각 INSERT, 출처코드 BAT)
  2. 공공데이터 포털 API 호출하여 공휴일/대체공휴일 INSERT (출처코드 API)
  3. 관리자가 회사 자체 휴무를 수동 등록 (출처코드 MAN)

이후 특근일이 발생하면 해당 레코드의 USE_YN'N'으로 UPDATE합니다.

정리

비영업일 테이블 설계에서 가장 중요한 건 "주말을 어떻게 처리할 것인가"와 "같은 날짜에 복수 사유를 허용할 것인가"입니다. 두 결정이 인덱스 전략, 조회 로직, 운영 방식을 모두 결정합니다. 데이터량이 적은 마스터 테이블이므로 로직 단순화에 무게를 두는 게 장기적으로 유리합니다.