-- AI 캐릭터 크리에이터 기능 최소 연결 운영 DB 반영 SQL -- MySQL 기준. 운영 반영 전 백업과 트랜잭션/락 영향을 점검한다. -- 1. member.member_kind 추가 SET @member_kind_column_exists := ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'member' AND column_name = 'member_kind' ); SET @add_member_kind_sql := IF( @member_kind_column_exists = 0, 'ALTER TABLE member ADD COLUMN member_kind VARCHAR(30) NOT NULL DEFAULT ''HUMAN'' COMMENT ''Member 주체 종류: HUMAN, AI_CHARACTER'' AFTER role', 'SELECT ''member.member_kind already exists'' AS message' ); PREPARE add_member_kind_stmt FROM @add_member_kind_sql; EXECUTE add_member_kind_stmt; DEALLOCATE PREPARE add_member_kind_stmt; -- 2. chat_character.creator_member_id nullable 컬럼 추가 SET @creator_member_column_exists := ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'chat_character' AND column_name = 'creator_member_id' ); SET @add_creator_member_sql := IF( @creator_member_column_exists = 0, 'ALTER TABLE chat_character ADD COLUMN creator_member_id BIGINT NULL COMMENT ''크리에이터 기능 주체 Member ID''', 'SELECT ''chat_character.creator_member_id already exists'' AS message' ); PREPARE add_creator_member_stmt FROM @add_creator_member_sql; EXECUTE add_creator_member_stmt; DEALLOCATE PREPARE add_creator_member_stmt; -- 3. 기존 chat_character별 AI 캐릭터용 Member 생성 및 매핑 DROP TEMPORARY TABLE IF EXISTS tmp_chat_character_creator_member; CREATE TEMPORARY TABLE tmp_chat_character_creator_member ( chat_character_id BIGINT NOT NULL PRIMARY KEY, creator_member_id BIGINT NULL ) COMMENT 'chat_character와 backfill member.id 임시 매핑'; INSERT INTO tmp_chat_character_creator_member (chat_character_id) SELECT c.id FROM chat_character c WHERE c.creator_member_id IS NULL; DROP PROCEDURE IF EXISTS backfill_chat_character_creator_member; DELIMITER // CREATE PROCEDURE backfill_chat_character_creator_member() BEGIN DECLARE done BOOLEAN DEFAULT FALSE; DECLARE v_chat_character_id BIGINT; DECLARE v_name VARCHAR(255); DECLARE v_description TEXT; DECLARE v_image_path VARCHAR(255); DECLARE character_cursor CURSOR FOR SELECT c.id, c.name, c.description, c.image_path FROM chat_character c INNER JOIN tmp_chat_character_creator_member m ON m.chat_character_id = c.id WHERE m.creator_member_id IS NULL ORDER BY c.id; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN character_cursor; read_loop: LOOP FETCH character_cursor INTO v_chat_character_id, v_name, v_description, v_image_path; IF done THEN LEAVE read_loop; END IF; INSERT INTO member ( email, password, nickname, profile_image, provider, gender, role, member_kind, is_visible_donation_rank, donation_ranking_period, is_active, container, introduce, instagram_url, fancimm_url, x_url, youtube_url, website_url, blog_url, pg_charge_can, pg_reward_can, google_charge_can, google_reward_can, apple_charge_can, apple_reward_can, created_at, updated_at ) VALUES ( NULL, '', v_name, v_image_path, 'EMAIL', 'NONE', 'CREATOR', 'AI_CHARACTER', TRUE, 'CUMULATIVE', TRUE, 'web', COALESCE(v_description, ''), '', '', '', '', '', '', 0, 0, 0, 0, 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ); UPDATE tmp_chat_character_creator_member SET creator_member_id = LAST_INSERT_ID() WHERE chat_character_id = v_chat_character_id; END LOOP; CLOSE character_cursor; END // DELIMITER ; CALL backfill_chat_character_creator_member(); DROP PROCEDURE IF EXISTS backfill_chat_character_creator_member; UPDATE chat_character c INNER JOIN tmp_chat_character_creator_member m ON m.chat_character_id = c.id SET c.creator_member_id = m.creator_member_id WHERE c.creator_member_id IS NULL AND m.creator_member_id IS NOT NULL; -- 4. unique index 추가 SET @creator_member_unique_exists := ( SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'chat_character' AND index_name = 'uk_chat_character_creator_member' ); SET @add_creator_member_unique_sql := IF( @creator_member_unique_exists = 0, 'ALTER TABLE chat_character ADD UNIQUE INDEX uk_chat_character_creator_member (creator_member_id)', 'SELECT ''uk_chat_character_creator_member already exists'' AS message' ); PREPARE add_creator_member_unique_stmt FROM @add_creator_member_unique_sql; EXECUTE add_creator_member_unique_stmt; DEALLOCATE PREPARE add_creator_member_unique_stmt; -- 5. foreign key 추가 SET @creator_member_fk_exists := ( SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema = DATABASE() AND table_name = 'chat_character' AND constraint_name = 'fk_chat_character_creator_member' AND constraint_type = 'FOREIGN KEY' ); SET @add_creator_member_fk_sql := IF( @creator_member_fk_exists = 0, 'ALTER TABLE chat_character ADD CONSTRAINT fk_chat_character_creator_member FOREIGN KEY (creator_member_id) REFERENCES member (id)', 'SELECT ''fk_chat_character_creator_member already exists'' AS message' ); PREPARE add_creator_member_fk_stmt FROM @add_creator_member_fk_sql; EXECUTE add_creator_member_fk_stmt; DEALLOCATE PREPARE add_creator_member_fk_stmt; -- 6. 운영 반영 전 필수 검증. 두 결과 모두 0이어야 한다. SELECT COUNT(*) AS invalid_ai_character_member_count FROM member WHERE member_kind = 'AI_CHARACTER' AND role <> 'CREATOR'; SELECT COUNT(*) AS missing_creator_member_count FROM chat_character WHERE creator_member_id IS NULL; -- 7. 검증 완료 후 creator_member_id NOT NULL 전환 SET @missing_creator_member_count := ( SELECT COUNT(*) FROM chat_character WHERE creator_member_id IS NULL ); SET @creator_member_nullable := ( SELECT is_nullable FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'chat_character' AND column_name = 'creator_member_id' ); SET @modify_creator_member_not_null_sql := IF( @missing_creator_member_count = 0 AND @creator_member_nullable = 'YES', 'ALTER TABLE chat_character MODIFY COLUMN creator_member_id BIGINT NOT NULL COMMENT ''크리에이터 기능 주체 Member ID''', 'SELECT ''chat_character.creator_member_id not modified; verify missing_creator_member_count is 0 and column is nullable'' AS message' ); PREPARE modify_creator_member_not_null_stmt FROM @modify_creator_member_not_null_sql; EXECUTE modify_creator_member_not_null_stmt; DEALLOCATE PREPARE modify_creator_member_not_null_stmt; DROP TEMPORARY TABLE IF EXISTS tmp_chat_character_creator_member;