# 마이그레이션 044: table_type_columns에 company_code 추가

## 목적

회사별로 독립적인 컬럼 타입 정의를 가능하게 합니다.

### 해결하는 문제

**현재 문제**:
- 회사 A: `item_info.material` → `category` (드롭다운)
- 회사 B: `item_info.material` → `text` (자유 입력)
- ❌ 현재는 둘 중 하나만 선택 가능!

**수정 후**:
- ✅ 각 회사가 독립적으로 컬럼 타입을 설정 가능

---

## 영향받는 테이블

- `table_type_columns`
  - `company_code VARCHAR(20)` 컬럼 추가
  - 기존 데이터를 모든 회사에 복제
  - 복합 유니크 인덱스 생성

---

## 실행 방법

### Docker 환경 (권장)

```bash
docker exec -i erp-node-db-1 psql -U postgres -d ilshin < db/migrations/044_add_company_code_to_table_type_columns.sql
```

### 로컬 PostgreSQL

```bash
psql -U postgres -d ilshin -f db/migrations/044_add_company_code_to_table_type_columns.sql
```

### pgAdmin / DBeaver

1. `db/migrations/044_add_company_code_to_table_type_columns.sql` 파일 열기
2. 전체 내용 복사
3. SQL 쿼리 창에 붙여넣기
4. 실행 (F5 또는 Execute)

---

## 마이그레이션 단계

1. **company_code 컬럼 추가** (nullable)
2. **기존 데이터 백업** (임시 테이블)
3. **데이터 복제** (기존 데이터를 모든 회사에 복제)
4. **기존 데이터 삭제** (company_code가 NULL인 것)
5. **NOT NULL 제약조건 추가**
6. **복합 유니크 인덱스 생성** (table_name, column_name, company_code)
7. **단순 인덱스 생성** (company_code)
8. **외래키 제약조건 추가** (company_info 참조)

---

## 검증 방법

### 1. 컬럼 추가 확인

```sql
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'table_type_columns' AND column_name = 'company_code';

-- 예상 결과:
-- column_name  | data_type         | is_nullable
-- company_code | character varying | NO
```

### 2. 인덱스 생성 확인

```sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'table_type_columns'
ORDER BY indexname;

-- 예상 결과:
-- idx_table_column_type_company
-- idx_table_type_columns_company
```

### 3. 데이터 마이그레이션 확인

```sql
-- 회사별 데이터 개수
SELECT company_code, COUNT(*) as column_count
FROM table_type_columns
GROUP BY company_code
ORDER BY company_code;

-- NULL 확인 (없어야 정상)
SELECT COUNT(*) as null_count
FROM table_type_columns
WHERE company_code IS NULL;

-- 예상 결과: 0
```

### 4. 회사별 독립성 확인

```sql
-- 같은 테이블/컬럼이 회사별로 존재하는지 확인
SELECT 
  table_name,
  column_name,
  COUNT(DISTINCT company_code) as company_count,
  STRING_AGG(DISTINCT company_code, ', ') as companies
FROM table_type_columns
GROUP BY table_name, column_name
HAVING COUNT(DISTINCT company_code) > 1
ORDER BY company_count DESC
LIMIT 10;
```

### 5. 외래키 제약조건 확인

```sql
SELECT 
  tc.constraint_name,
  tc.table_name,
  kcu.column_name,
  ccu.table_name AS foreign_table_name,
  ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
  ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
  ON ccu.constraint_name = tc.constraint_name
WHERE tc.table_name = 'table_type_columns'
  AND tc.constraint_type = 'FOREIGN KEY';

-- 예상 결과:
-- fk_table_type_columns_company | table_type_columns | company_code | company_info | company_code
```

---

## 롤백 방법 (문제 발생 시)

