docs: [guides] 테이블 설계 가이드 비전문가용 문서 추가

- options JSON 컬럼 패턴을 엑셀 비유로 쉽게 설명
- 멀티테넌시(tenant_id) 개념 해설
- 실제 SAM 테이블 예시 (주문, 입고, 공정)
- FAQ 5개, 판단 흐름도, 한 장 요약 포함
- INDEX.md에 문서 등록
This commit is contained in:
김보곤
2026-03-02 17:15:25 +09:00
parent a3c910d91b
commit 8cb15cf3c4
2 changed files with 487 additions and 0 deletions

View File

@@ -165,6 +165,7 @@ docs/
| [auto-login-guide.md](guides/auto-login-guide.md) | MNG→DEV 자동 로그인 | 자동 로그인 구현 시 |
| [erp-api-list.md](guides/erp-api-list.md) | ERP API 목록 (List vs Detail 구분) | 프론트 API 연동 시 |
| [erp-api-detail.md](guides/erp-api-detail.md) | ERP API 상세 스펙 | 프론트 API 연동 시 |
| [table-design-guide.md](guides/table-design-guide.md) | 테이블 설계 가이드 (비전문가용, options JSON 패턴) | 테이블 구조 이해 시 |
| [item-master-guide.md](guides/item-master-guide.md) | 품목기준관리 페이지-섹션-필드 구조 | 품목 UI 구현 시 |
| [item-master-items-api.md](guides/item-master-items-api.md) | ItemMaster & Items API 문서 | 품목 API 연동 시 |

View File

@@ -0,0 +1,486 @@
# 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