aggiungi una colonna alla tabella mysql se non esiste


109

La mia ricerca e i miei esperimenti non hanno ancora dato una risposta, quindi spero in qualche aiuto.

Sto modificando il file di installazione di un'applicazione che nelle versioni precedenti non aveva una colonna che voglio aggiungere ora. Non voglio aggiungere la colonna manualmente, ma nel file di installazione e solo se la nuova colonna non esiste già nella tabella.

La tabella viene creata come segue:

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 aggiungo quanto segue, sotto l'istruzione create table, allora non sono sicuro di cosa succede se la colonna esiste già (e forse è popolata):

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

Quindi, ho provato quanto segue che ho trovato da qualche parte. Questo non sembra funzionare ma non sono del tutto sicuro di averlo usato correttamente.

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

Qualcuno ha un buon modo per farlo?


2
Alterare information_schema.COLUMNS, cioè cosa fa la stored procedure, è il modo per andare IMHO. Quale parte di esso "sembra non funzionare"?
rodion

Risposte:


49

Nota che INFORMATION_SCHEMAnon è supportato in MySQL prima della 5.0. Né le stored procedure sono supportate prima della 5.0, quindi se è necessario supportare MySQL 4.1, questa soluzione non è buona.

Una soluzione utilizzata dai framework che utilizzano le migrazioni del database consiste nel registrare nel database un numero di revisione per lo schema. Solo una tabella con una singola colonna e una singola riga, con un numero intero che indica quale revisione è attualmente in vigore. Quando aggiorni lo schema, incrementa il numero.

Un'altra soluzione sarebbe provare semplicemente il ALTER TABLE ADD COLUMNcomando. Dovrebbe generare un errore se la colonna esiste già.

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

Individua l'errore e ignoralo nello script di aggiornamento.


1
OK, questo è davvero rozzo ma qualcuno deve dirlo. se stai semplicemente eseguendo uno script SQL dalla riga di comando, puoi dare a mysql il --forcepassaggio, il che significa che vai avanti anche se c'è un errore. quindi, provaci. vuoi solo essere sicuro che non ci siano affermazioni che NON vuoi avere successo se qualcosa di precedente ha fallito.
David

85

Ecco una soluzione funzionante (appena provata con MySQL 5.0 su 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 ;

A prima vista sembra probabilmente più complicato di quanto dovrebbe, ma qui dobbiamo affrontare i seguenti problemi:

  • IF le istruzioni funzionano solo nelle procedure memorizzate, non quando vengono eseguite direttamente, ad esempio nel client mysql
  • più elegante e conciso SHOW COLUMNSnon funziona nella stored procedure quindi è necessario utilizzare INFORMATION_SCHEMA
  • la sintassi per la delimitazione delle istruzioni è strana in MySQL, quindi devi ridefinire il delimitatore per poter creare stored procedure. Non dimenticare di ripristinare il delimitatore!
  • INFORMATION_SCHEMA è globale per tutti i database, non dimenticare di filtrare TABLE_SCHEMA=DATABASE(). DATABASE()restituisce il nome del database attualmente selezionato.

1
Vorrei poter assegnare punti bonus per aver spiegato i problemi coinvolti che hanno portato a questo approccio. Grazie.
Bryan Petty

48

Se sei su MariaDB, non è necessario utilizzare stored procedure. Usa, ad esempio:

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

Vedere qui


8
Brillante! Ancora un altro motivo per usare MariaDB.
Andrew Ensley,

+1 Ho lavorato con Maria tutto il tempo e ho provato tutti questi passaggi sopra nessuno di loro ha funzionato fino a quando non sono arrivato a questo, questo mi ha salvato la vita.
Thielicious

Interessante .. Non ho mai avuto
problemi

Questa soluzione è ottima, ma richiede MariaDB 10.0.2. Avviso per chiunque desideri utilizzare questa soluzione elegante, ma è bloccato su una versione precedente.
jblopez

ALTER TABLE nome_tabella CAMBIA COLONNA SE ESISTE nome_errore_colonna nome_colonna tinyint (1) DEFAULT 0; -Funziona anche bene!
Damonsson,

34

La maggior parte delle risposte riguarda come aggiungere una colonna in modo sicuro in una procedura memorizzata, ho avuto la necessità di aggiungere una colonna a una tabella in modo sicuro senza utilizzare una procedura memorizzata e ho scoperto che MySQL non consente l'uso di IF Exists()un SP esterno . Pubblicherò la mia soluzione che potrebbe aiutare qualcuno nella stessa situazione.

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
Nota che ho dovuto aggiungere un "LIMIT 1" all'istruzione SELECT quando lavoravo tramite la GUI del workbench MySQL.
Al Dass

23

Un altro modo per farlo sarebbe ignorare l'errore con 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();;

Penso che sia più ordinato in questo modo che con una existssottoquery. Soprattutto se hai molte colonne da aggiungere e desideri eseguire lo script più volte.

maggiori informazioni sui gestori continue possono essere trovate su http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html


Lo adoro! Non ci avrei mai pensato. Sto per passare a questo modo di fare le cose di sicuro.
Johnny Kauffman

ERRORE 1060 (42S21): nome colonna duplicato "newcolumnname"
Jake

Questo è davvero carino!
ATOzTOA

6

Sto usando MySQL 5.5.19.

Mi piace avere script che puoi eseguire e rieseguire senza errori, specialmente dove gli avvisi sembrano persistere, che vengono visualizzati di nuovo più tardi mentre eseguo script che non hanno errori / avvisi. Per quanto riguarda l'aggiunta di campi, mi sono scritto una procedura per renderlo un po 'meno digitato:

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

Il codice per creare la procedura addFieldIfNotExists è il seguente:

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

Non ho scritto una procedura per modificare in sicurezza una colonna, ma penso che la procedura sopra potrebbe essere facilmente modificata per farlo.


5

Ho preso lo sproc dell'OP e l'ho reso riutilizzabile e indipendente dallo schema. Ovviamente richiede ancora 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 ;

Questo funziona bene per me. L'unico cambiamento che ho dovuto apportare è stato rimuovere le virgolette (`) nella chiamata a concat. Inoltre, può semplificare il codice rimuovendo le variabili @paramTable, @ParamColumn, @ParamSchema e @ParamColumnDetails e utilizzando semplicemente i parametri direttamente.
hrabinowitz

1

Ho appena provato lo script della procedura memorizzata. Sembra che il problema siano i 'segni attorno ai delimitatori. I documenti MySQL mostrano che i caratteri delimitatori non necessitano delle virgolette singole.

Quindi tu vuoi:

delimiter //

Invece di:

delimiter '//'

Per me va bene :)


