-- 02155 Promo Schema Fix -- -- 修复历史环境中 02154 未正确执行(或部分 GORM AutoMigrate 推断)导致的 -- promo 系统列类型 / 索引偏差。完全幂等:可重复执行。 -- -- 覆盖偏差: -- 1) subscribe_promo.quantity 实际 int/NULL -> BIGINT NOT NULL DEFAULT 1 -- 2) subscribe_promo 唯一索引 实际 (subscribe_id, promo_rule_id) -> (subscribe_id, quantity, promo_rule_id) -- 3) order.promo_rule_id 实际 int/NULL -> BIGINT UNSIGNED NOT NULL DEFAULT 0 -- 4) order.promo_discount 实际 varchar(255)/NULL -> BIGINT NOT NULL DEFAULT 0 -- -- 设计原则: -- - 所有 ALTER 前先做 NULL/空串兜底,避免 NOT NULL 转换失败。 -- - 类型已经正确的环境(02154 正常跑过)不会被改动,所有 IF 判断都基于 -- INFORMATION_SCHEMA 当前真实状态。 -- - 索引差异处理 4 个分支:仅当索引确实是错的旧形态时才替换,已经是新形态则不动。 -- ============================================================================ -- 1) subscribe_promo.quantity -- ============================================================================ -- 1.1 列不存在则补建(极端历史环境兜底) SET @col_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND COLUMN_NAME = 'quantity' ); SET @sql = IF( @col_exists = 0, 'ALTER TABLE `subscribe_promo` ADD COLUMN `quantity` BIGINT NOT NULL DEFAULT 1 COMMENT ''购买数量'' AFTER `subscribe_id`', 'SELECT ''subscribe_promo.quantity exists, skip ADD''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 1.2 NULL 兜底为 1(旧 AutoMigrate 推断列允许 NULL,必须先回填再 NOT NULL) UPDATE `subscribe_promo` SET `quantity` = 1 WHERE `quantity` IS NULL; -- 1.3 类型 / 可空 / 默认值修正:只在与目标定义不一致时改 SET @col_def_wrong = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND COLUMN_NAME = 'quantity' AND ( LOWER(DATA_TYPE) <> 'bigint' OR IS_NULLABLE = 'YES' OR COLUMN_DEFAULT IS NULL OR COLUMN_DEFAULT <> '1' ) ); SET @sql = IF( @col_def_wrong = 1, 'ALTER TABLE `subscribe_promo` MODIFY COLUMN `quantity` BIGINT NOT NULL DEFAULT 1 COMMENT ''购买数量''', 'SELECT ''subscribe_promo.quantity already matches target definition''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ============================================================================ -- 2) subscribe_promo 唯一索引:旧形态 -> (subscribe_id, quantity, promo_rule_id) -- ============================================================================ -- 2.1 删除已知的所有旧形态唯一索引(如果存在) SET @idx_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND INDEX_NAME = 'idx_subscribe_rule' ); SET @sql = IF( @idx_exists = 1, 'ALTER TABLE `subscribe_promo` DROP INDEX `idx_subscribe_rule`', 'SELECT ''subscribe_promo.idx_subscribe_rule absent''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @idx_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND INDEX_NAME = 'uk_subscribe_rule' ); SET @sql = IF( @idx_exists = 1, 'ALTER TABLE `subscribe_promo` DROP INDEX `uk_subscribe_rule`', 'SELECT ''subscribe_promo.uk_subscribe_rule absent''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @idx_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND INDEX_NAME = 'uk_subscribe_qty_rule' ); SET @sql = IF( @idx_exists = 1, 'ALTER TABLE `subscribe_promo` DROP INDEX `uk_subscribe_qty_rule`', 'SELECT ''subscribe_promo.uk_subscribe_qty_rule absent''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 2.2 新建目标唯一索引(缺失时才建) SET @idx_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'subscribe_promo' AND INDEX_NAME = 'uk_subscribe_quantity_rule' ); SET @sql = IF( @idx_exists = 0, 'ALTER TABLE `subscribe_promo` ADD UNIQUE KEY `uk_subscribe_quantity_rule` (`subscribe_id`, `quantity`, `promo_rule_id`)', 'SELECT ''subscribe_promo.uk_subscribe_quantity_rule exists''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ============================================================================ -- 3) order.promo_rule_id -> BIGINT UNSIGNED NOT NULL DEFAULT 0 -- ============================================================================ SET @col_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'promo_rule_id' ); SET @sql = IF( @col_exists = 0, 'ALTER TABLE `order` ADD COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''促销规则ID, 0=未使用促销'' AFTER `discount`', 'SELECT ''order.promo_rule_id exists, skip ADD''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; UPDATE `order` SET `promo_rule_id` = 0 WHERE `promo_rule_id` IS NULL; SET @col_def_wrong = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'promo_rule_id' AND ( LOWER(DATA_TYPE) <> 'bigint' OR INSTR(LOWER(COLUMN_TYPE), 'unsigned') = 0 OR IS_NULLABLE = 'YES' OR COLUMN_DEFAULT IS NULL OR COLUMN_DEFAULT <> '0' ) ); SET @sql = IF( @col_def_wrong = 1, 'ALTER TABLE `order` MODIFY COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''促销规则ID, 0=未使用促销''', 'SELECT ''order.promo_rule_id already matches target definition''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ============================================================================ -- 4) order.promo_discount -> BIGINT NOT NULL DEFAULT 0 -- 历史 AutoMigrate 推断为 varchar(255)/NULL,金额字段错存为字符串。 -- 必须先把空串/NULL 兜底为 '0',再 MODIFY,否则 MySQL 转 BIGINT 会写 0 -- (这里我们仍兜底显式化,避免触发 strict mode 报错)。 -- ============================================================================ SET @col_exists = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'promo_discount' ); SET @sql = IF( @col_exists = 0, 'ALTER TABLE `order` ADD COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT ''促销优惠金额(分)'' AFTER `promo_rule_id`', 'SELECT ''order.promo_discount exists, skip ADD''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 当且仅当当前是字符串型时做兜底(避免对已经是 BIGINT 的环境跑无谓 UPDATE) SET @col_is_string = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'promo_discount' AND LOWER(DATA_TYPE) IN ('varchar', 'char', 'text') ); SET @sql = IF( @col_is_string = 1, 'UPDATE `order` SET `promo_discount` = ''0'' WHERE `promo_discount` IS NULL OR `promo_discount` = ''''', 'SELECT ''order.promo_discount not string type, skip backfill''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @col_def_wrong = ( SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'promo_discount' AND ( LOWER(DATA_TYPE) <> 'bigint' OR IS_NULLABLE = 'YES' OR COLUMN_DEFAULT IS NULL OR COLUMN_DEFAULT <> '0' ) ); SET @sql = IF( @col_def_wrong = 1, 'ALTER TABLE `order` MODIFY COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT ''促销优惠金额(分)''', 'SELECT ''order.promo_discount already matches target definition''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;