adicionar coluna à tabela mysql se ela não existir

109

Minhas pesquisas e experimentos ainda não produziram uma resposta, então espero alguma ajuda.

Estou modificando o arquivo de instalação de um aplicativo que nas versões anteriores não tinha uma coluna que desejo adicionar agora. Não quero adicionar a coluna manualmente, mas sim no arquivo de instalação e somente se a nova coluna ainda não existir na tabela.

A tabela é criada da seguinte maneira:

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.';

Se eu adicionar o seguinte, abaixo da instrução create table, não tenho certeza do que acontecerá se a coluna já existir (e talvez esteja preenchida):

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

Então, tentei o seguinte, que encontrei em algum lugar. Isso não parece funcionar, mas não tenho certeza se o usei corretamente.

/*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;*/

Alguém tem uma boa maneira de fazer isso?

E Wierda
fonte
2
Alterar information_schema.COLUMNS, ou seja, o que o procedimento armazenado faz, é o caminho a seguir IMHO. Que parte "parece não funcionar"?
rodion

Respostas:

49

Observe que INFORMATION_SCHEMAnão é compatível com MySQL anterior a 5.0. Nem os procedimentos armazenados são suportados antes do 5.0, portanto, se você precisa do MySQL 4.1, esta solução não é boa.

Uma solução usada por estruturas que usam migrações de banco de dados é registrar em seu banco de dados um número de revisão para o esquema. Apenas uma tabela com uma única coluna e uma única linha, com um número inteiro indicando a revisão em vigor. Ao atualizar o esquema, aumente o número.

Outra solução seria apenas tentar o ALTER TABLE ADD COLUMNcomando. Deve gerar um erro se a coluna já existir.

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

Identifique o erro e desconsidere-o em seu script de atualização.

Bill Karwin
fonte
1
OK, isso é realmente grosseiro, mas alguém tem que dizer. se você está simplesmente executando um script SQL a partir da linha de comando, pode dar ao mysql a --forceopção, o que significa continuar mesmo se houver um erro. então, vá em frente. você só quer ter certeza de que não há instruções que NÃO queira que tenham sucesso se algo anterior tiver falhado.
David
85

Aqui está uma solução de trabalho (recém-testada com MySQL 5.0 no Solaris):

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 ;

À primeira vista, provavelmente parece mais complicado do que deveria, mas temos que lidar com os seguintes problemas aqui:

  • IF instruções só funcionam em procedimentos armazenados, não quando executados diretamente, por exemplo, no cliente mysql
  • mais elegante e conciso SHOW COLUMNSnão funciona em stored procedure, então tem que usar INFORMATION_SCHEMA
  • a sintaxe para delimitar instruções é estranha no MySQL, então você deve redefinir o delimitador para poder criar procedimentos armazenados. Não se esqueça de mudar o delimitador de volta!
  • INFORMATION_SCHEMA é global para todos os bancos de dados, não se esqueça de filtrar TABLE_SCHEMA=DATABASE(). DATABASE()retorna o nome do banco de dados atualmente selecionado.
geekQ
fonte
1
Gostaria de poder conceder pontos de bônus por explicar os problemas envolvidos que levaram a essa abordagem. Obrigado.
Bryan Petty
48

Se você estiver no MariaDB, não há necessidade de usar procedimentos armazenados. Basta usar, por exemplo:

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

Veja aqui

giuseppe
fonte
8
Brilhante! Mais uma razão para usar MariaDB.
Andrew Ensley
+1 Tenho trabalhado com Maria o tempo todo e experimentando todos esses passos acima, nenhum deles funcionou até que eu comecei este aqui, isso salvou minha vida.
Thielicious
Interessante .. Nunca é difícil falar sobre MariaDB e este artigo pode dar uma ideia sobre isso seravo.fi/2015/…
walv
Esta solução é ótima, mas requer MariaDB 10.0.2. Apenas alerta para quem deseja usar esta solução elegante, mas está preso em uma versão mais antiga.
jblopez
ALTER TABLE nome_da_tabela ALTERAR COLUNA SE EXISTS nome_da_coluna_name_da_coluna tinyint (1) DEFAULT 0; -funciona também é bom!
Damonsson
34

A maioria das respostas abordam como adicionar uma coluna com segurança em um procedimento armazenado. Eu tive a necessidade de adicionar uma coluna a uma tabela com segurança sem usar um proc armazenado e descobri que o MySQL não permite o uso de IF Exists()fora de um SP . Vou postar minha solução para que possa ajudar alguém na mesma situação.

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;
rahvin_t
fonte
1
Nota Eu tive que adicionar um "LIMIT 1" à instrução SELECT ao trabalhar através da GUI do ambiente de trabalho MySQL.
Al Dass de
23

