# column_labels 테이블 제거 영향 분석

## 개요

현재 시스템은 컬럼 메타데이터를 **두 개의 테이블**에서 관리하고 있습니다:
- `column_labels`: 레거시 테이블 (회사코드 없음, 공통 데이터)
- `table_type_columns`: 새 테이블 (회사코드 있음, 멀티테넌시 지원)

이 문서는 `column_labels` 테이블을 제거하고 `table_type_columns`로 통합할 때의 영향을 분석합니다.

---

## 1. 두 테이블 스키마 비교

### column_labels (레거시)

| 컬럼 | 타입 | 설명 |
|------|------|------|
| id | INTEGER | PK |
| table_name | VARCHAR | 테이블명 |
| column_name | VARCHAR | 컬럼명 |
| column_label | VARCHAR | 한글 라벨 |
| web_type | VARCHAR | 레거시 (사용 안 함) |
| input_type | VARCHAR | 입력 타입 |
| detail_settings | TEXT | 상세 설정 (JSON) |
| description | TEXT | 설명 |
| display_order | INTEGER | 표시 순서 |
| is_visible | BOOLEAN | 표시 여부 |
| code_category | VARCHAR | 코드 카테고리 |
| code_value | VARCHAR | 코드 값 |
| reference_table | VARCHAR | 참조 테이블 |
| reference_column | VARCHAR | 참조 컬럼 |
| display_column | VARCHAR | 표시 컬럼 |
| created_date | TIMESTAMP | 생성일 |
| updated_date | TIMESTAMP | 수정일 |

**특징**: `company_code` 없음 → 멀티테넌시 불가

### table_type_columns (신규)

| 컬럼 | 타입 | 설명 |
|------|------|------|
| id | INTEGER | PK |
| table_name | VARCHAR | 테이블명 |
| column_name | VARCHAR | 컬럼명 |
| input_type | VARCHAR | 입력 타입 |
| detail_settings | TEXT | 상세 설정 (JSON) |
| is_nullable | VARCHAR | NULL 허용 |
| display_order | INTEGER | 표시 순서 |
| company_code | VARCHAR | **회사 코드** |
| created_date | TIMESTAMP | 생성일 |
| updated_date | TIMESTAMP | 수정일 |

**특징**: `company_code` 있음 → 멀티테넌시 지원

### 누락된 컬럼 (table_type_columns에 추가 필요)

| 컬럼 | 용도 |
|------|------|
| column_label | 한글 라벨 |
| description | 설명 |
| is_visible | 표시 여부 |
| code_category | 코드 카테고리 |
| code_value | 코드 값 |
| reference_table | 참조 테이블 (엔티티) |
| reference_column | 참조 컬럼 |
| display_column | 표시 컬럼 |

---

## 2. 영향 받는 파일 목록

### 백엔드 파일 (16개, 87회 참조)

| 파일 | 참조 횟수 | 영향도 | 용도 |
|------|----------|--------|------|
| `tableManagementService.ts` | 21회 | 🔴 매우 높음 | 컬럼 조회/저장/업데이트 핵심 로직 |
| `screenGroupController.ts` | 13회 | 🟡 중간 | 화면 그룹/메뉴 동기화 시 라벨 조회 |
| `masterDetailExcelService.ts` | 7회 | 🟡 중간 | 엑셀 다운로드 시 엔티티 관계 조회 |
| `ddlExecutionService.ts` | 7회 | 🟡 중간 | 테이블 생성/삭제 시 메타데이터 등록 |
| `tableManagementController.ts` | 7회 | 🟡 중간 | API 엔드포인트 |
| `screenManagementService.ts` | 5회 | 🔴 높음 | 화면에서 컬럼 라벨 조회 |
| `entityJoinService.ts` | 4회 | 🔴 높음 | 엔티티 조인 관계 감지 |
| `entityReferenceController.ts` | 4회 | 🟡 중간 | 엔티티 참조 데이터 조회 |
| `adminController.ts` | 3회 | 🟢 낮음 | 엑셀 업로드 컬럼 매핑 |
| `dataService.ts` | 3회 | 🟢 낮음 | 컬럼 라벨 조회 |
| `flowController.ts` | 3회 | 🟢 낮음 | 플로우 컬럼 라벨 조회 |
| `categoryTreeService.ts` | 1회 | 🟢 낮음 | 카테고리 라벨 조회 |
| `tableManagementRoutes.ts` | 1회 | 🟢 낮음 | 라우트 주석 |
| `multiConnectionQueryService.ts` | 1회 | 🟢 낮음 | 멀티 연결 쿼리 |
| `migrate-input-type-to-web-type.ts` | 6회 | 🟢 낮음 | 마이그레이션 스크립트 |
| `types/ddl.ts` | 1회 | 🟢 낮음 | 타입 정의 |