```sql
BEGIN;

-- 1. 외래키 제약조건 제거
ALTER TABLE table_type_columns 
DROP CONSTRAINT IF EXISTS fk_table_type_columns_company;

-- 2. 인덱스 제거
DROP INDEX IF EXISTS idx_table_column_type_company;
DROP INDEX IF EXISTS idx_table_type_columns_company;

-- 3. company_code를 nullable로 변경
ALTER TABLE table_type_columns 
ALTER COLUMN company_code DROP NOT NULL;

-- 4. company_code 컬럼 제거
ALTER TABLE table_type_columns 
DROP COLUMN IF EXISTS company_code;

COMMIT;
```

---

## 테스트 시나리오

### 시나리오 1: 회사별 다른 타입 설정

```sql
-- 회사 A: material을 카테고리로 설정
UPDATE table_type_columns
SET input_type = 'category'
WHERE table_name = 'item_info' 
  AND column_name = 'material'
  AND company_code = 'COMPANY_A';

-- 회사 B: material을 텍스트로 설정
UPDATE table_type_columns
SET input_type = 'text'
WHERE table_name = 'item_info' 
  AND column_name = 'material'
  AND company_code = 'COMPANY_B';

-- 확인
SELECT table_name, column_name, input_type, company_code
FROM table_type_columns
WHERE table_name = 'item_info' AND column_name = 'material'
ORDER BY company_code;

-- 예상 결과:
-- item_info | material | category | *
-- item_info | material | text     | COMPANY_7
```

### 시나리오 2: 유니크 제약조건 확인

```sql
-- 같은 회사에서 같은 테이블/컬럼 중복 삽입 시도 (실패해야 정상)
INSERT INTO table_type_columns (table_name, column_name, input_type, company_code)
VALUES ('test_table', 'test_column', 'text', 'COMPANY_A');

-- 다시 시도 (에러 발생해야 함)
INSERT INTO table_type_columns (table_name, column_name, input_type, company_code)
VALUES ('test_table', 'test_column', 'number', 'COMPANY_A');

-- 예상 에러:
-- ERROR: duplicate key value violates unique constraint "idx_table_column_type_company"
```

---

## 주의사항

1. **백업 필수**: 마이그레이션 실행 전 반드시 데이터베이스 백업
2. **데이터 복제**: 기존 데이터가 모든 회사에 복제되므로 데이터 양이 증가
3. **트랜잭션**: 전체 마이그레이션이 하나의 트랜잭션으로 실행됨 (실패 시 자동 롤백)
4. **성능 영향**: 회사 수가 많으면 실행 시간이 길어질 수 있음
5. **코드 수정**: 백엔드 코드도 함께 수정해야 함

---

## 예상 데이터 변화

### Before (기존)

```
id | table_name | column_name | input_type | company_code
---|------------|-------------|------------|-------------
1  | item_info  | material    | text       | NULL
2  | projects   | type        | category   | NULL
```

### After (마이그레이션 후)

```
id | table_name | column_name | input_type | company_code
---|------------|-------------|------------|-------------
1  | item_info  | material    | text       | *
2  | item_info  | material    | text       | COMPANY_7
3  | projects   | type        | category   | *
4  | projects   | type        | category   | COMPANY_7
```

---

## 다음 단계

마이그레이션 완료 후:

1. **백엔드 코드 수정**: `company_code` 파라미터 추가
   - `tableService.ts`
   - `dataService.ts`
   - `tableController.ts`

2. **프론트엔드 코드 수정**: API 호출 시 `company_code` 자동 포함

3. **테스트**: 회사별로 다른 컬럼 타입 설정 확인

---

## 관련 파일

- 마이그레이션 파일: `db/migrations/044_add_company_code_to_table_type_columns.sql`
- 분석 문서: `docs/테이블_컬럼_타입_멀티테넌시_구조적_문제_분석.md`
- 백엔드 서비스: `backend-node/src/services/tableService.ts`

---

**작성일**: 2025-11-06  
**심각도**: 🔴 높음  
**영향 범위**: 전체 동적 테이블 시스템