Outra maneira de fazer isso seria ignorar o erro com um 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();;

Eu acho que é mais limpo dessa forma do que com uma existssubconsulta. Especialmente se você tiver muitas colunas a adicionar e quiser executar o script várias vezes.

mais informações sobre continuar manipuladores podem ser encontradas em http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html

Jake
fonte
Adoro! Jamais teria pensado nisso. Vou mudar para essa maneira de fazer as coisas com certeza.
Johnny Kauffman
ERROR 1060 (42S21): Nome da coluna duplicado 'newcolumnname'
Jake
Isso é realmente bacana!
ATOzTOA de
6

Estou usando o MySQL 5.5.19.

Gosto de ter scripts que você pode executar e executar novamente sem erros, especialmente onde os avisos parecem demorar, aparecendo novamente mais tarde enquanto estou executando scripts que não têm erros / avisos. No que diz respeito à adição de campos, escrevi um procedimento para torná-lo um pouco menos digitado:

-- 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');

O código para criar o procedimento addFieldIfNotExists é o seguinte:

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;
$$

Não escrevi um procedimento para modificar uma coluna com segurança, mas acho que o procedimento acima pode ser facilmente modificado para isso.

Jonathan
fonte
5

Peguei o sproc do OP e tornei-o reutilizável e independente de esquema. Obviamente, ainda requer 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 ;
Thomas Paine
fonte
Isto funciona bem para mim. A única mudança que tive que fazer foi remover as aspas (`) na chamada para concat. Além disso, pode simplificar o código removendo as variáveis ​​@paramTable, @ParamColumn, @ParamSchema e @ParamColumnDetails e apenas usar os parâmetros diretamente.
hrabinowitz
1

Tentei apenas o script de procedimento armazenado. Parece que o problema são as 'marcas em torno dos delimitadores. O MySQL Docs mostra que os caracteres delimitadores não precisam de aspas simples.

Então você quer:

delimiter //

Ao invés de:

delimiter '//'

Funciona para mim :)

Menino que rugiu
fonte
@Andy Você perdeu completamente o ponto. Este comentário aponta onde o OP cometeu seu erro. O OP já estava lá, senão pelas aspas simples.
RichardTheKiwi
1

Se estiver executando isso em um script, você desejará adicionar a seguinte linha posteriormente para torná-lo reexecutável, caso contrário, obterá um erro de procedimento já existe.

drop procedure foo;
tapete crocker
fonte
1

A melhor maneira de adicionar a coluna em PHP> PDO:

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

Nota: a coluna da tabela não é repetível, o que significa que não precisamos verificar a existência de uma coluna, mas para resolver o problema verificamos o código acima:

por exemplo se funciona o alerta 1, senão 0, o que significa que a coluna existe! :)

Maher
fonte
1

Verifique se a coluna existe ou não no 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 :)
Maher
fonte
1

O procedimento de Jake https://stackoverflow.com/a/6476091/6751901 é muito simples e uma boa solução para adicionar novas colunas, mas com uma linha adicional:

DROP PROCEDURE IF EXISTS foo;;

você pode adicionar novas colunas mais tarde lá, e funcionará da próxima vez também:

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();;
simpel
fonte
0
$smpt = $pdo->prepare("SHOW fields FROM __TABLE__NAME__");
$smpt->execute();
$res = $smpt->fetchAll(PDO::FETCH_ASSOC);
//print_r($res);

Então, em $ res por ciclo, procure a chave de sua coluna Smth assim:

    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;
    }
realmag777
fonte
1
isso pode ser feito um pouco mais eficiente SHOW fields FROM __TABLE__NAME__ where field='_my_col_'; e, em seguida, verifique se o conjunto de resultados não está vazio
Eugen Mayer
0

Abaixo está o procedimento armazenado no MySQL para adicionar coluna (s) em diferentes tabelas em diferentes bancos de dados (s) se a coluna não existir em uma (s) tabela (s) de banco de dados (s) com as seguintes vantagens

  • várias colunas podem ser adicionadas ao mesmo tempo para alterar várias tabelas em bancos de dados diferentes
  • três comandos mysql executados, ou seja, DROP, CREATE, CALL para procedimento
  • O nome da base de dados deve ser alterado de acordo com o USO, caso contrário, o problema pode ocorrer para vários dados

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();

Abdul Rehman
fonte
-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 '';
Phạm Trần Phú Quốc
fonte
4
Seria ótimo se você pudesse adicionar alguma descrição à sua solução se nós (os usuários) pudéssemos entender as vantagens desta solução apesar das outras. Esta é uma melhoria para esta e para as respostas futuras.
Luís Cruz