공통 코드 테이블
같은 상태가 여러 철자로 저장되는 오류를 재현하고 그룹·상세 코드와 복합 FK로 회원 게시판 입력 규칙을 고정합니다.
13~18장은 핵심 과정 뒤 필요한 주제를 골라 읽는 선택 심화입니다.
이 장은 상태·유형 값의 표시명, 활성 여부, 배포 수명을 데이터로 운영해야 할 때 선택합니다.
10장의 복합 키·FK와 12장의 최종 DDL을 이해하면 시작할 수 있습니다.
상태값은 짧지만 행 전체의 의미를 바꿉니다.
PUBLISHED, 게시, published가 모두 같은 뜻이라면 화면은 그럴듯해도 집계와 상태 전이는 서로 다른 모집단을 읽습니다.
공통 코드는 모든 문자열을 한 표에 모으는 기술이 아닙니다.
값의 유일 범위, 표시 이름, 사용 여부를 분리하고 실제 업무 행이 올바른 그룹의 코드만 참조하도록 만드는 데이터 규칙입니다.
회원 게시판의 가져오기 경로가 게시글 상태와 공개 범위를 자유 문자열로 받는다고 가정합니다.
같은 PUBLISHED 뜻을 세 철자로 저장한 뒤 게시 시점 합계를 조회해 누락을 확인합니다.
문자열 상태값의 불일치
오타는 SQL 문법 오류가 아니므로 INSERT가 모두 성공합니다.
조회 조건은 정확히 일치하는 문자열만 찾고, 새 언어의 표시 이름을 붙이려면 업무 행을 다시 UPDATE해야 합니다.
DROP TABLE IF EXISTS post_code_bad;
CREATE TABLE post_code_bad (
post_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_status VARCHAR(30) NOT NULL,
visibility VARCHAR(30) NOT NULL,
view_count SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (post_id)
);
INSERT INTO post_code_bad
(post_status, visibility, view_count)
VALUES
('PUBLISHED', 'PUBLIC', 30),
('published', 'manual', 40),
('게시', '직접입력', 50);
SELECT COUNT(*) AS published_count,
SUM(view_count) AS published_views
FROM post_code_bad
WHERE post_status = 'PUBLISHED';세 행이 모두 게시를 뜻하지만 정확히 PUBLISHED인 한 행만 집계됩니다.
published_count | published_views
1 | 30
rows accepted by INSERT: 3
constraint errors: 0애플리케이션 상수만으로는 관리자 SQL, 가져오기, 오래된 서비스가 같은 집합을 쓴다고 보장할 수 없습니다.
반대로 표시 이름을 업무 행에 저장하면 번역이나 문구 변경이 사실 데이터의 대량 수정으로 번집니다.
코드값은 안정적인 식별자이고 표시 이름은 바뀔 수 있는 설명입니다.
두 책임을 나누고 그룹을 키에 포함해야 ACTIVE처럼 여러 문맥에 존재할 수 있는 값도 충돌하지 않습니다.
같은 뜻 세 행이 한 집계에서 갈라지는 지점
- 입력 경로 — API·배치·관리 SQL이 같은 허용 집합을 통과하는지 나열합니다.
- 유일 범위 — 코드 하나가 전역 유일인지 그룹 안에서만 유일인지 정합니다.
- 표시 분리 — 저장 식별자와 사용자에게 보일 문구를 다른 컬럼으로 둡니다.
- 반례 — 대소문자·공백·번역 문자열을 넣어 어느 제약에서 막히는지 확인합니다.
코드 그룹과 허용 값
(group_code, code) 복합 키는 값의 의미 범위를 표현합니다.
업무 테이블도 두 컬럼을 함께 참조해야 다른 그룹의 같은 철자를 잘못 끌어오지 않습니다.
코드 삭제는 기존 사실을 끊을 수 있으므로 보통 is_active로 신규 선택만 막습니다.
이미 저장된 행은 계속 해석할 수 있어야 하며 표시 이름 변경은 식별자 변경과 분리합니다.
이름 공간과 표시 이름을 키 구조로 옮기기
- 이름 공간 — 게시글 상태와 공개 범위를 서로 다른 그룹으로 선언합니다.
- 안정적 키 — 코드값은 배포·로그·API가 오래 참조할 수 있는 영문 식별자로 둡니다.
- 복합 FK — 업무 행의 그룹과 값을 함께 FK로 연결합니다.
- 사용 중단 — 사용 중단은 삭제보다 활성 플래그와 선택 목록 필터로 처리합니다.
공통 코드와 입력 제약
먼저 그룹과 상세 코드를 만들고, 게시글 데모 행에는 상태 그룹과 공개 범위 그룹을 명시합니다.
검사가 그룹 상수를 고정하고 복합 FK가 실제 코드 존재를 검증합니다.
DROP TABLE IF EXISTS final_post_code_demo;
DROP TABLE IF EXISTS final_code;
DROP TABLE IF EXISTS final_code_group;
CREATE TABLE final_code_group (
group_code VARCHAR(40) NOT NULL,
group_name VARCHAR(100) NOT NULL,
description VARCHAR(300) NOT NULL,
PRIMARY KEY (group_code)
) ENGINE=InnoDB;
CREATE TABLE final_code (
group_code VARCHAR(40) NOT NULL,
code VARCHAR(40) NOT NULL,
display_name_ko VARCHAR(100) NOT NULL,
sort_order SMALLINT UNSIGNED NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (group_code, code),
CONSTRAINT fk_final_code_group FOREIGN KEY (group_code)
REFERENCES final_code_group (group_code) ON DELETE RESTRICT,
CONSTRAINT uq_final_code_order UNIQUE (group_code, sort_order),
CONSTRAINT chk_final_code_active CHECK (is_active IN (0, 1))
) ENGINE=InnoDB;
INSERT INTO final_code_group
(group_code, group_name, description)
VALUES
('MEMBER_STATUS', '회원 상태', '가입·정지·탈퇴 수명주기'),
('POST_STATUS', '게시글 상태', '초안·게시·숨김·삭제 수명주기'),
('POST_VISIBILITY', '게시글 공개 범위', '전체·회원·작성자 공개 범위');
INSERT INTO final_code
(group_code, code, display_name_ko, sort_order)
VALUES
('MEMBER_STATUS', 'ACTIVE', '활성', 10),
('MEMBER_STATUS', 'SUSPENDED', '정지', 20),
('MEMBER_STATUS', 'DELETED', '탈퇴', 30),
('POST_STATUS', 'DRAFT', '초안', 10),
('POST_STATUS', 'PUBLISHED', '게시됨', 20),
('POST_STATUS', 'HIDDEN', '숨김', 30),
('POST_STATUS', 'DELETED', '삭제됨', 40),
('POST_VISIBILITY', 'PUBLIC', '전체 공개', 10),
('POST_VISIBILITY', 'MEMBERS_ONLY', '회원 공개', 20),
('POST_VISIBILITY', 'PRIVATE', '작성자 전용', 30);
CREATE TABLE final_post_code_demo (
post_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
status_group_code VARCHAR(40) NOT NULL DEFAULT 'POST_STATUS',
post_status VARCHAR(40) NOT NULL,
visibility_group_code VARCHAR(40) NOT NULL DEFAULT 'POST_VISIBILITY',
visibility_code VARCHAR(40) NOT NULL,
view_count SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (post_id),
CONSTRAINT chk_demo_status_group
CHECK (status_group_code = 'POST_STATUS'),
CONSTRAINT chk_demo_visibility_group
CHECK (visibility_group_code = 'POST_VISIBILITY'),
CONSTRAINT chk_demo_view_count
CHECK (view_count BETWEEN 0 AND 65535),
CONSTRAINT fk_demo_status FOREIGN KEY
(status_group_code, post_status)
REFERENCES final_code (group_code, code),
CONSTRAINT fk_demo_visibility FOREIGN KEY
(visibility_group_code, visibility_code)
REFERENCES final_code (group_code, code)
) ENGINE=InnoDB;
INSERT INTO final_post_code_demo
(post_status, visibility_code, view_count)
VALUES ('PUBLISHED', 'PUBLIC', 30);업무 행은 안정적인 코드만 저장하고 화면은 코드 테이블에서 한국어 이름을 읽습니다.
다른 그룹 값이나 오타는 저장 전에 거절됩니다.
개선 실행 결과code groups: 3
detail codes: 10
post rows: 1
post_status | visibility_code | view_count
PUBLISHED | PUBLIC | 30그룹 컬럼을 반복 저장하는 비용은 있지만 FK가 의미 범위를 직접 보장합니다.
전역 대리키 하나만 저장하면 숫자만으로 그룹을 알 수 없고 배포 간 ID 고정 규칙도 추가로 필요합니다.
is_active=0은 과거 참조를 허용하면서 신규 입력을 막는 정책 신호일 뿐 FK가 자동으로 차단하지는 않습니다.
쓰기 API는 활성 여부를 확인하고 DB에는 모든 과거 코드가 남아 있어야 합니다.
오타·그룹 혼동·문구 변경을 따로 재현하기
- 초기
CREATE— 자식·상세·그룹 역순DROP뒤 부모부터 생성합니다. - 정상 입력 — 상태와 공개 범위 각각 한 값을 복합 FK로 저장합니다.
- 오타 입력 —
COMPLETE를 넣어 1452가 발생하는지 확인합니다. - 표시 변경 — 이름만 바꾸고 게시글 행과 코드값이 그대로인지 비교합니다.
코드값과 표시명 검증
오타와 그룹 혼동은 각각 FK와 검사가 막습니다.
표시 문구 변경은 게시글 행을 수정하지 않고 JOIN 결과만 바뀌어야 합니다.
INSERT INTO final_post_code_demo
(post_status, visibility_code, view_count)
VALUES ('PUBLISH', 'PUBLIC', 20);
-- ERROR 1452: fk_demo_status
INSERT INTO final_post_code_demo
(status_group_code, post_status, visibility_code, view_count)
VALUES ('POST_VISIBILITY', 'PUBLIC', 'PRIVATE', 20);
-- ERROR 3819: chk_demo_status_group
UPDATE final_code
SET display_name_ko = '게시됨'
WHERE group_code = 'POST_STATUS'
AND code = 'PUBLISHED';
SELECT d.post_id,
d.post_status,
status_code.display_name_ko AS status_name,
visibility_code.display_name_ko AS visibility_name,
d.view_count
FROM final_post_code_demo AS d
JOIN final_code AS status_code
ON status_code.group_code = d.status_group_code
AND status_code.code = d.post_status
JOIN final_code AS visibility_code
ON visibility_code.group_code = d.visibility_group_code
AND visibility_code.code = d.visibility_code;두 위반 INSERT는 행을 남기지 않습니다.
정상 행의 post_status는 PUBLISHED로 유지되고 JOIN한 이름만 게시됨으로 바뀝니다.
정렬 규칙과 활성 플래그가 보장하지 않는 것
코드값의 정렬 규칙이 대소문자를 무시하면 PRIVATE와 private가 같은 키가 될 수 있습니다.
API 규칙에서 대소문자를 구분해야 한다면 코드 컬럼의 정렬 규칙을 명시하거나 입력을 대문자로 정규화합니다.
코드 테이블을 모든 도메인 값의 쓰레기통으로 만들지 않습니다.
FK·수명·추가 속성이 독립적인 엔티티라면 전용 테이블이 더 읽기 쉽습니다.
코드 변경을 호환성 배포로 관찰하기
코드 추가·중단은 일반 데이터 수정이 아니라 호환성 변경으로 승인 기록과 영향 API 목록을 남깁니다.
FK 1452, 비활성 코드 입력 시도, 그룹별 코드 수, 중복 sort_order를 운영 지표로 봅니다.
허용 집합을 저장하는 네 방식 비교
| 선택 | 얻는 효과 | 감수할 비용 | 적합한 조건 |
|---|---|---|---|
| 검사 목록 | 단순하고 DB에 가까움 | 이름·정렬 메타데이터 부족 | 값이 적고 배포와 함께 바뀔 때 |
| 전용 코드 테이블 | 강한 의미와 전용 속성 | 테이블 수 증가 | 도메인별 수명이 다를 때 |
| 그룹형 공통 코드 | 여러 선택 목록의 일관된 관리 | 복합 FK와 운영 규칙 | 작고 정적인 목록이 많을 때 |
| 자유 문자열 | 초기 입력이 빠름 | 오타·변경·번역 비용 | 외부 원문을 임시로 적재할 때 |
철자와 그룹을 흔드는 입력 실험
- 오타 — 상태를
PUBLISHD로 보내고 오류 제약 이름을 기록합니다. - 그룹 — 공개 범위 코드
PRIVATE를 상태 위치에 넣어 검사와 FK 순서를 봅니다. - 중단 —
MEMBERS_ONLY를 비활성화한 뒤 기존 행 해석과 신규 선택 정책을 나눕니다. - 번역 — 한국어 이름 변경이 업무 행 수와 집계에 영향을 주지 않는지 확인합니다.
이름 변경·테넌트·외부 원문에서 달라지는 규칙
- 코드 자체를 이름 변경하면 과거 로그·이벤트·외부 규칙이 끊기므로 새 코드와 별칭 전환을 사용합니다.
- 표시 순서는 사용자별 정렬과 다를 수 있어 업무 의미를 부여하지 않습니다.
- 테넌트별 값이 다르면
tenant_id를 키에 포함할지 전역 기준을 복제할지 결정합니다. - 외부 공급자의 원문 상태는 임시 적재에 보존하고 내부 코드로 매핑 오류가 난 행을 격리합니다.
다음 문서는 모든 그룹에 attr1을 붙이는 대신, 특정 코드 집합에만 필요한 정책 속성을 전용 확장 테이블로 배치합니다.
공통 코드 적용 기준
| 판단 축 | 확인할 질문 |
|---|---|
| 크기 | 허용 집합이 작고 대부분 읽기인가? |
| 수명 | 코드 중단 뒤에도 과거 행을 해석해야 하는가? |
| 표시 | 식별자와 이름·번역을 분리할 필요가 있는가? |
| 무결성 | 업무 행이 DB FK로 허용 집합을 참조해야 하는가? |
| 독립성 | 별도 엔티티로 승격할 만큼 관계와 속성이 많은가? |
공통 코드라는 이름보다 값의 유일 범위와 수명을 먼저 설명합니다.
그룹형 테이블을 선택했다면 업무 행까지 복합 FK로 이어져야 중앙 관리가 실제 무결성이 됩니다.
연습 문제
기존 POST_VISIBILITY 그룹에 링크 보유자만 읽는 LINK_ONLY 코드를 추가하고 게시글 데모에 연결하세요.
POST_STATUS의 철자가 같은 값은 공개 범위 위치에 들어오지 않아야 합니다.
해설과 예시 답안
공개 범위 코드를 기존 그룹에 추가하고 복합 FK가 그룹 경계를 지키는지 확인합니다.
INSERT INTO final_code
(group_code, code, display_name_ko, sort_order)
VALUES ('POST_VISIBILITY', 'LINK_ONLY', '링크 보유자 공개', 40);
UPDATE final_post_code_demo
SET visibility_code = 'LINK_ONLY'
WHERE post_id = 1;
UPDATE final_post_code_demo
SET visibility_group_code = 'POST_STATUS',
visibility_code = 'PUBLISHED'
WHERE post_id = 1;
-- ERROR 3819 또는 ERROR 1452정상 변경은 성공하고 다른 그룹 값은 검사 또는 복합 FK에서 거절되어야 합니다.
핵심 정리
- 코드값과 표시 이름은 수명이 다릅니다.
- 그룹을 이름 공간으로 쓰면 상세 키는
(group_code, code)입니다. - 업무 행의 복합 FK가 중앙 목록을 실제 허용 집합으로 만듭니다.
- 사용 중단은 과거 참조를 끊는 삭제와 구분합니다.
다음 문서에서는 코드마다 다른 정책 속성을 범용 슬롯이 아닌 명시적 확장 관계로 설계합니다.