데이터 베이스/설계 2편, 실무에서 반드시 마주치는 9가지 설계 패턴

Ch01. 공통 코드 설계 - 그룹화 설계

webmaster 2026. 7. 10. 19:08

그룹 코드와 상세 코드 분리

공통 코드를 개의 테이블로 분리한다. 그룹을 관리하는 테이블과 상세 코드를 관리하는 테이블이다.

그룹 코드와 상세 코드 분리

DROP TABLE IF EXISTS common_code;
DROP TABLE IF EXISTS common_code_detail;
DROP TABLE IF EXISTS common_code_group;

-- 그룹 코드 테이블
CREATE TABLE common_code_group (
	group_code VARCHAR(50) PRIMARY KEY,
	group_name VARCHAR(100) NOT NULL,
	description VARCHAR(500),
	use_yn CHAR(1) NOT NULL DEFAULT 'Y',
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
	CURRENT_TIMESTAMP
);

-- 상세 코드 테이블
CREATE TABLE common_code_detail (
	group_code VARCHAR(50) NOT NULL,
	code VARCHAR(50) NOT NULL,
	name VARCHAR(100) NOT NULL,
	description VARCHAR(500),
	sort_order INT NOT NULL DEFAULT 0,
	use_yn CHAR(1) NOT NULL DEFAULT 'Y',
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
	CURRENT_TIMESTAMP,
	PRIMARY KEY (group_code, code),
	FOREIGN KEY (group_code) REFERENCES common_code_group(group_code)
);
  • 그룹 코드 테이블 (common_code_group)
    • group_code: 그룹을 식별하는 코드 (예: ORDER_STATUS, MEMBER_GRADE)
    • group_name : 그룹의 표시 이름 (예: 주문상태, 회원등급)
    • description : 그룹에 대한 설명
    • use_yn: 사용 여부 (Y/N). 코드 그룹 전체를 비활성화할 때 사용
    • created_at , updated_at : 생성일시, 수정일시
  • 상세 코드 테이블 (common_code_detail)
    • group_code: 이 코드가 속한 그룹
    • code: 실제 코드값
    • name: 코드의 표시 이름
    • description : 코드에 대한 설명
    • sort_order: 정렬 순서. 화면에 목록을 표시할 때 순서를 지정
    • use_yn : 사용 여부. 특정 코드만 비활성화할 때 사용
    • created_at, updated_at: 생성일시, 수정일시

기본 키는 "(group_code, code)" 복합 키다. 같은 그룹 내에서만 코드가 유일하면 되므로, 다른 그룹에서는 같은 드값을 사용할 있다.

 

use_yn은 왜 CHAR(1)인가요?

"MySQL에서 BOOLEAN을 사용하면 내부적으로 TINYINT(1)(숫자 0, 1)로 저장된다. 둘 다 1 Byte를 사용하므로 저장 효율은 비슷하지만, 실무에서는 데이터의 의미를 더 명확하게 표현하기 위해('Y'는 "예", 'N'은 아니요) "CHAR(1)"을 사용하여 'Y', 'N' 문자로 저장하는 관습이 널리 퍼져 있다. 특히 대규모 프로젝트나 레거시 시스템과의 호환성을 고려할 때 많이 사용되는 방식이다.

참고로 과거에는 BOOLEAN 타입이 없는 데이터베이스도 있었다. 이 방식은 'Y', 'N' 이외에 다른 단어도 입력할 수 있기 때문에 실수로 인한 버그가 발생할 수 있다. 따라서 최신 프로젝트라면 BOOLEAN을 사용하여 "true/false"로 매핑하는 것이 더 나은 방법이다."

여기서는 레거시를 포함한 다양한 예시를 보여주기 위해 이 방식을 선택했다.

 

데이터 입력

이제 그룹 코드와 상세 코드를 입력해보자. 참고로 실무에서는 어드민 페이지를 만들어서 어드민 페이지를 통해 공통 코드를 등록하고 관리한다.

-- 그룹 코드 입력
INSERT INTO common_code_group (group_code, group_name, description) VALUES
('ORDER_STATUS', '주문상태', '주문의 진행 상태를 나타내는 코드'),
('MEMBER_GRADE', '회원등급', '회원의 등급을 나타내는 코드'),
('PAYMENT_STATUS', '결제상태', '결제의 진행 상태를 나타내는 코드'),
('PAYMENT_METHOD', '결제수단', '결제 방법을 나타내는 코드');

SELECT * FROM common_code_group;

쿼리 결과

 

상세 코드 입력

-- 상세 코드 입력
INSERT INTO common_code_detail (group_code, code, name, sort_order) VALUES

-- 주문 상태
('ORDER_STATUS', 'ORDER', '주문접수', 1),
('ORDER_STATUS', 'PAID','결제완료', 2),
('ORDER_STATUS', 'SHIPPING','배송중', 3),
('ORDER_STATUS', 'DELIVERED','배송완료', 4),
('ORDER_STATUS', 'CANCEL','주문취소', 5),

-- 회원 등급
('MEMBER_GRADE', 'NORMAL','일반회원', 1),
('MEMBER_GRADE', 'VIP', 'VIP회원', 2),
('MEMBER_GRADE', 'VVIP', 'VVIP회원', 3),

-- 결제 상태
('PAYMENT_STATUS', 'PENDING', '결제대기', 1),
('PAYMENT_STATUS', 'COMPLETE','결제완료', 2),
('PAYMENT_STATUS', 'FAILED','결제실패', 3),
('PAYMENT_STATUS', 'CANCEL','결제취소', 4),
('PAYMENT_STATUS', 'REFUND', '환불완료', 5),

