agregar columna a la tabla mysql si no existe

109

Mi investigación y experimentos aún no han dado una respuesta, por lo que espero recibir ayuda.

Estoy modificando el archivo de instalación de una aplicación que en versiones anteriores no tenía una columna que quiero agregar ahora. No quiero agregar la columna manualmente, sino en el archivo de instalación y solo si la nueva columna no existe en la tabla.

La tabla se crea de la siguiente manera:

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 agrego lo siguiente, debajo de la declaración de creación de la tabla, entonces no estoy seguro de qué sucede si la columna ya existe (y tal vez está poblada):

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

Entonces, probé lo siguiente que encontré en alguna parte. Esto no parece funcionar, pero no estoy completamente seguro de haberlo utilizado correctamente.

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

¿Alguien tiene una buena manera de hacer esto?

E Wierda
fuente
2
Alterar information_schema.COLUMNS, es decir, lo que hace el procedimiento almacenado, es el camino a seguir en mi humilde opinión. ¿Qué parte de ella "no parece funcionar"?
Rodion

Respuestas:

49

Tenga en cuenta que INFORMATION_SCHEMAno es compatible con MySQL antes de 5.0. Los procedimientos almacenados tampoco son compatibles con versiones anteriores a 5.0, por lo que si necesita admitir MySQL 4.1, esta solución no es buena.

Una solución utilizada por los frameworks que utilizan migraciones de bases de datos es registrar en su base de datos un número de revisión para el esquema. Solo una tabla con una sola columna y una sola fila, con un número entero que indica qué revisión está vigente. Cuando actualice el esquema, incremente el número.

Otra solución sería simplemente probar el ALTER TABLE ADD COLUMNcomando. Debería arrojar un error si la columna ya existe.

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

Detecte el error e ignórelo en su script de actualización.

Bill Karwin
fuente
1
De acuerdo, esto es realmente crudo, pero alguien tiene que decirlo. Si simplemente está ejecutando un script SQL desde la línea de comandos, puede darle a mysql el --forcecambio, lo que significa que continúe incluso si hay un error. entonces, hazlo. solo desea asegurarse de que no haya declaraciones que NO desee que tengan éxito si algo anterior ha fallado.
David
85

