将列添加到mysql表(如果不存在)


109

我的研究和实验还没有找到答案,所以我希望能有所帮助。

我正在修改一个应用程序的安装文件,该应用程序在以前的版本中没有我要立即添加的列。我不想手动添加列,而是要在安装文件中并且仅当表中不存在新列时才添加。

该表如下创建:

CREATE TABLE IF NOT EXISTS `#__comm_subscribers` (
      `subscriber_id` int(11) NOT NULL auto_increment,
      `user_id` int(11) NOT NULL default '0',
      `subscriber_name` varchar(64) NOT NULL default '',
      `subscriber_surname` varchar(64) NOT NULL default '',
      `subscriber_email` varchar(64) NOT NULL default '',
      `confirmed` tinyint(1) NOT NULL default '0',
      `subscribe_date` datetime NOT NULL default '0000-00-00 00:00:00',
      PRIMARY KEY  (`subscriber_id`),
      UNIQUE KEY `subscriber_email` (`subscriber_email`)
    ) ENGINE=MyISAM CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' COMMENT='Subscribers for Comm are stored here.';

如果我在create table语句下面添加以下内容,那么我不确定如果该列已经存在(可能已填充)会发生什么情况:

ALTER TABLE `#__comm_subscribers` ADD `subscriber_surname`;
ALTER TABLE `#__comm_subscribers` MODIFY `subscriber_surname` varchar(64) NOT NULL default '';

因此,我尝试了在某处找到的以下内容。这似乎不起作用,但是我不能完全确定我是否正确使用了它。

/*delimiter '//'
CREATE PROCEDURE addcol() BEGIN
IF NOT EXISTS(
SELECT * FROM information_schema.COLUMNS
WHERE COLUMN_NAME='subscriber_surname' AND TABLE_NAME='#__comm_subscribers'
)
THEN
    ALTER TABLE `#__comm_subscribers`
    ADD COLUMN `subscriber_surname` varchar(64) NOT NULL default '';
END IF;
END;
//
delimiter ';'
CALL addcol();
DROP PROCEDURE addcol;*/

有人有这样做的好方法吗?


2
更改information_schema.COLUMNS(即存储过程的功能)是恕我直言的方法。它的哪一部分“似乎不起作用”?
2009年

Answers:


49

请注意,INFORMATION_SCHEMA5.0之前的MySQL不支持。5.0之前的版本也不支持存储过程,因此如果您需要支持MySQL 4.1,则此解决方案不是很好。

使用数据库迁移的框架使用的一种解决方案是在数据库中记录该模式的修订号。只是一个具有单列和单行的表,其中的整数表示当前有效的修订版本。更新架构时,请增加编号。

另一种解决方案是只尝试ALTER TABLE ADD COLUMN命令。如果该列已经存在,则应该抛出错误。

ERROR 1060 (42S21): Duplicate column name 'newcolumnname'

捕获错误并在升级脚本中忽略它。


1
好的,这真的很粗糙,但是有人不得不说。如果仅从命令行运行SQL脚本,则可以给mysql --force切换,这意味着即使有错误也可以继续进行。然后,就去吧。您只想确保没有任何语句,如果先前发生了故障,您就不会成功。
大卫,

85

这是一个有效的解决方案(刚刚在Solaris上的MySQL 5.0上试用过):

DELIMITER $$

DROP PROCEDURE IF EXISTS upgrade_database_1_0_to_2_0 $$
CREATE PROCEDURE upgrade_database_1_0_to_2_0()
BEGIN

-- rename a table safely
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()
        AND TABLE_NAME='my_old_table_name') ) THEN
    RENAME TABLE 
        my_old_table_name TO my_new_table_name,
END IF;

-- add a column safely
IF NOT EXISTS( (SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()
        AND COLUMN_NAME='my_additional_column' AND TABLE_NAME='my_table_name') ) THEN
    ALTER TABLE my_table_name ADD my_additional_column varchar(2048) NOT NULL DEFAULT '';
END IF;

END $$

CALL upgrade_database_1_0_to_2_0() $$

DELIMITER ;

