# 마이그레이션 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 **심각도**: 🔴 높음 **영향 범위**: 전체 동적 테이블 시스템