ajouter une colonne à la table mysql si elle n'existe pas

109

Mes recherches et mes expériences n'ont pas encore apporté de réponse, j'espère donc avoir de l'aide.

Je modifie le fichier d'installation d'une application qui dans les versions précédentes n'avait pas de colonne que je souhaite ajouter maintenant. Je ne veux pas ajouter la colonne manuellement, mais dans le fichier d'installation et uniquement si la nouvelle colonne n'existe pas déjà dans le tableau.

La table est créée comme suit:

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

Si j'ajoute ce qui suit, sous l'instruction create table, je ne suis pas sûr de ce qui se passe si la colonne existe déjà (et est peut-être remplie):

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

Alors, j'ai essayé ce qui suit que j'ai trouvé quelque part. Cela ne semble pas fonctionner mais je ne suis pas tout à fait sûr de l'avoir utilisé correctement.

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

Quelqu'un a-t-il un bon moyen de le faire?

E Wierda
la source
2
Modifier information_schema.COLUMNS, c'est-à-dire ce que fait la procédure stockée, est la voie à suivre à mon humble avis. Quelle partie "ne semble pas fonctionner"?
rodion

Réponses:

49

Notez que ce INFORMATION_SCHEMAn'est pas pris en charge dans MySQL avant la version 5.0. Les procédures stockées ne sont pas non plus prises en charge avant la version 5.0, donc si vous devez prendre en charge MySQL 4.1, cette solution n'est pas bonne.

Une solution utilisée par les frameworks qui utilisent des migrations de bases de données consiste à enregistrer dans votre base de données un numéro de révision pour le schéma. Juste un tableau avec une seule colonne et une seule ligne, avec un entier indiquant quelle révision est en vigueur. Lorsque vous mettez à jour le schéma, incrémentez le nombre.

Une autre solution serait d' essayer simplement la ALTER TABLE ADD COLUMNcommande. Il devrait générer une erreur si la colonne existe déjà.

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

Attrapez l'erreur et ignorez-la dans votre script de mise à niveau.

Bill Karwin
la source
1
OK, c'est vraiment grossier mais quelqu'un doit le dire. si vous exécutez simplement un script SQL à partir de la ligne de commande, vous pouvez donner le --forcecommutateur à mysql , ce qui signifie continuer même s'il y a une erreur. alors, allez-y. vous voulez juste être sûr qu'il n'y a aucune instruction que vous ne voulez PAS réussir si quelque chose de précédent a échoué.
David
85

Voici une solution de travail (juste essayée avec MySQL 5.0 sur 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 ;

À première vue, cela semble probablement plus compliqué qu'il ne le devrait, mais nous devons traiter les problèmes suivants ici:

  • IF les instructions ne fonctionnent que dans les procédures stockées, pas lorsqu'elles sont exécutées directement, par exemple dans le client mysql
  • plus élégant et concis SHOW COLUMNSne fonctionne pas dans la procédure stockée, il faut donc utiliser INFORMATION_SCHEMA
  • la syntaxe des instructions de délimitation est étrange dans MySQL, vous devez donc redéfinir le délimiteur pour pouvoir créer des procédures stockées. N'oubliez pas de remettre le délimiteur!
  • INFORMATION_SCHEMA est global pour toutes les bases de données, n'oubliez pas de filtrer sur TABLE_SCHEMA=DATABASE(). DATABASE()renvoie le nom de la base de données actuellement sélectionnée.
geekQ
la source
1
J'aimerais pouvoir attribuer des points bonus pour avoir expliqué les problèmes impliqués qui ont conduit à cette approche. Je vous remercie.
Bryan Petty
48

Si vous êtes sur MariaDB, pas besoin d'utiliser des procédures stockées. Utilisez simplement, par exemple:

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

Vois ici

Giuseppe
la source
8
Brillant! Encore une autre raison d'utiliser MariaDB.
Andrew Ensley
+1 J'ai travaillé avec Maria tout le temps et j'ai essayé toutes ces étapes au-dessus aucune d'elles n'a fonctionné jusqu'à ce que j'aie atteint celle-ci, cela m'a sauvé la vie.
Thielicious
Intéressant .. Je n'ai jamais trop
walv
Cette solution est excellente, mais nécessite MariaDB 10.0.2. Attention, tous ceux qui souhaitent utiliser cette solution élégante, mais qui sont bloqués sur une version plus ancienne.
jblopez le
ALTER TABLE nom_table CHANGE COLUMN IF EXISTS nom_colonne erronée nom_colonne tinyint (1) DEFAULT 0; -fonctionne aussi bien!
Damonsson le
35

La plupart des réponses traitent de la façon d'ajouter une colonne en toute sécurité dans une procédure stockée, j'ai eu le besoin d'ajouter une colonne à une table en toute sécurité sans utiliser de proc stocké et j'ai découvert que MySQL ne permettait pas l'utilisation de l' IF Exists()extérieur d'un SP . Je publierai ma solution qui pourrait aider quelqu'un dans la même situation.

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
la source
1
Remarque J'ai dû ajouter un "LIMIT 1" à l'instruction SELECT lorsque je travaillais via l'interface graphique de MySQL Workbench.
Al Dass
23

Une autre façon de procéder serait d'ignorer l'erreur avec un 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();;

Je pense que c'est plus soigné de cette façon qu'avec une existssous-requête. Surtout si vous avez beaucoup de colonnes à ajouter et que vous souhaitez exécuter le script plusieurs fois.

plus d'informations sur les gestionnaires de poursuite peuvent être trouvées sur http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html

Jake
la source
Aimer! Je n'aurais jamais pensé à ça. Je vais certainement passer à cette façon de faire.
Johnny Kauffman
ERREUR 1060 (42S21): nom de colonne en double 'newcolumnname'
Jake
C'est vraiment chouette!
ATOzTOA
6

J'utilise MySQL 5.5.19.

J'aime avoir des scripts que vous pouvez exécuter et réexécuter sans erreur, en particulier là où les avertissements semblent persister, réapparaissant plus tard pendant que j'exécute des scripts sans erreur / avertissement. En ce qui concerne l'ajout de champs, je me suis écrit une procédure pour le rendre un peu moins tapant:

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

Le code pour créer la procédure addFieldIfNotExists est le suivant:

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

Je n'ai pas écrit de procédure pour modifier une colonne en toute sécurité, mais je pense que la procédure ci-dessus pourrait être facilement modifiée pour le faire.

Jonathan
la source
5

J'ai pris le sproc de l'OP et l'ai rendu réutilisable et indépendant du schéma. De toute évidence, cela nécessite toujours 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
la source
Cela fonctionne bien pour moi. Le seul changement que j'ai dû faire était de supprimer les guillemets arrière (`) dans l'appel à concat. En outre, peut simplifier le code en supprimant les variables @paramTable, @ParamColumn, @ParamSchema et @ParamColumnDetails et utilisez simplement les paramètres directement.
hrabinowitz
1

Je viens d'essayer le script de procédure stockée. Il semble que le problème réside dans les 'marques autour des délimiteurs. Les documents MySQL montrent que les caractères séparateurs n'ont pas besoin des guillemets simples.

Alors tu veux:

delimiter //

Au lieu de:

delimiter '//'

Travaille pour moi :)