乍一看,它看起来可能比它应该的要复杂,但是我们必须在这里处理以下问题:

  • IF 语句仅在存储过程中有效,而不能直接运行(例如在mysql客户端中)
  • 更优雅,简洁的方法SHOW COLUMNS不适用于存储过程,因此必须使用INFORMATION_SCHEMA
  • 在MySQL中,用于分隔语句的语法很奇怪,因此必须重新定义分隔符才能创建存储过程。不要忘记将定界符切换回去!
  • INFORMATION_SCHEMA对于所有数据库都是全局的,请不要忘记对进行过滤TABLE_SCHEMA=DATABASE()DATABASE()返回当前所选数据库的名称。

1
希望我能因解释导致这种方法的问题而获得奖励积分。谢谢。
布莱恩·佩蒂

48

如果您使用的是MariaDB,则无需使用存储过程。只需使用,例如:

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name tinyint(1) DEFAULT 0;

看这里


8
辉煌!使用MariaDB的另一个原因。
Andrew Ensley

+1我一直在和Maria一起工作,尝试所有这些步骤之前,所有这些步骤都没有奏效,直到我继续执行此步骤为止,这挽救了我的生命。
Thielicious

有趣的。我从来没有认真思考MariaDB的和这篇文章能给一些关于它的主意seravo.fi/2015/...
walv

这个解决方案很棒,但是确实需要MariaDB 10.0.2。如果您想使用这种优雅的解决方案,但仍停留在较旧的版本上,则可以直接使用。
jblopez

ALTER TABLE table_name如果存在则更改列column_wrong_name column_name tinyint(1)默认值0; -作品也不错!
Damonsson

34

大多数答案都涉及如何在存储过程中安全地添加列,我需要在不使用存储proc的情况下将列安全地添加到表中,并且发现MySQL不允许在SPIF Exists()外部使用。我将发布我的解决方案,以帮助可能遇到相同情况的人。

SELECT count(*)
INTO @exist
FROM information_schema.columns 
WHERE table_schema = database()
and COLUMN_NAME = 'original_data'
AND table_name = 'mytable';

set @query = IF(@exist <= 0, 'alter table intent add column mycolumn4 varchar(2048) NULL after mycolumn3', 
'select \'Column Exists\' status');

prepare stmt from @query;

EXECUTE stmt;

1
注意当通过MySQL工作台GUI工作时,我必须在SELECT语句中添加“ LIMIT 1”。
Al Dass 2014年

23

做到这一点的另一种方法是用忽略错误declare continue handler

delimiter ;;
create procedure foo ()
begin
    declare continue handler for 1060 begin end;
    alter table atable add subscriber_surname varchar(64);
end;;
call foo();;

我认为这种方式比使用exists子查询更整洁。尤其是如果您要添加很多列,并且您想多次运行该脚本。

有关继续处理程序的更多信息,请参见http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html


爱它!永远不会想到这一点。我肯定会切换到这种做事方式。
约翰尼·考夫曼

错误1060(42S21):重复的列名'newcolumnname'–
Jake

这真是太好了!
ATOzTOA

6

我正在使用MySQL 5.5.19。

我喜欢可以无错误运行和重新运行的脚本,尤其是在警告似乎持续存在的地方,稍后在运行无错误/警告的脚本时会再次出现。就添加字段而言,我为自己编写了一个减少输入的过程:

-- add fields to template table to support ignoring extra data 
-- at the top/bottom of every page
CALL addFieldIfNotExists ('template', 'firstPageHeaderEndY', 'INT NOT NULL DEFAULT 0');
CALL addFieldIfNotExists ('template', 'pageHeaderEndY', 'INT NOT NULL DEFAULT 0');
CALL addFieldIfNotExists ('template', 'pageFooterBeginY', 'INT NOT NULL DEFAULT 792');

创建addFieldIfNotExists过程的代码如下:

DELIMITER $$

DROP PROCEDURE IF EXISTS addFieldIfNotExists 
$$

DROP FUNCTION IF EXISTS isFieldExisting 
$$

CREATE FUNCTION isFieldExisting (table_name_IN VARCHAR(100), field_name_IN VARCHAR(100)) 
RETURNS INT
RETURN (
    SELECT COUNT(COLUMN_NAME) 
    FROM INFORMATION_SCHEMA.columns 
    WHERE TABLE_SCHEMA = DATABASE() 
    AND TABLE_NAME = table_name_IN 
    AND COLUMN_NAME = field_name_IN
)
$$