-- 결제 수단
('PAYMENT_METHOD', 'CARD','신용카드', 1),
('PAYMENT_METHOD', 'BANK','계좌이체', 2),
('PAYMENT_METHOD', 'VIRTUAL','가상계좌', 3),
('PAYMENT_METHOD', 'MOBILE','휴대폰결제', 4);

SELECT * FROM common_code_detail ORDER BY group_code, sort_order;

쿼리 결과

  • 주목할 점은 "ORDER_STATUS"의 "CANCEL"과 "PAYMENT_STATUS"의 "CANCEL"이 공존한다는 것이다.
  • 그룹이 다르기 때문에 같은 코드값을 사용할 있다.

 

실제 테이블에 적용

DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS members;

-- 회원 테이블
CREATE TABLE members (
	member_id BIGINT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(50) NOT NULL,
	email VARCHAR(100) NOT NULL,
	grade VARCHAR(20) NOT NULL DEFAULT 'NORMAL',
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 주문 테이블
CREATE TABLE orders (
	order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
	member_id BIGINT NOT NULL,
	order_status VARCHAR(20) NOT NULL DEFAULT 'ORDER',
	total_amount INT NOT NULL,
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	FOREIGN KEY (member_id) REFERENCES members(member_id)
);
-- 결제 테이블
CREATE TABLE payments (
	payment_id BIGINT PRIMARY KEY AUTO_INCREMENT,
	order_id BIGINT NOT NULL,
	payment_method VARCHAR(20) NOT NULL,
	payment_status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
	amount INT NOT NULL,
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
-- 회원 데이터
INSERT INTO members (name, email, grade) VALUES
('션', 'seon@example.com', 'NORMAL'),
('네이트', 'nate@example.com', 'VIP'),
('이순신', 'lee@example.com', 'VVIP');

-- 주문 데이터
INSERT INTO orders (member_id, order_status, total_amount) VALUES
(1, 'ORDER', 50000),
(1, 'PAID', 75000),
(2, 'SHIPPING', 120000),
(2, 'DELIVERED', 85000),
(3, 'CANCEL', 45000);

-- 결제 데이터
INSERT INTO payments (order_id, payment_method, payment_status, amount) VALUES
(1, 'CARD', 'PENDING', 50000),
(2, 'CARD', 'COMPLETE', 75000),
(3, 'BANK', 'COMPLETE', 120000),
(4, 'MOBILE', 'COMPLETE', 85000),
(5, 'CARD', 'CANCEL', 45000);

 

공통 코드와 조인하여 조회

회원 목록 조회

SELECT
	m.member_id,
	m.name,
	m.email,
	m.grade,
	c.name AS grade_name
FROM members m
JOIN common_code_detail c
ON c.group_code = 'MEMBER_GRADE' AND m.grade = c.code
ORDER BY m.member_id;

쿼리 결과

  • 코드를 구분할 때 "group_code" , "code" 두 필드를 모두 구분해야 한다.

 

주문 목록 조회

SELECT
	o.order_id,
	m.name AS member_name,
	o.order_status,
	c.name AS status_name,
	o.total_amount
FROM orders o
JOIN members m ON o.member_id = m.member_id
JOIN common_code_detail c
ON c.group_code = 'ORDER_STATUS' AND o.order_status = c.code
ORDER BY o.order_id;

쿼리 결과

결제 목록 조회 (여러 공통 코드 조인)

SELECT
    p.payment_id,
    o.order_id,
    p.payment_method,
    pm.name AS method_name,
    p.payment_status,
    ps.name AS status_name,
    p.amount
FROM payments p
JOIN orders o ON p.order_id = o.order_id
JOIN common_code_detail pm
ON pm.group_code = 'PAYMENT_METHOD' AND p.payment_method = pm.code
JOIN common_code_detail ps
ON ps.group_code = 'PAYMENT_STATUS' AND p.payment_status = ps.code
ORDER BY p.payment_id;

쿼리 결과

특정 그룹의 코드 목록 조회

화면에서 드롭다운 목록을 표시할 때는 특정 그룹의 코드 목록이 필요하다.

SELECT code, name
FROM common_code_detail
WHERE group_code = 'ORDER_STATUS'
AND use_yn = 'Y'
ORDER BY sort_order;

쿼리 결과

  • "use_yn" 필드를 사용해서 화면에 출력할 필드만 필터링할 수 있다.
  • 만약 특정 상태를 노출하고 싶지 않다면 공통 코드 테이블에서 "use_yn"만 "N"으로 변경하면 된다.

코드 비활성화

특정 코드를 이상 사용하지 않으려면 "use_yn" "N"으로 변경한다. 예를 들어 가상계좌 결제를 중단한다면?

UPDATE common_code_detail
SET use_yn = 'N'
WHERE group_code = 'PAYMENT_METHOD' AND code = 'VIRTUAL';

-- 결제 수단 목록 다시 조회
SELECT code, name
FROM common_code_detail
WHERE group_code = 'PAYMENT_METHOD'
AND use_yn = 'Y'
ORDER BY sort_order;

쿼리 결과

  • 가상계좌가 목록에서 제외되었다.
  • 기존에 가상계좌로 결제된 데이터는 그대로 유지되면서, 새로운 결제에서는 가상계좌를 선택할 수 없게 된다.
  • 보통 이런 모든 조작은 어드민 툴을 통해서 운영자가 실시간으로 편리하게 관리할 수 있도록 한다.