【问题标题】:Alter table if exists or create if doesn't如果存在则更改表,如果不存在则创建
【发布时间】:2013-05-26 02:03:11
【问题描述】:

我需要运行一个安装程序,它也可以是一个更新程序。 安装程序需要能够最终获得 mysql 数据库的特定方案/结构,无论某些表是否存在、遗漏了几列或不需要更改,因为它们的结构是最新的。

如何优雅地组合ALTERCREATE

我在想肯定有“添加...如果...重复”之类的东西

假设我有表 A。在一个客户端中,该表具有一列 -A1,而另一个客户端具有相同的表,但具有 A1 列和 A2 列。

我希望我的 sql 命令使两个客户端的表 A 都包含三列:A1、A2 和 A3。

同样,我的脚本是一个我转储到 mysql 的 sql 文件。

我该怎么做?谢谢:-)

【问题讨论】:

    标签: mysql alter-table create-table


    【解决方案1】:

    MySQL INFORMATION_SCHEMA 数据库救援:

    -- First check if the table exists
    IF EXISTS(SELECT table_name 
                FROM INFORMATION_SCHEMA.TABLES
               WHERE table_schema = 'db_name'
                 AND table_name LIKE 'wild')
    
    -- If exists, retreive columns information from that table
    THEN
       SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
         FROM INFORMATION_SCHEMA.COLUMNS
        WHERE table_name = 'tbl_name'
          AND table_schema = 'db_name';
    
       -- do some action, i.e. ALTER TABLE if some columns are missing 
       ALTER TABLE ...
    
    -- Table does not exist, create a new table
    ELSE
       CREATE TABLE ....
    
    END IF;
    

    更多信息:

    更新:

    另一个可能更简单的选择是删除现有表并使用新架构重新创建它。为此,您需要:

    1. 创建临时表,即现有表的精确副本
    2. 用旧表中的数据填充临时表
    3. 放下旧桌子
    4. 使用新架构创建新表
    5. 用临时表中的信息填充新表
    6. 删除临时表。

    所以,在 SQL 代码中:

    CREATE TABLE old_table_copy LIKE old_table;
    
    INSERT INTO old_table_copy
    SELECT * FROM old_table;
    
    DROP TABLE old_table;
    
    CREATE TABLE new_table (...new values...);
    
    INSERT INTO new_table ([... column names from old table ...])
    SELECT [...column names from old table ...] 
    FROM old_table_copy;
    
    DROP TABLE old_table_copy;
    

    其实最后一步,“删除临时表。”,你可以跳过一会儿。以防万一,您可能希望对旧表进行某种备份,“以防万一”。

    更多信息:

    【讨论】:

    • 我在哪里运行这样的脚本——我是一个有 mysql 功能的新手。您可以将其作为查询运行吗?
    • 我对您的“安装程序”一无所知,因为您没有提供任何信息,但基本上您有两个选择:(1)您在一个 SQL 查询中执行此操作,使用STORED PROCEDURE,或者 (2) 你使用其他一些外部编程语言并在那里执行 IF / ELSE 条件。
    • If Exists 语句似乎不再起作用?尝试使用 MySQL 8 执行此操作时出现语法错误,是否已在以后的版本中删除?
    【解决方案2】:

    如果你使用的是编码语言,那么:

    SHOW TABLES LIKE 'myTable'
    

    如果它返回一个值调用alter else调用create

    【讨论】:

    • 我不使用编码语言。我正在转储一个 sql 文件。
    • 好的然后创建一个mysql函数来计算SHOW TABLES LIKE 'myTable'的结果,如果0创建其他改变?有很多关于mysql函数的文档。
    【解决方案3】:

    虽然这个话题已经有几年的历史了,但我一直在寻找类似问题的解决方案。关于@GregD 的答案,我找到了一个适合我的解决方案。 SQL 脚本创建表,但是,如果数据库中已有表,它应该应用更改。在此版本中,它仅在有附加列时才有效。修改的(剩余的)列不起作用。

    -- We create a procedure that is dropped if it exists already.
    DROP PROCEDURE IF EXISTS changeFunction;
    -- We have to change the delimiter for the procedure.
    DELIMITER $$
    CREATE PROCEDURE changeFunction()
        BEGIN
    -- Check if table already exists
    IF EXISTS(SELECT table_name 
                FROM INFORMATION_SCHEMA.TABLES
               WHERE table_schema = 'your_database_name'
                 AND table_name LIKE 'your_table_name')
    
    -- It exists, so create a new table and copy the data.
    THEN
        -- Copy the data into a copy of the table.
        CREATE TABLE `your_table_name_copy` LIKE `your_table_name`;
        INSERT INTO `your_table_name_copy` SELECT * FROM `your_table_name`;
        -- Remove old table
        DROP TABLE `your_table_name`;
        -- Create the new table
        CREATE TABLE IF NOT EXISTS `your_table_name` (
            -- Your columns
        );
        -- Copy values, it determines the old column names by itself
        SET @v1 := (SELECT GROUP_CONCAT(COLUMN_NAME)
          FROM INFORMATION_SCHEMA.COLUMNS
          WHERE TABLE_SCHEMA = 'your_database_name' AND TABLE_NAME = 'your_table_name_copy');
        -- Since the columns are detected automatically, we have to execute the 
        -- statement as prepared statement.
        SET @q1 := CONCAT('INSERT INTO `your_table_name` (', @v1, ') ',
                          'SELECT ', @v1, ' FROM `your_table_name_copy`');
        PREPARE stmt FROM @q1;
        EXECUTE stmt;
        -- Remove copy
        DROP TABLE `your_table_name_copy`;
    ELSE
        -- It does not exist, simply create it
        CREATE TABLE IF NOT EXISTS `your_table_name` (
            -- Your columns
        );
    END IF;
    END $$
    -- Reset the delimiter
    DELIMITER ;
    -- Call the function
    CALL changeFunction();
    

    【讨论】:

      【解决方案4】:

      我将给出两个示例来说明如何使用 MySQL 8.0+ 来执行此操作。首先,如果存在某些东西,alter 语句才能工作,它必须位于 存储过程 中。下面的示例显示了如何删除和添加约束(如果存在或不存在)。如果它不存在,它将简单地忽略 if 并创建它。如果它确实存在,它将删除它,然后再次创建它。 这对于调试很有用,因此您可以快速更改约束逻辑并对其进行测试。对于那些不熟悉 DELIMITER 的人来说,它从本质上改变了 ;在这种情况下为 $$。这是必要的,以便过程知道 ; 属于它,而不是完整的 SQL 查询正在结束。

          DROP PROCEDURE IF EXISTS trading.create_constraint;
          DELIMITER $$
          CREATE PROCEDURE trading.create_constraint()
              BEGIN
                  IF EXISTS (SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS WHERE CONSTRAINT_NAME = 'assigned_equity_percent_not_over_100')
                  THEN
                      ALTER TABLE Strategies DROP CONSTRAINT assigned_equity_percent_not_over_100;
                  END IF;
                  ALTER TABLE Strategies ADD CONSTRAINT assigned_equity_percent_not_over_100 CHECK (assigned_equity_percent <= 100 AND assigned_equity_percent>=0);
              END$$
          DELIMITER ;
          CALL create_constraint();
      

      下面的另一个示例是如何使用触发器创建更高级的过程,该触发器将计算所有值的总和,并通过 SIGNAL SQLSTATE '45000' 引发错误来确保在更新或插入时它们不超过 100错误。

      DROP PROCEDURE IF EXISTS trading.check_sum_strategy_assigned_equity;
      DELIMITER $$
      CREATE PROCEDURE trading.check_sum_strategy_assigned_equity(IN new_equity_percentage DECIMAL(5,2),IN old_equity_percentage DECIMAL(5,2),IN updating BOOLEAN)
      BEGIN
          IF (updating=false) THEN
              IF ((SELECT SUM(assigned_equity_percent) total FROM Strategies WHERE strategy_pack_id = strategy_pack_id)+new_equity_percentage)>100
              THEN
                  SIGNAL SQLSTATE '45000' SET message_text = 'Can not INSERT a value of 100 for assigned equity percent with the given strategy_pack_id';
              END IF;
          ELSE
              IF ((SELECT SUM(assigned_equity_percent) total FROM Strategies WHERE strategy_pack_id = strategy_pack_id)-old_equity_percentage+new_equity_percentage)>100
              THEN
                  SIGNAL SQLSTATE '45000' SET message_text = 'Can not UPDATE a value of 100 for assigned equity percent with the given strategy_pack_id';
              END IF;
          END IF;
      END$$
      DELIMITER ;
      DROP TRIGGER IF EXISTS ins_sum_strat_ass_eq;
      CREATE TRIGGER ins_sum_strat_ass_eq
      BEFORE INSERT ON Strategies
      FOR EACH ROW
      CALL trading.check_sum_strategy_assigned_equity(NEW.assigned_equity_percent,0.00,FALSE);
      DROP TRIGGER IF EXISTS up_sum_strat_ass_eq;
      CREATE TRIGGER up_sum_strat_ass_eq
      BEFORE UPDATE ON Strategies
      FOR EACH ROW
      CALL trading.check_sum_strategy_assigned_equity(NEW.assigned_equity_percent,OLD.assigned_equity_percent,TRUE);
      

      需要注意的是,DELIMITER 只能通过 MySQL 客户端工作,而不是通过 API 工作,因此您会收到错误消息,指出它无法识别 DELIMITER。考虑到这一点,您可以运行类似于我在下面的 javascript 中所做的代码。

      //@JA - This function sanitizes custom SQL so that any delimiter code is removed which only works via client or phpmyadmin.
      var sanitize_for_mysql = function(sql) {
          sql = sql.replace(/([$][$])/gm, ";");
          sql = sql.replace(/DELIMITER ;/gm, "");
          return sql;
      };
      
      module.exports = sanitize_for_mysql;
      

      通过这样做,您仍然可以使用像 Sequelize 这样的 ORM 来运行此代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-13
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        • 1970-01-01
        • 2012-06-16
        • 2012-02-04
        相关资源
        最近更新 更多