Garçon qui rugit
la source
@Andy Vous avez complètement manqué le point. Ce commentaire indique où l'OP a commis son erreur. L'OP était déjà là sinon pour les guillemets simples.
RichardTheKiwi
1

Si vous exécutez ceci dans un script, vous voudrez ajouter la ligne suivante par la suite pour le rendre ré-exécutable, sinon vous obtenez une erreur de procédure déjà existante.

drop procedure foo;
mijoteuse mat
la source
1

Le meilleur moyen d'ajouter la colonne en PHP> PDO:

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

Remarque: la colonne du tableau n'est pas répétable, cela signifie que nous n'avons pas besoin de vérifier l'existence d'une colonne, mais pour résoudre le problème, nous vérifions le code ci-dessus:

par exemple si cela fonctionne alerte 1, sinon 0, ce qui signifie que la colonne existe! :)

Maher
la source
1

Vérifier si la colonne existe ou non en 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
la source
1

La procédure de Jake https://stackoverflow.com/a/6476091/6751901 est une solution très simple et bonne pour ajouter de nouvelles colonnes, mais avec une ligne supplémentaire:

DROP PROCEDURE IF EXISTS foo;;

vous pouvez y ajouter de nouvelles colonnes plus tard, et cela fonctionnera également la prochaine fois:

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

Ensuite, dans $ res by cycle, recherchez la clé de votre colonne Smth comme ceci:

    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
la source
1
cela peut être fait un peu plus efficacement SHOW fields FROM __TABLE__NAME__ where field='_my_col_'; , puis vérifier que le jeu de résultats n'est pas vide
Eugen Mayer
0

Vous trouverez ci-dessous la procédure stockée dans MySQL pour ajouter des colonnes dans différentes tables dans différentes bases de données si la colonne n'existe pas dans une ou plusieurs tables de base de données avec les avantages suivants

  • plusieurs colonnes peuvent être ajoutées à la fois pour modifier plusieurs tables dans différentes bases de données
  • trois commandes mysql s'exécutent, c'est-à-dire DROP, CREATE, CALL For Procedure
  • Le nom de la base de données doit être modifié selon l'utilisation, sinon un problème peut survenir pour plusieurs données

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
la source
-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
la source
4
Ce serait formidable si vous pouviez ajouter une description à votre solution, car nous (les utilisateurs) pourrions comprendre les avantages de cette solution malgré les autres. Il s'agit d'une amélioration pour cette réponse et les futures réponses.
Luís Cruz