### 프론트엔드 파일 (15개, 20회 참조)

| 파일 | 참조 횟수 | 영향도 | 용도 |
|------|----------|--------|------|
| `V2Repeater.tsx` | 3회 | 🟢 낮음 | 타입 주석 |
| `ScreenDesigner.tsx` | 2회 | 🟢 낮음 | 타입 주석 |
| `ButtonConfigPanel.tsx` | 2회 | 🟢 낮음 | 타입 주석 |
| `ScreenRelationFlow.tsx` | 2회 | 🟢 낮음 | 타입 주석 |
| `buttonActions.ts` | 1회 | 🟢 낮음 | 주석 |
| `webTypeMapping.ts` | 1회 | 🟢 낮음 | 주석 |
| `screenGroup.ts` | 1회 | 🟢 낮음 | API 타입 |
| `tableManagement.ts` | 1회 | 🟢 낮음 | API 타입 |
| `TableSettingModal.tsx` | 1회 | 🟢 낮음 | 주석 |
| `tableSchema.ts` | 1회 | 🟢 낮음 | 타입 |
| `types/ddl.ts` | 1회 | 🟢 낮음 | 타입 정의 |
| `ControlConditionStep.tsx` | 1회 | 🟢 낮음 | 주석 |
| `ActionConditionBuilder.tsx` | 1회 | 🟢 낮음 | 주석 |
| `WebTypeInput.tsx` | 1회 | 🟢 낮음 | 주석 |
| `types/multiConnection.ts` | 1회 | 🟢 낮음 | 타입 |

---

## 3. 주요 사용 패턴 분석

### 패턴 1: 컬럼 조회 (가장 많음)

```sql
-- 현재 방식: column_labels + table_type_columns 조인
SELECT 
  c.column_name,
  COALESCE(cl.column_label, c.column_name) as "displayName",
  COALESCE(ttc.input_type, cl.input_type, 'text') as "inputType",
  COALESCE(ttc.detail_settings, cl.detail_settings) as "detailSettings",
  cl.reference_table as "referenceTable"  -- ❌ 문제: ttc의 detailSettings 무시
FROM information_schema.columns c
LEFT JOIN column_labels cl ON c.table_name = cl.table_name AND c.column_name = cl.column_name
LEFT JOIN table_type_columns ttc ON c.table_name = ttc.table_name 
  AND c.column_name = ttc.column_name 
  AND ttc.company_code = $company_code
```

**문제점**: `referenceTable`이 `column_labels`에서만 조회됨 → 회사별 엔티티 설정 무시

### 패턴 2: 컬럼 저장 (이중 저장)

```typescript
// 현재: 두 테이블에 모두 저장
await query(`INSERT INTO column_labels (...) VALUES (...) ON CONFLICT ... DO UPDATE ...`);
await this.updateColumnInputType(...); // table_type_columns에도 저장
```

**문제점**: 데이터 불일치 가능성, 유지보수 어려움

### 패턴 3: 엔티티 관계 조회

```sql
SELECT column_name, reference_table, reference_column
FROM column_labels
WHERE table_name = $1 AND input_type = 'entity'
```

**문제점**: 회사별 엔티티 설정 무시 (column_labels에 company_code 없음)

---

## 4. 마이그레이션 계획

### Phase 1: 스키마 확장 (table_type_columns)

```sql
-- 마이그레이션 파일: 044_extend_table_type_columns.sql

-- 1. 누락된 컬럼 추가
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS column_label VARCHAR(200);
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS description TEXT;
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS is_visible BOOLEAN DEFAULT true;
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS code_category VARCHAR(100);
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS code_value VARCHAR(100);
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS reference_table VARCHAR(100);
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS reference_column VARCHAR(100);
ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS display_column VARCHAR(100);

-- 2. 인덱스 추가
CREATE INDEX IF NOT EXISTS idx_ttc_reference_table ON table_type_columns(reference_table);
CREATE INDEX IF NOT EXISTS idx_ttc_input_type ON table_type_columns(input_type);
```

### Phase 2: 데이터 마이그레이션

