добавить столбец в таблицу 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.';

Если я добавлю следующее под оператором создания таблицы, я не уверен, что произойдет, если столбец уже существует (и, возможно, заполнен):

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, то есть то, что делает хранимая процедура, - это путь ИМХО. Какая его часть «не работает»?
Rodion

Ответы:


49

Обратите внимание, что INFORMATION_SCHEMAэто не поддерживается в MySQL до 5.0. Хранимые процедуры не поддерживаются и до 5.0, поэтому, если вам нужно поддерживать MySQL 4.1, это решение не подходит.

Одним из решений, используемых фреймворками, использующими миграции баз данных, является запись в базу данных номера редакции схемы. Просто таблица с одним столбцом и одной строкой, с целым числом, указывающим, какая ревизия является текущей в силе. Когда вы обновляете схему, увеличивайте число.

Другим решением было бы просто попробовать на ALTER TABLE ADD COLUMNкоманду. Он должен выдать ошибку, если столбец уже существует.

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

Поймайте ошибку и проигнорируйте ее в сценарии обновления.


1
Хорошо, это действительно грубо, но кто-то должен это сказать. если вы просто запускаете сценарий SQL из командной строки, вы можете указать mysql --forceпереключатель, что означает продолжение работы даже в случае ошибки. тогда просто дерзай. вы просто хотите быть уверены, что нет никаких заявлений, которые вы НЕ хотите добиться, если что-то предыдущее не удалось.
David

85

Вот рабочее решение (только что опробовано с MySQL 5.0 на 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 ;

На первый взгляд, это может показаться сложнее, чем должно быть, но здесь нам приходится иметь дело со следующими проблемами:

  • 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.
Эндрю Энсли,

+1 Я работал с Марией все время и пробовал все эти шаги выше, ни один из них не работал, пока я не добился этого, это спасло мне жизнь.
Thielicious

Интересно .. Я никогда не беспокоился
walv

Это отличное решение, но для него требуется MariaDB 10.0.2. Внимание всем, кто хочет использовать это элегантное решение, но застрял на более старой версии.
jblopez

ALTER TABLE имя_таблицы ИЗМЕНИТЬ КОЛОНК, ЕСЛИ СУЩЕСТВУЕТ неправильное имя_столбца имя_столбца tinyint (1) ПО УМОЛЧАНИЮ 0; -работает тоже хорошо!
Damonsson

35

Большинство ответов касаются того, как безопасно добавить столбец в хранимую процедуру, у меня возникла необходимость добавить столбец в таблицу без использования хранимой процедуры, и я обнаружил, что MySQL не позволяет использовать IF Exists()вне SP . Я опубликую свое решение, которое может помочь кому-то в такой же ситуации.

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
Обратите внимание: мне пришлось добавить «LIMIT 1» к оператору SELECT при работе через графический интерфейс рабочего места MySQL.
Al Dass

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'
Джейк

Это действительно здорово!
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

Я взял sproc OP и сделал его многоразовым и независимым от схемы. Очевидно, для этого по-прежнему требуется 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, и просто использовать параметры напрямую.
Грабиновиц

1

Только что попробовал сценарий хранимой процедуры. Кажется, проблема в 'метках вокруг разделителей. В MySQL Docs показывают , что символы - ограничители не нужны одиночные кавычки.

Итак, вы хотите:

delimiter //

Вместо того:

delimiter '//'

Работает для меня :)


@Andy Ты полностью упустил суть. Этот комментарий указывает, где ОП допустил ошибку. OP уже был там, если бы не одинарные кавычки.
RichardTheKiwi

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

Процедура от Джейка 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 по циклу найдите ключ своего столбца 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_'; а затем проверить, не является ли набор результатов непустым
Ойген Майер

0

Ниже представлена ​​хранимая процедура в MySQL для добавления столбца (столбцов) в разные таблицы в разных базах данных, если столбец не существует в таблице (ах) базы данных, со следующими преимуществами.

  • можно добавить несколько столбцов, использовать одновременно для изменения нескольких таблиц в разных базах данных
  • выполняются три команды mysql, т.е. DROP, CREATE, CALL для процедуры
  • Имя БАЗЫ ДАННЫХ должно быть изменено в соответствии с ИСПОЛЬЗОВАНИЕМ, иначе проблема может возникнуть для нескольких данных.

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
Было бы здорово, если бы вы могли добавить некоторое описание к вашему решению, чтобы мы (пользователи) могли понять преимущества этого решения, несмотря на другие. Это улучшение для этого и будущих ответов.
Луис Крус,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.