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