Files
sam-docs/sam/docs/guides/table-design-guide.md
김보곤 8cb15cf3c4 docs: [guides] 테이블 설계 가이드 비전문가용 문서 추가
- options JSON 컬럼 패턴을 엑셀 비유로 쉽게 설명
- 멀티테넌시(tenant_id) 개념 해설
- 실제 SAM 테이블 예시 (주문, 입고, 공정)
- FAQ 5개, 판단 흐름도, 한 장 요약 포함
- INDEX.md에 문서 등록
2026-03-02 17:15:25 +09:00

22 KiB

SAM 테이블 설계 가이드 — 비전문가용

작성일: 2026-03-02 대상 독자: 개발자, 기획자, 관리자 — 데이터베이스를 잘 모르는 분도 읽을 수 있습니다 관련 정책: standards/options-column-policy.md (개발자 전용 상세 규칙)


1. 이 문서는 왜 필요한가?

SAM은 여러 회사가 하나의 시스템을 공유하는 구조입니다. A회사, B회사, C회사가 모두 같은 프로그램을 쓰지만, 각 회사가 필요한 정보는 다릅니다.

이 문서는 SAM에서 데이터를 어떻게 저장하는지, 그 설계 철학을 누구나 이해할 수 있도록 설명합니다.


2. 기본 개념: 데이터베이스 테이블이란?

데이터베이스 테이블은 엑셀 시트와 같습니다.

"주문" 테이블 (= 엑셀 시트)

  열(컬럼)     →  주문번호  │  고객명   │  금액    │  상태
  ─────────────────────────────────────────────────────────
  행(레코드) 1 →  ORD-001  │  김철수   │  500,000 │  완료
  행(레코드) 2 →  ORD-002  │  이영희   │  300,000 │  진행중
  행(레코드) 3 →  ORD-003  │  박민수   │  800,000 │  대기
  • 열(컬럼) = 정보의 종류 (주문번호, 고객명, 금액...)
  • 행(레코드) = 실제 데이터 한 건 (주문 1건)

3. 문제: 회사마다 필요한 정보가 다르다

SAM은 여러 회사가 같은 테이블을 공유합니다.

같은 "주문" 테이블을 쓰는데...

  🏭 A회사 (블라인드 제조)
     → "절곡 각도", "날개 수" 정보가 필요해요

  🏭 B회사 (스크린 제조)
     → "메시 밀도", "소재 종류" 정보가 필요해요

  🏭 C회사 (셔터 제조)
     → "날개 간격", "색상 코드" 정보가 필요해요

3.1 전통적인 해결 방법 (SAM은 이렇게 안 합니다)

필요할 때마다 엑셀에 열을 추가하는 것처럼, 테이블에 컬럼을 추가합니다.

"주문" 테이블 — 전통적 방식

  주문번호 │ 고객명 │ 금액 │ 절곡각도 │ 날개수 │ 메시밀도 │ 소재 │ 날개간격 │ 색상코드
  ─────────────────────────────────────────────────────────────────────────────────
  ORD-001 │ 김철수 │ 50만 │  45도   │  12개 │  (빈칸) │ (빈칸) │ (빈칸) │ (빈칸)    ← A회사
  ORD-002 │ 이영희 │ 30만 │ (빈칸)  │ (빈칸)│   18   │ 폴리  │ (빈칸) │ (빈칸)    ← B회사
  ORD-003 │ 박민수 │ 80만 │ (빈칸)  │ (빈칸)│ (빈칸) │ (빈칸) │  25mm │ #FF0000   ← C회사

문제점:

  • 회사가 100개면? 열이 수백 개로 늘어남
  • 각 회사는 자기 것 빼고 전부 빈칸
  • 새 회사가 들어올 때마다 시스템 전체를 수정해야 함
  • 열 추가 = 시스템 중단 위험이 있는 작업

3.2 SAM의 해결 방법: "메모칸(options)" 하나로 통합