CREATE PROCEDURE addFieldIfNotExists (
    IN table_name_IN VARCHAR(100)
    , IN field_name_IN VARCHAR(100)
    , IN field_definition_IN VARCHAR(100)
)
BEGIN

    -- http://javajon.blogspot.com/2012/10/mysql-alter-table-add-column-if-not.html

    SET @isFieldThere = isFieldExisting(table_name_IN, field_name_IN);
    IF (@isFieldThere = 0) THEN

        SET @ddl = CONCAT('ALTER TABLE ', table_name_IN);
        SET @ddl = CONCAT(@ddl, ' ', 'ADD COLUMN') ;
        SET @ddl = CONCAT(@ddl, ' ', field_name_IN);
        SET @ddl = CONCAT(@ddl, ' ', field_definition_IN);

        PREPARE stmt FROM @ddl;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;

    END IF;

END;
$$

我没有编写用于安全地修改列的过程,但是我认为可以很容易地修改上述过程。


5

我已经采用了OP的proc,并使其可重用且与架构无关。显然,它仍然需要MySQL 5。

DROP PROCEDURE IF EXISTS AddCol;

DELIMITER //

CREATE PROCEDURE AddCol(
    IN param_schema VARCHAR(100),
    IN param_table_name VARCHAR(100),
    IN param_column VARCHAR(100),
    IN param_column_details VARCHAR(100)
) 
BEGIN
    IF NOT EXISTS(
    SELECT NULL FROM information_schema.COLUMNS
    WHERE COLUMN_NAME=param_column AND TABLE_NAME=param_table_name AND table_schema = param_schema
    )
    THEN
        set @paramTable = param_table_name ;
        set @ParamColumn = param_column ;
        set @ParamSchema = param_schema;
        set @ParamColumnDetails = param_column_details;
        /* Create the full statement to execute */
        set @StatementToExecute = concat('ALTER TABLE `',@ParamSchema,'`.`',@paramTable,'` ADD COLUMN `',@ParamColumn,'` ',@ParamColumnDetails);
        /* Prepare and execute the statement that was built */
        prepare DynamicStatement from @StatementToExecute ;
        execute DynamicStatement ;
        /* Cleanup the prepared statement */
        deallocate prepare DynamicStatement ;

    END IF;
END //

DELIMITER ;

