# 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](../standards/options-column-policy.md) | 개발자용 상세 정책 (코드 규칙, 마이그레이션 패턴) | 개발자 |
| [database/README.md](../system/database/README.md) | DB 스키마 전체 현황 (220개 모델) | 개발자 |
| [PROJECT_DEVELOPMENT_POLICY.md](PROJECT_DEVELOPMENT_POLICY.md) | 개발 공통 정책 (테이블 생성 절차) | 개발자 |
| [system/overview.md](../system/overview.md) | SAM 시스템 전체 아키텍처 | 전체 |

---

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