핵심 열만 남기고, 나머지는 메모칸 하나에 자유롭게 적습니다.

"주문" 테이블 — SAM 방식

  주문번호 │ 고객명 │ 금액 │ 상태 │ options (메모칸)
  ────────────────────────────────────────────────────────────────────────
  ORD-001 │ 김철수 │ 50만 │ 완료 │ {"절곡각도": 45, "날개수": 12}           ← A회사
  ORD-002 │ 이영희 │ 30만 │ 진행 │ {"메시밀도": 18, "소재": "폴리에스터"}    ← B회사
  ORD-003 │ 박민수 │ 80만 │ 대기 │ {"날개간격": 25, "색상코드": "#FF0000"}   ← C회사
  ORD-004 │ 최지은 │ 40만 │ 대기 │ null                                     ← 메모 없음

options = JSON이라는 형식의 메모칸. { } 안에 자유롭게 정보를 넣을 수 있습니다.


4. 어떤 정보를 열(컬럼)로 만들고, 어떤 정보를 메모칸(options)에 넣나?

이것이 SAM 테이블 설계의 가장 중요한 판단 기준입니다.

4.1 판단 흐름 (5가지 질문)

새로운 정보를 저장해야 할 때, 아래 질문에 답합니다.

질문 1. 이 정보로 다른 테이블의 데이터를 연결(참조)하나?
        예: 고객ID로 고객 테이블을 찾는다
        → YES: 일반 컬럼

질문 2. 이 정보로 자주 검색(필터)하나?
        예: "완료" 상태인 주문만 보여줘
        → YES: 일반 컬럼

질문 3. 이 정보로 정렬하나?
        예: 최신 주문부터 보여줘
        → YES: 일반 컬럼

질문 4. 이 정보가 절대 중복되면 안 되나?
        예: 주문번호는 세상에 하나뿐이어야 한다
        → YES: 일반 컬럼

질문 5. 이 정보로 합계/평균을 계산하나?
        예: 이번 달 매출 합계
        → YES: 일반 컬럼

질문 1~5 전부 NO → options 메모칸에 저장

4.2 실생활 예시로 비교

예시 1: "주문" 테이블

정보 어디에 저장? 이유
주문번호 일반 컬럼 중복 불가 + 검색 필수
고객 ID 일반 컬럼 고객 테이블과 연결
금액 일반 컬럼 합계 계산 필요
상태 (진행/완료) 일반 컬럼 필터(검색) 필수
생성일 일반 컬럼 정렬 필요
배송지 주소 options 부가 정보, 검색 안 함
수신자 이름 options 부가 정보
수신자 연락처 options 부가 정보
특이사항 메모 options 있어도 되고 없어도 됨

실제 SAM 코드에서 주문(Order) 테이블:

일반 컬럼: id, tenant_id, order_number, client_id, total_amount, status, created_at
options:   {"shipping_cost_code":"착불", "receiver":"홍길동",
            "receiver_contact":"010-1234-5678",
            "shipping_address":"서울 강남구 역삼동 123"}

예시 2: "입고검사" 테이블

정보 어디에 저장? 이유
품목 ID 일반 컬럼 품목 테이블과 연결
수량 일반 컬럼 합계 계산
입고일 일반 컬럼 정렬 + 검색
제조사 options 모든 입고에 있지는 않음
검사 결과 (합격/불합격) options 검사를 안 하는 회사도 있음
검사일 options 선택적 정보

실제 SAM 코드에서 입고(Receiving) 테이블:

일반 컬럼: id, tenant_id, item_id, quantity, received_at, status
options:   {"manufacturer":"삼성전자",
            "inspection_status":"합격",
            "inspection_date":"2026-03-01"}

검사 결과가 options에 있는 이유: 모든 회사가 입고검사를 하는 것은 아닙니다. A회사는 검사를 하고, B회사는 안 합니다. 이걸 일반 컬럼으로 만들면 B회사에겐 항상 빈칸입니다.