```sql
-- column_labels 데이터를 table_type_columns로 이관 (company_code = '*' 로 공통 데이터)
INSERT INTO table_type_columns (
  table_name, column_name, input_type, detail_settings, 
  column_label, description, is_visible, code_category, code_value,
  reference_table, reference_column, display_column, display_order,
  company_code, created_date, updated_date
)
SELECT 
  table_name, column_name, 
  COALESCE(input_type, 'text'), 
  detail_settings,
  column_label, 
  description, 
  COALESCE(is_visible, true), 
  code_category, 
  code_value,
  reference_table, 
  reference_column, 
  display_column, 
  display_order,
  '*',  -- 공통 데이터 (회사별 설정 없으면 폴백)
  COALESCE(created_date, NOW()), 
  COALESCE(updated_date, NOW())
FROM column_labels
ON CONFLICT (table_name, column_name, company_code) 
DO UPDATE SET
  column_label = COALESCE(EXCLUDED.column_label, table_type_columns.column_label),
  description = COALESCE(EXCLUDED.description, table_type_columns.description),
  reference_table = COALESCE(EXCLUDED.reference_table, table_type_columns.reference_table),
  reference_column = COALESCE(EXCLUDED.reference_column, table_type_columns.reference_column),
  display_column = COALESCE(EXCLUDED.display_column, table_type_columns.display_column),
  updated_date = NOW();
```

### Phase 3: 코드 수정

#### 3.1 조회 쿼리 변경 패턴

```sql
-- 변경 전
SELECT ... FROM column_labels WHERE table_name = $1

-- 변경 후 (회사코드 폴백 포함)
SELECT * FROM (
  SELECT *, 
    ROW_NUMBER() OVER (
      PARTITION BY table_name, column_name 
      ORDER BY CASE WHEN company_code = $company_code THEN 0 ELSE 1 END
    ) as rn
  FROM table_type_columns
  WHERE table_name = $1 
    AND company_code IN ($company_code, '*')
) ranked
WHERE rn = 1
```

#### 3.2 저장 쿼리 변경 패턴

```sql
-- 변경 전: 두 테이블에 저장
INSERT INTO column_labels (...) ...;
INSERT INTO table_type_columns (...) ...;

-- 변경 후: 하나의 테이블만
INSERT INTO table_type_columns (
  table_name, column_name, input_type, detail_settings,
  column_label, description, is_visible, code_category, code_value,
  reference_table, reference_column, display_column, display_order,
  company_code, created_date, updated_date
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW(), NOW())
ON CONFLICT (table_name, column_name, company_code) 
DO UPDATE SET ...;
```

### Phase 4: 레거시 코드 정리

1. `column_labels` 관련 INSERT/UPDATE 제거
2. `column_labels` LEFT JOIN을 `table_type_columns`로 변경
3. 프론트엔드 주석/타입 업데이트

### Phase 5: 테이블 삭제 (최종)

```sql
-- 모든 코드 마이그레이션 완료 후
DROP TABLE IF EXISTS column_labels;
```

---

## 5. 수정해야 할 파일 상세

### 🔴 우선순위 1: 핵심 서비스 (3개)

#### tableManagementService.ts (21개 쿼리)

| 함수 | 라인 | 수정 내용 |
|------|------|----------|
| `checkCodeTypeColumn` | 30-36 | column_labels → table_type_columns |
| `getColumnList` | 215-218, 257-258 | JOIN 변경 + referenceTable 추출 |
| `updateColumnSettings` | 460-494 | column_labels INSERT 제거 |
| `getColumnLabels` | 670-673 | table_type_columns로 변경 |
| `setColumnInputType` | 735-740 | column_labels INSERT 제거 |
| `getFileColumns` | 1288 | table_type_columns로 변경 |
| `getColumnMetaInfo` | 1956 | table_type_columns로 변경 |
| `findEntityRelation` | 3579-3590 | table_type_columns로 변경 |
| `upsertColumnLabel` | 3723 | table_type_columns로 변경 |
| `getColumnInputTypes` | 4129 | 이미 table_type_columns 사용 중 ✅ |
| `detectEntityRelation` | 4810, 4838 | table_type_columns로 변경 |

#### screenManagementService.ts (5개 쿼리)

| 함수 | 라인 | 수정 내용 |
|------|------|----------|
| `getTableColumns` | 1279 | table_type_columns로 변경 |
| `getTableColumns` | 1334 | 라벨 추가 로직 수정 |
| `getColumnInfo` | 2083 | table_type_columns로 변경 |
| `saveColumnSettings` | 2104 | table_type_columns로 변경 |