Aquí hay una solución que funciona (acaba de probar con MySQL 5.0 en 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 primera vista, probablemente parezca más complicado de lo que debería, pero aquí tenemos que lidiar con los siguientes problemas:

  • IF Las declaraciones solo funcionan en procedimientos almacenados, no cuando se ejecutan directamente, por ejemplo, en el cliente mysql
  • más elegante y conciso SHOW COLUMNSno funciona en el procedimiento almacenado, por lo que debe usar INFORMATION_SCHEMA
  • la sintaxis para delimitar sentencias es extraña en MySQL, por lo que debe redefinir el delimitador para poder crear procedimientos almacenados. ¡No olvide volver a cambiar el delimitador!
  • INFORMATION_SCHEMA es global para todas las bases de datos, no olvide filtrar TABLE_SCHEMA=DATABASE(). DATABASE()devuelve el nombre de la base de datos actualmente seleccionada.
geekQ
fuente
1
Ojalá pudiera otorgar puntos de bonificación por explicar los problemas involucrados que llevaron a este enfoque. Gracias.
Bryan Petty
48

Si está en MariaDB, no es necesario utilizar procedimientos almacenados. Solo usa, por ejemplo:

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

Mira aquí

Giuseppe
fuente
8
¡Brillante! Otra razón más para usar MariaDB.
Andrew Ensley
+1 He estado trabajando con María todo el tiempo y probando todos estos pasos anteriores ninguno de ellos funcionó hasta que llegué a este, esto me salvó la vida.
Thielicious
Interesante ... Nunca me preocupo
walv
Esta solución es excelente, pero requiere MariaDB 10.0.2. Solo avise a cualquiera que quiera usar esta elegante solución, pero está atascado en una versión anterior.
jblopez
ALTER TABLE nombre_tabla CAMBIAR COLUMNA SI EXISTE nombre_corriente_columna nombre_columna tinyint (1) DEFAULT 0; -¡Funciona también bien!
Damonsson
34

La mayoría de las respuestas abordan cómo agregar una columna de forma segura en un procedimiento almacenado, tuve la necesidad de agregar una columna a una tabla de manera segura sin usar un proceso almacenado y descubrí que MySQL no permite el uso de IF Exists()un SP externo . Publicaré mi solución que podría ayudar a alguien en la misma situación.

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
fuente
1
Tenga en cuenta que tuve que agregar un "LIMIT 1" a la instrucción SELECT cuando trabajaba a través de la GUI del banco de trabajo MySQL.
Al Dass
23

Otra forma de hacer esto sería ignorar el error 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();;

Creo que es más ordenado de esta manera que con una existssubconsulta. Especialmente si tiene muchas columnas para agregar y desea ejecutar el script varias veces.

se puede encontrar más información sobre los controladores continuos en http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html

Jake
fuente
¡Quiéralo! Nunca hubiera pensado en eso. Seguro que voy a cambiar a esta forma de hacer las cosas.
Johnny Kauffman
ERROR 1060 (42S21): nombre de columna duplicado 'newcolumnname'
Jake
¡Esto es realmente genial!
ATOzTOA
6

Estoy usando MySQL 5.5.19.

Me gusta tener scripts que se pueden ejecutar y volver a ejecutar sin errores, especialmente cuando las advertencias parecen persistir, apareciendo de nuevo más tarde mientras ejecuto scripts que no tienen errores / advertencias. En lo que respecta a la adición de campos, escribí un procedimiento para hacerlo un poco menos tipeado:

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

El código para crear el procedimiento addFieldIfNotExists es el siguiente:

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

No escribí un procedimiento para modificar de forma segura una columna, pero creo que el procedimiento anterior podría modificarse fácilmente para hacerlo.

Jonathan
fuente
5

Tomé el sproc del OP y lo hice reutilizable e independiente del esquema. Obviamente, todavía requiere 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
fuente
Esto funciona bien para mi. El único cambio que tuve que hacer fue eliminar las comillas traseras (`) en la llamada a concat. Además, puede simplificar el código eliminando las variables @paramTable, @ParamColumn, @ParamSchema y @ParamColumnDetails y simplemente usar los parámetros directamente.
hrabinowitz
1

Probé el script de procedimiento almacenado. Parece que el problema son las 'marcas alrededor de los delimitadores. Los documentos de MySQL muestran que los caracteres delimitadores no necesitan las comillas simples.

Entonces quieres:

delimiter //

En vez de:

delimiter '//'

Funciona para mi :)

Chico que rugió
fuente
@Andy No entendiste por completo. Este comentario señala dónde cometió el error el OP. El OP ya estaba allí si no fuera por las comillas simples.
RichardTheKiwi
1

Si está ejecutando esto en un script, querrá agregar la siguiente línea después para que se pueda volver a ejecutar; de lo contrario, obtendrá un error de procedimiento ya existente.

drop procedure foo;
estera vajilla
fuente
1

La mejor manera de agregar la columna en PHP> PDO:

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

Nota: la columna en la tabla no es repetible, eso significa que no necesitamos verificar la existencia de una columna, pero para resolver el problema verificamos el código anterior:

por ejemplo, si funciona alerta 1, si no 0, ¡lo que significa que la columna existe! :)

Maher
fuente
1

Compruebe si la columna existe o no 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
fuente
1

El procedimiento de Jake https://stackoverflow.com/a/6476091/6751901 es una solución muy simple y buena para agregar nuevas columnas, pero con una línea adicional:

DROP PROCEDURE IF EXISTS foo;;

puede agregar nuevas columnas más adelante allí, y también funcionará la próxima vez:

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

Luego, en $ res por ciclo, busque la clave de su columna Smth así:

    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
fuente
1
esto se puede hacer un poco más eficiente SHOW fields FROM __TABLE__NAME__ where field='_my_col_'; y luego verificar que el conjunto de resultados no esté vacío
Eugen Mayer
0

A continuación se muestra el procedimiento almacenado en MySQL para agregar columnas en diferentes tablas en diferentes bases de datos si la columna no existe en una (s) tabla (s) de base de datos con las siguientes ventajas

  • Se pueden agregar varias columnas a la vez para modificar varias tablas en diferentes bases de datos
  • se ejecutan tres comandos de mysql, es decir, DROP, CREATE, CALL para procedimiento
  • El nombre de la BASE DE DATOS debe cambiarse según el USO; de lo contrario, pueden surgir problemas para varios datos

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
fuente
-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
fuente
4
Sería genial si pudiera agregar alguna descripción a su solución, ya que nosotros (los usuarios) podemos entender las ventajas de esta solución a pesar de las demás. Esta es una mejora para esta y futuras respuestas.
Luís Cruz