예시 3: "공정" 테이블

정보 어디에 저장? 이유
공정 코드 일반 컬럼 중복 불가 + 검색
공정명 일반 컬럼 검색 + 표시
담당 부서 일반 컬럼 필터
작업일지 필요 여부 options 회사별로 다름
검사 필요 여부 options 회사별로 다름
일반 컬럼: id, tenant_id, process_code, process_name, department
options:   {"needs_work_log": true, "needs_inspection": false}

5. 메모칸(options)의 실제 모습: JSON이란?

options에 저장되는 데이터 형식은 JSON입니다. JSON은 프로그래밍 세계의 "구조화된 메모장"이라고 생각하면 됩니다.

5.1 JSON 기본 문법

{                              ← 시작
  "키": "값",                  ← 문자(텍스트)
  "이름": "홍길동",
  "나이": 30,                  ← 숫자 (따옴표 없음)
  "합격": true,                ← 참/거짓 (따옴표 없음)
  "메모": null                 ← 값 없음
}                              ← 끝

5.2 중첩(nested) — 메모 안의 메모

{
  "배송": {                          ← 배송 관련 정보를 묶음
    "주소": "서울 강남구 역삼동",
    "수신자": "홍길동",
    "연락처": "010-1234-5678"
  },
  "검사": {                          ← 검사 관련 정보를 묶음
    "결과": "합격",
    "검사일": "2026-03-01",
    "검사자ID": 5
  }
}

5.3 목록(배열) — 여러 개를 나열

{
  "선택지": [                        ← 대괄호 [ ] = 목록
    {"label": "블라인드", "value": "blind"},
    {"label": "스크린",  "value": "screen"},
    {"label": "셔터",   "value": "shutter"}
  ]
}

이 형태는 드롭다운 메뉴의 선택지 목록을 저장할 때 사용합니다.


6. 멀티테넌시란? — 여러 회사가 하나의 시스템을 쓰는 구조

6.1 개념

┌──────────────────────────────────────────────┐
│           SAM 시스템 (하나의 프로그램)          │
│                                              │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│   │  A회사    │  │  B회사    │  │  C회사    │  │
│   │ tenant=1 │  │ tenant=2 │  │ tenant=3 │  │
│   │          │  │          │  │          │  │
│   │ 블라인드  │  │ 스크린   │  │ 셔터     │  │
│   │ 제조     │  │ 제조     │  │ 제조     │  │
│   └──────────┘  └──────────┘  └──────────┘  │
│                                              │
│   같은 테이블을 쓰지만,                       │
│   tenant_id로 데이터가 완전히 분리됨            │
└──────────────────────────────────────────────┘

6.2 tenant_id = 회사 식별 번호

모든 테이블의 모든 행에 tenant_id(회사 번호)가 붙어 있습니다.

"주문" 테이블

  id │ tenant_id │ 주문번호  │ 금액   │ options
  ───────────────────────────────────────────────────────────
  1  │     1     │ ORD-001  │ 50만  │ {"절곡각도": 45}           ← A회사 데이터
  2  │     1     │ ORD-002  │ 30만  │ {"절곡각도": 90}           ← A회사 데이터
  3  │     2     │ ORD-001  │ 80만  │ {"메시밀도": 18}           ← B회사 데이터
  4  │     3     │ ORD-001  │ 40만  │ {"날개간격": 25}           ← C회사 데이터

A회사가 로그인하면 → 시스템이 자동으로 tenant_id = 1인 데이터만 보여줌 B회사가 로그인하면 → 시스템이 자동으로 tenant_id = 2인 데이터만 보여줌

A회사는 B회사의 데이터를 절대 볼 수 없습니다. 시스템이 자동으로 차단합니다.

6.3 options + tenant_id = 강력한 조합

이 두 가지가 합쳐지면:

같은 테이블, 같은 컬럼 구조인데
  ✅ 회사마다 다른 데이터 (tenant_id로 분리)
  ✅ 회사마다 다른 속성 (options로 유연하게)
  ✅ 시스템 수정 없이 확장 가능

7. SAM 테이블의 표준 구조

SAM에서 새 테이블을 만들면 항상 이 구조를 따릅니다.

┌─────────────────────────────────────────────────────────────────┐
│  SAM 표준 테이블 구조                                            │
│                                                                 │
│  ① 식별자                                                       │
│     id             — 자동 생성 번호 (1, 2, 3...)                 │
│     tenant_id      — 어느 회사의 데이터인지                       │
│                                                                 │
│  ② 핵심 정보 (검색/정렬/연결에 쓰는 것만)                         │
│     code           — 코드 (중복 불가)                             │
│     status         — 상태 (검색용)                                │
│     is_active      — 사용 여부                                   │
│     sort_order     — 표시 순서                                   │
│     (+ FK 컬럼들)  — 다른 테이블 연결                             │
│                                                                 │
│  ③ 메모칸                                                       │
│     options        — 나머지 전부 (JSON)                           │
│                                                                 │
│  ④ 감사 기록 (자동)                                              │
│     created_by     — 누가 만들었나                                │
│     updated_by     — 누가 수정했나                                │
│     deleted_by     — 누가 삭제했나                                │
│     created_at     — 언제 만들었나                                │
│     updated_at     — 언제 수정했나                                │
│     deleted_at     — 언제 삭제했나 (휴지통 개념)                   │
└─────────────────────────────────────────────────────────────────┘

영역별 설명

영역 역할 비유
① 식별자 "이 데이터가 누구 것인지" 구분 우편물의 받는 사람 + 주소
② 핵심 정보 검색, 정렬, 집계에 꼭 필요한 정보 엑셀의 고정 열
③ options 회사마다 다른 부가 정보 엑셀의 "비고" 칸 (자유 서식)
④ 감사 기록 언제 누가 뭘 했는지 자동 추적 CCTV 기록

8. 실제 SAM에서 options를 쓰는 테이블들 (22개)

현재 SAM에서 options 메모칸을 사용하는 주요 테이블입니다.

테이블 한글명 options에 저장하는 정보 예시
orders 주문 배송지, 수신자, 연락처, 담당자
quotes 견적 견적 요약, 비용 항목, 가격 조정
receivings 입고 제조사, 검사 결과, 검사일
work_orders 작업지시 절곡 정보 (bending_info)
work_order_items 작업지시 항목 작업 결과, 양품/불량 수량, LOT번호
processes 공정 작업일지 필요 여부, 검사 필요 여부
order_nodes 주문 노드 위치, 구역, 층, 실 (트리 구조)
products 제품 동적 옵션 (라벨, 값, 단위)
items 품목 품목별 동적 속성
materials 자재 자재 추가 속성
menus 메뉴 섹션, 메뉴 타입, 필요 권한
users 사용자 개인 설정/환경설정
tenants 회사(테넌트) 회사 규모, 업종
document_template_section_fields 문서 양식 필드 선택지 목록, API 경로
item_fields 품목 필드 정의 필드별 세부 설정

9. 자주 묻는 질문 (FAQ)

Q1. options에 넣으면 검색이 안 되나요?

아닙니다. MySQL 8.0은 JSON 내부도 검색할 수 있습니다.

일반 컬럼 검색:  "상태가 '완료'인 주문 찾아줘"         → 매우 빠름
options 검색:   "제조사가 '삼성'인 입고 찾아줘"        → 가능하지만 조금 느림

다만, 매일 수천 번 검색하는 정보라면 일반 컬럼으로 승격하는 것이 맞습니다. 가끔 검색하는 정보라면 options로 충분합니다.

Q2. options에 아무 정보나 마음대로 넣을 수 있나요?

기술적으로는 가능하지만, 개발팀 내부에서 어떤 키를 쓸지 미리 약속합니다.