这对我来说很好。我唯一要做的更改是删除对concat的调用中的反引号(`)。另外,可以通过删除变量@ paramTable,@ ParamColumn,@ ParamSchema和@ParamColumnDetails来简化代码,而直接使用参数即可。
hrabinowitz 2013年

1

刚刚尝试了存储过程脚本。似乎问题是'分隔符周围的标记。在MySQL的文档显示,分隔符不需要单引号。

所以你要:

delimiter //

代替:

delimiter '//'

为我工作:)


@安迪你完全错了。这篇评论指出了OP犯错的地方。如果没有单引号,OP已经在那里。
RichardTheKiwi 2012年

1

如果您正在脚本中运行此程序,则需要在其后添加以下行以使其可重新运行,否则您将获得过程已存在的错误。

drop procedure foo;

1

在PHP> PDO中添加列的最佳方法:

$Add = $dbh->prepare("ALTER TABLE `YourCurrentTable` ADD `YourNewColumnName` INT NOT NULL");
$Add->execute();

注意:表中的列不可重复,这意味着我们不需要检查一列的存在,但是为了解决问题,我们检查上面的代码:

例如,如果它工作警报1,如果不为0,则表示该列存在!:)


1

检查PDO中是否存在列(100%)

{
    if(isset($_POST['Add']))
    {
        $ColumnExist = $dbh->prepare("SELECT * FROM ColumnChecker where column_name='$insert_column_name' LIMIT 1");
        $ColumnExist ->execute();
        $ColumnName = $ColumnExist->fetch(2);
        $Display_Column_Name = $ColumnName['column_name'];

        if($Display_Column_Name == $insert_column_name)
        {
            echo "$Display_Column_Name already exist";
        } //*****************************
        else 
        {
            $InsertColumn = $dbh->prepare("insert into ColumnChecker ( column_name ) values ('$insert_column_name')");
            $InsertColumn->execute();

            if($InsertColumn)
            {
                $Add = $dbh->prepare("ALTER TABLE `$Table` ADD `$insert_column_name` $insert_column_type($insert_column_Length) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ");
                $Add->execute();

                if($Add)
                {
                    echo 'Table has been updated';  
                }
                else 
                {
                    echo 'Sorry! Try again...'; 
                }
            }   
        }
    }
}#Add Column into Table :)

1

来自Jake https://stackoverflow.com/a/6476091/6751901的过程非常简单,是添加新列的很好的解决方案,但增加了一行:

DROP PROCEDURE IF EXISTS foo;;

您可以稍后在此处添加新列,下次也可以使用:

delimiter ;;
DROP PROCEDURE IF EXISTS foo;;
create procedure foo ()
begin
    declare continue handler for 1060 begin end;
    alter table atable add subscriber_surname varchar(64);
    alter table atable add subscriber_address varchar(254);
end;;
call foo();;

0
$smpt = $pdo->prepare("SHOW fields FROM __TABLE__NAME__");
$smpt->execute();
$res = $smpt->fetchAll(PDO::FETCH_ASSOC);
//print_r($res);

然后在$ res by cycle中查找您的列Smth的键,如下所示:

    if($field['Field'] == '_my_col_'){
       return true;
    }
+

**Below code is good for checking column existing in the WordPress tables:**
public static function is_table_col_exists($table, $col)
    {
        global $wpdb;
        $fields = $wpdb->get_results("SHOW fields FROM {$table}", ARRAY_A);
        foreach ($fields as $field)
        {
            if ($field['Field'] == $col)
            {
                return TRUE;
            }
        }

        return FALSE;
    }

1
可以提高效率 SHOW fields FROM __TABLE__NAME__ where field='_my_col_'; ,然后检查结果集是否为非空
Eugen Mayer

0

以下是MySQL中的存储过程,以便在数据库表中不存在列的情况下在不同数据库的不同表中添加列,具有以下优点

  • 可以一次添加多个列,以更改不同数据库中的多个表
  • 运行三个mysql命令,即DROP,CREATE,CALL For Procedure
  • 数据库名称应根据USE进行更改,否则多个数据可能会出现问题

DROP PROCEDURE  IF EXISTS `AlterTables`;
DELIMITER $$
CREATE PROCEDURE `AlterTables`() 
BEGIN
    DECLARE table1_column1_count INT;
    DECLARE table2_column2_count INT;
    SET table1_column1_count = (  SELECT COUNT(*) 
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE   TABLE_SCHEMA = 'DATABASE_NAME' AND
			    TABLE_NAME = 'TABLE_NAME1' AND 
                            COLUMN_NAME = 'TABLE_NAME1_COLUMN1');
    SET table2_column2_count = (  SELECT COUNT(*) 
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE   TABLE_SCHEMA = 'DATABASE_NAME' AND
			    TABLE_NAME = 'TABLE_NAME2' AND 
                            COLUMN_NAME = 'TABLE_NAME2_COLUMN2');
    IF table1_column1_count = 0 THEN
        ALTER TABLE `TABLE_NAME1`ADD `TABLE_NAME1_COLUMN1` text COLLATE 'latin1_swedish_ci' NULL AFTER `TABLE_NAME1_COLUMN3`,COMMENT='COMMENT HERE';
    END IF;
    IF table2_column2_count = 0 THEN
        ALTER TABLE `TABLE_NAME2` ADD `TABLE_NAME2_COLUMN2` VARCHAR( 100 ) NULL DEFAULT NULL COMMENT 'COMMENT HERE';
    END IF;
END $$
DELIMITER ;
call AlterTables();


-1
ALTER TABLE `subscriber_surname` ADD  IF NOT EXISTS  `#__comm_subscribers`.`subscriber_surname`;

ALTER TABLE `#__comm_subscribers` MODIFY `subscriber_surname` varchar(64) NOT NULL default '';

4
如果您可以在解决方案中添加一些说明,那将非常好,因为我们(用户)可以理解该解决方案的优点,尽管有其他优点。这是对本问题以及将来答案的一种改进。
路易斯·克鲁兹(
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.