#### entityJoinService.ts (4개 쿼리)

| 함수 | 라인 | 수정 내용 |
|------|------|----------|
| `detectEntityColumns` | 36 | table_type_columns로 변경 |
| `getTableColumns` | 755 | table_type_columns로 변경 |

### 🟡 우선순위 2: 보조 서비스 (5개)

| 파일 | 수정 쿼리 수 | 수정 내용 |
|------|-------------|----------|
| `screenGroupController.ts` | 13개 | 라벨 조회, FK 조회 쿼리 변경 |
| `masterDetailExcelService.ts` | 7개 | 엔티티 관계 조회 변경 |
| `ddlExecutionService.ts` | 7개 | 테이블 생성 시 메타데이터 등록 변경 |
| `entityReferenceController.ts` | 4개 | 참조 데이터 조회 변경 |
| `adminController.ts` | 3개 | 스키마 조회 변경 |

### 🟢 우선순위 3: 기타 (8개)

| 파일 | 수정 내용 |
|------|----------|
| `dataService.ts` | 라벨 조회 변경 |
| `flowController.ts` | 라벨 조회 변경 |
| `categoryTreeService.ts` | JOIN 변경 |
| `tableManagementRoutes.ts` | 주석 수정 |
| `multiConnectionQueryService.ts` | 주석/타입 수정 |
| `migrate-input-type-to-web-type.ts` | 마이그레이션 스크립트 (이미 실행됨, 삭제 가능) |
| `types/ddl.ts` | 타입 정의 수정 |
| 프론트엔드 15개 파일 | 주석/타입 수정 |

---

## 6. 예상 작업 시간

| 단계 | 작업 | 예상 시간 |
|------|------|----------|
| Phase 1 | 스키마 확장 (마이그레이션 SQL) | 30분 |
| Phase 2 | 데이터 마이그레이션 (SQL) | 30분 |
| Phase 3.1 | tableManagementService.ts 수정 | 2시간 |
| Phase 3.2 | screenManagementService.ts 수정 | 1시간 |
| Phase 3.3 | entityJoinService.ts 수정 | 30분 |
| Phase 3.4 | 보조 서비스 5개 수정 | 2시간 |
| Phase 3.5 | 기타 파일 8개 수정 | 1시간 |
| Phase 4 | 테스트 및 검증 | 2시간 |
| Phase 5 | column_labels 삭제 | 10분 |
| **합계** | | **약 10시간** |

---

## 7. 리스크 및 주의사항

### 높은 리스크

1. **데이터 불일치**: 마이그레이션 중 column_labels와 table_type_columns 데이터 충돌 가능
2. **회사코드 폴백 로직 복잡성**: 모든 조회에 `company_code IN ($code, '*')` + 우선순위 필요
3. **기존 운영 데이터 손실**: 마이그레이션 실수 시 column_labels 데이터 유실 가능

### 완화 방안

1. **단계적 마이그레이션**: column_labels는 당분간 유지, 조회만 table_type_columns 우선으로 변경
2. **폴백 헬퍼 함수**: 회사코드 폴백 로직을 공통 함수로 추출
3. **백업 필수**: 마이그레이션 전 column_labels 전체 백업

---

## 8. 결론

### 현재 문제점

1. **이중 관리**: 같은 데이터가 두 테이블에 저장됨
2. **멀티테넌시 불완전**: `referenceTable` 등이 `column_labels`에서만 조회되어 회사별 설정 무시
3. **유지보수 어려움**: 변경 시 두 곳 모두 수정 필요

### 권장 방향

**장기적으로 `table_type_columns`로 통합 권장**

하지만 작업량이 상당하므로:

1. **단기 (즉시)**: 조회 시 `detailSettings`에서 `referenceTable` 우선 추출하도록 수정
2. **중기 (1-2주)**: `table_type_columns` 스키마 확장 + 데이터 마이그레이션
3. **장기 (한 달)**: 모든 코드 수정 후 `column_labels` 제거

---

## 참고 자료

- `table_type_columns` 관련 마이그레이션: `db/migrations/030_create_table_type_columns.sql`
- 테이블 타입 관리 UI: `frontend/app/(main)/admin/systemMng/tableMngList/page.tsx`
- 컬럼 조회 핵심 로직: `backend-node/src/services/tableManagementService.ts:getColumnList()`