✅ 약속된 키:  {"manufacturer": "삼성", "inspection_status": "합격"}
❌ 멋대로:    {"asdf": 123, "temp_data": "???"}

코드에서 상수로 정의하여 일관성을 유지합니다.

Q3. 전통적 방식보다 뭐가 좋은 건가요?

비교 항목 전통적 방식 (열 추가) SAM 방식 (options JSON)
새 정보 추가 시 시스템 수정 필요 코드만 변경
다른 회사에 영향 있음 (전체 구조 변경) 없음
빈칸(null) 낭비 많음 없음
검색 속도 빠름 조금 느림 (충분히 실용적)
유연성 낮음 높음
시스템 중단 위험 있음 (대형 테이블 수정 시) 없음

Q4. 그럼 모든 정보를 options에 넣으면 되지 않나요?

아닙니다. 핵심 정보는 반드시 일반 컬럼으로 만들어야 합니다.

❌ 나쁜 예: 모든 것을 options에

  id │ tenant_id │ options
  ──────────────────────────────────────────────────────────────
  1  │     1     │ {"주문번호":"ORD-001", "금액":500000, "상태":"완료", ...}

  → 주문번호 검색 느림, 금액 합계 계산 불가, 중복 방지 불가
✅ 좋은 예: 핵심은 컬럼, 부가는 options

  id │ tenant_id │ order_number │ amount │ status │ options
  ──────────────────────────────────────────────────────────────
  1  │     1     │ ORD-001      │ 500000 │ 완료   │ {"배송지":"서울..."}

  → 검색 빠름, 합계 가능, 중복 방지 가능, 부가 정보도 유연

Q5. options 데이터는 화면에서 어떻게 보이나요?

사용자 화면에서는 options 안에 있는지, 일반 컬럼인지 구분할 수 없습니다. 프로그램이 자동으로 꺼내서 보여줍니다.

화면에 보이는 모습:

  ┌─────────────────────────────────┐
  │ 입고 상세 정보                   │
  │                                 │
  │ 품목:      SUS304 스틸           │  ← 일반 컬럼
  │ 수량:      100개                 │  ← 일반 컬럼
  │ 입고일:    2026-03-01            │  ← 일반 컬럼
  │ 제조사:    삼성전자               │  ← options에서 꺼냄
  │ 검사결과:  합격                   │  ← options에서 꺼냄
  │ 검사일:    2026-03-01            │  ← options에서 꺼냄
  └─────────────────────────────────┘

  사용자는 어디에 저장되어 있는지 알 필요 없음!

10. 한 장 요약

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   SAM 테이블 설계 = "핵심만 컬럼, 나머진 메모칸(options)"         │
│                                                                 │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │  tenant_id    → 어느 회사 것인지 (자동 격리)              │  │
│   │  핵심 컬럼들   → 검색/정렬/연결/집계에 쓰는 필수 정보       │  │
│   │  options      → 나머지 전부 (회사마다 다른 부가 정보)       │  │
│   │  감사 컬럼들   → 누가/언제 만들고/수정하고/삭제했는지        │  │
│   └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   이렇게 하면:                                                   │
│     ✅ 회사 추가해도 테이블 구조 안 바꿈                          │
│     ✅ 새 정보 추가해도 시스템 수정 최소화                         │
│     ✅ 회사마다 다른 정보를 유연하게 저장                          │
│     ✅ 데이터 보안 (회사 간 완전 분리)                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

관련 문서

문서 설명 대상
options-column-policy.md 개발자용 상세 정책 (코드 규칙, 마이그레이션 패턴) 개발자
database/README.md DB 스키마 전체 현황 (220개 모델) 개발자
PROJECT_DEVELOPMENT_POLICY.md 개발 공통 정책 (테이블 생성 절차) 개발자
system/overview.md SAM 시스템 전체 아키텍처 전체

최종 업데이트: 2026-03-02