@ Andy Hai completamente perso il punto. Questo commento sottolinea dove l'OP ha commesso il suo errore. L'OP c'era già se non per le virgolette singole.
RichardTheKiwi

1

Se lo stai eseguendo in uno script, ti consigliamo di aggiungere la seguente riga in seguito per renderlo rieseguibile, altrimenti ottieni un errore di procedura già esistente.

drop procedure foo;

1

Il modo migliore per aggiungere la colonna in PHP> PDO:

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

Nota: la colonna nella tabella non è ripetibile, ciò significa che non abbiamo bisogno di controllare l'esistenza di una colonna, ma per risolvere il problema controlliamo il codice sopra:

per esempio se funziona alert 1, se non 0, significa che la colonna esiste! :)


1

Controlla se la colonna esiste o meno in 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

La procedura di Jake https://stackoverflow.com/a/6476091/6751901 è una soluzione molto semplice e buona per aggiungere nuove colonne, ma con una riga aggiuntiva:

DROP PROCEDURE IF EXISTS foo;;

puoi aggiungere nuove colonne in seguito e funzionerà anche la prossima volta:

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

Quindi in $ res by cycle cerca la chiave della tua colonna Smth in questo modo:

    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
questo può essere fatto un po 'più efficiente SHOW fields FROM __TABLE__NAME__ where field='_my_col_'; e quindi verificare che il set di risultati non sia vuoto
Eugen Mayer

0

Di seguito sono riportate le procedure memorizzate in MySQL per aggiungere colonne in tabelle diverse in database diversi se la colonna non esiste in una tabella o tabelle database con i seguenti vantaggi

  • è possibile aggiungere più colonne contemporaneamente per modificare più tabelle in database diversi
  • vengono eseguiti tre comandi mysql, ovvero DROP, CREATE, CALL For Procedure
  • Il nome DATABASE deve essere modificato in base a USE altrimenti potrebbe verificarsi un problema per più dati

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
Sarebbe bello se potessi aggiungere qualche descrizione alla tua soluzione se noi (gli utenti) potessimo capire i vantaggi di questa soluzione nonostante le altre. Questo è un miglioramento per questa e per le future risposte.
Luís Cruz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.