check if column exists before ALTER TABLE -- mysql - mysql

Is there a way to check if a column exists in a mySQL DB prior to (or as) the ALTER TABLE ADD coumn_name statement runs? Sort of an IF column DOES NOT EXIST ALTER TABLE thing.
I've tried ALTER IGNORE TABLE my_table ADD my_column but this still throws the error if the column I'm adding already exists.
EDIT: use case is to upgrade a table in an already installed web app-- so to keep things simple, I want to make sure the columns I need exist, and if they don't, add them using ALTER TABLE

Since mysql control statements (e.g. "IF") only work in stored procedures, a temporary one can be created and executed:
DROP PROCEDURE IF EXISTS add_version_to_actor;
DELIMITER $$
CREATE DEFINER=CURRENT_USER PROCEDURE add_version_to_actor ( )
BEGIN
DECLARE colName TEXT;
SELECT column_name INTO colName
FROM information_schema.columns
WHERE table_schema = 'connjur'
AND table_name = 'actor'
AND column_name = 'version';
IF colName is null THEN
ALTER TABLE actor ADD version TINYINT NOT NULL DEFAULT '1' COMMENT 'code version of actor when stored';
END IF;
END$$
DELIMITER ;
CALL add_version_to_actor;
DROP PROCEDURE add_version_to_actor;

Do you think you can try this?:
SELECT IFNULL(column_name, '') INTO #colName
FROM information_schema.columns
WHERE table_name = 'my_table'
AND column_name = 'my_column';
IF #colName = '' THEN
-- ALTER COMMAND GOES HERE --
END IF;
It's no one-liner, but can you at least see if it will work for you? At least while waiting for a better solution..

Utility functions and procedures
First, I have a set of utility functions and procedures that I use to do things like drop foreign keys, normal keys and columns. I just leave them in the database so I can use them as needed.
Here they are.
delimiter $$
create function column_exists(ptable text, pcolumn text)
returns bool
reads sql data
begin
declare result bool;
select
count(*)
into
result
from
information_schema.columns
where
`table_schema` = 'my_database' and
`table_name` = ptable and
`column_name` = pcolumn;
return result;
end $$
create function constraint_exists(ptable text, pconstraint text)
returns bool
reads sql data
begin
declare result bool;
select
count(*)
into
result
from
information_schema.table_constraints
where
`constraint_schema` = 'my_database' and
`table_schema` = 'my_database' and
`table_name` = ptable and
`constraint_name` = pconstraint;
return result;
end $$
create procedure drop_fk_if_exists(ptable text, pconstraint text)
begin
if constraint_exists(ptable, pconstraint) then
set #stat = concat('alter table ', ptable, ' drop foreign key ', pconstraint);
prepare pstat from #stat;
execute pstat;
end if;
end $$
create procedure drop_key_if_exists(ptable text, pconstraint text)
begin
if constraint_exists(ptable, pconstraint) then
set #stat = concat('alter table ', ptable, ' drop key ', pconstraint);
prepare pstat from #stat;
execute pstat;
end if;
end $$
create procedure drop_column_if_exists(ptable text, pcolumn text)
begin
if column_exists(ptable, pcolumn) then
set #stat = concat('alter table ', ptable, ' drop column ', pcolumn);
prepare pstat from #stat;
execute pstat;
end if;
end $$
delimiter ;
Dropping constraints and columns using the utilities above
With those in place, it is pretty easy to use them to check columns and constraints for existence:
-- Drop service.component_id
call drop_fk_if_exists('service', 'fk_service_1');
call drop_key_if_exists('service', 'component_id');
call drop_column_if_exists('service', 'component_id');
-- Drop commit.component_id
call drop_fk_if_exists('commit', 'commit_ibfk_1');
call drop_key_if_exists('commit', 'commit_idx1');
call drop_column_if_exists('commit', 'component_id');
-- Drop component.application_id
call drop_fk_if_exists('component', 'fk_component_1');
call drop_key_if_exists('component', 'application_id');
call drop_column_if_exists('component', 'application_id');

Make a count sentence with the example below by John Watson.
SELECT count(*) FROM information_schema.COLUMNS
WHERE COLUMN_NAME = '...'
and TABLE_NAME = '...'
and TABLE_SCHEMA = '...'
Save that result in an integer and then make it a condition to apply the ADD COLUMN sentence.

You can test if a column exists with:
IF EXISTS (
SELECT * FROM information_schema.COLUMNS
WHERE COLUMN_NAME = '...'
and TABLE_NAME = '...'
and TABLE_SCHEMA = '...')
...
Just fill in your column name, table name, and database name.

Although its quite an old post but still i feel good about sharing my solution to this issue. If column doesn't exist then an exception would occur definitely and then i am creating the column in table.
I just used the code below:
try
{
DATABASE_QUERY="SELECT gender from USER;";
db.rawQuery(DATABASE_QUERY, null);
}
catch (Exception e)
{
e.printStackTrace();
DATABASE_UPGRADE="alter table USER ADD COLUMN gender VARCHAR(10) DEFAULT 0;";
db.execSQL(DATABASE_UPGRADE);
}

You can create a procedure with a CONTINUE handler in case the column exists (please note this code doesn't work in PHPMyAdmin):
DROP PROCEDURE IF EXISTS foo;
CREATE PROCEDURE foo() BEGIN
DECLARE CONTINUE HANDLER FOR 1060 BEGIN END;
ALTER TABLE `tableName` ADD `columnName` int(10) NULL AFTER `otherColumn`;
END;
CALL foo();
DROP PROCEDURE foo;
This code should not raise any error in case the column already exists. It will just do nothing and carry on executing the rest of the SQL.

DELIMITER $$
DROP PROCEDURE IF EXISTS `addcol` $$
CREATE DEFINER=`admin`#`localhost` PROCEDURE `addcol`(tbn varchar(45), cn varchar(45), ct varchar(45))
BEGIN
#tbn: table name, cn: column name, ct: column type
DECLARE CONTINUE HANDLER FOR 1060 BEGIN END;
set cn = REPLACE(cn, ' ','_');
set #a = '';
set #a = CONCAT("ALTER TABLE `", tbn ,"` ADD column `", cn ,"` ", ct);
PREPARE stmt FROM #a;
EXECUTE stmt;
END $$
DELIMITER ;

This syntax work for me :
SHOW COLUMNS FROM < tablename > LIKE '< columnName >'
More in this post :
https://mzulkamal.com/blog/mysql-5-7-check-if-column-exist?viewmode=0

As per MYSQL Community:
IGNORE is a MySQL extension to standard SQL. It controls how ALTER TABLE works if there are duplicates on unique keys in the new table or if warnings occur when strict mode is enabled. If IGNORE is not specified, the copy is aborted and rolled back if duplicate-key errors occur. If IGNORE is specified, only one row is used of rows with duplicates on a unique key. The other conflicting rows are deleted. Incorrect values are truncated to the closest matching acceptable value.
So a working Code is:
ALTER IGNORE TABLE CLIENTS ADD CLIENT_NOTES TEXT DEFAULT NULL;
Data posted here:
http://dev.mysql.com/doc/refman/5.1/en/alter-table.html

Related

Mysql calling procedure failed when dynamically alter table in it

I want to alter my tables dynamically based on whether the table has specific column.
My database name is summer_cms, and there are over 50 tables in it.
What I want are below:
If a table has a column named add_time, then I would like to add a column add_user_id in it.
Similarly, I would like to add update_user_id in the table if update_time is found.
I know I should get it down in the process of creating the database schemas, but my database has been built and I have to alter it by need.
So I create a procedure to do it:
CREATE PROCEDURE ALTER_SUMMER_TABLE()
BEGIN
DECLARE tableName VARCHAR(64);
DECLARE exitence VARCHAR(64);
DECLARE ntable INT; # number of tables
DECLARE i INT; # index
SET i = 0;
# get the count of table
SELECT COUNT(DISTINCT(TABLE_NAME)) INTO ntable FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'summer_cms';
WHILE i < ntable DO
# select the specific table name into the variable of `tableName`.
SELECT TABLE_NAME INTO tableName
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'summer_cms'
AND COLUMN_NAME = 'add_time'
LIMIT 1 OFFSET i;
# alter table, but I get error in this clause.
ALTER TABLE tableName ADD COLUMN `add_user_id` INT NOT NULL DEFAULT 0 COMMENT 'add user id';
# check if the table has `update_time`
SELECT TABLE_NAME INTO exitence
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'summer_cms'
AND TABLE_NAME = tableName
AND COLUMN_NAME = 'update_time';
# add `update_user_id` if `update_time` be found.
IF exitence THEN
ALTER TABLE tableName ADD COLUMN `update_user_id` INT NOT NULL DEFAULT 0 COMMENT 'update user id';
END IF;
SET i = i + 1;
END WHILE;
END
But I got an error when I call this procedure.
Procedure execution failed
1146 - Table 'summer_cms.tableName' doesn't exist
Dose anyone could tell me what I was missing or wrong? Any help will be appreciated.
There a a few alterations you can make to your procedure to make it more streamlined as well as getting round a few problems.
First using a cursor to select the table names rather than using the two selects your using. Secondly to use a prepared statement to allow you to dynamically set the table name...
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `ALTER_SUMMER_TABLE`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE tableName VARCHAR(64);
declare cur cursor for SELECT TABLE_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'summer_cms'
AND COLUMN_NAME = 'add_time';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
open cur;
start_loop: loop
fetch cur into tableName;
if (done = 1 )THEN
LEAVE start_loop;
END IF;
SET #sql = CONCAT('ALTER TABLE ', tableName,' ADD COLUMN `add_user_id` INT NOT NULL DEFAULT 0 ');
PREPARE stmt FROM #sql;
EXECUTE stmt;
end loop;
close cur;
END$$
DELIMITER ;
You could do a few tweaks - only fetch table names where the column doesn't already exist for example.
Here's an example of dynamic sql
drop procedure if exists alter_table;
delimiter //
CREATE DEFINER=`root`#`localhost` procedure alter_table()
begin
declare tablename varchar(20);
set tablename = 'u';
set #sqlstmt = concat('ALTER TABLE ', tableName, ' ADD COLUMN ', char(96), 'add_user_id', char(96), ' INT NOT NULL DEFAULT 0 COMMENT', char(39), 'add user id', char(39),';');
prepare stmt from #sqlstmt;
execute stmt;
deallocate prepare stmt;
end //
delimiter ;
Note I have used ascii backticks and single quotes.

MySQL - is it possible to conditionally run queries?

I was wondering if it's possible to conditionally run certain statements in MySQL. Something like this:
IF EXISTS (SELECT * FROM information_schema.columns WHERE TABLE_NAME = 'test_table' AND COLUMN_NAME = 'userid' AND IS_NULLABLE = 'NO')
THEN
ALTER TABLE test_table MODIFY userid INT(11) NULL;
END IF;
I've done some googleing and I'm not pulling up anything useful or sane. Wondering if I'm missing something or if this is just a serious MySQL limitation.
Thanks to William_Wilson, Here's my working query. I still think this is totally backwards, but I supposed that's how MySQL rolls...
delimiter //
create procedure update_stuff()
begin
IF EXISTS (SELECT * FROM information_schema.columns WHERE TABLE_NAME = 'test_table' AND COLUMN_NAME = 'userid' AND IS_NULLABLE = 'NO')
THEN
ALTER TABLE test_table MODIFY userid INT(11) NULL;
END IF;
END
//
delimiter ;
-- Execute the procedure
call update_stuff();
-- Drop the procedure
drop procedure update_stuff;

Loop through array in mysql Stored procs

I need to dynamically alter set of tables
Currently i can alter only one using below SP.
DROP PROCEDURE IF EXISTS add_version_to_actor;
DELIMITER $$
CREATE DEFINER=CURRENT_USER PROCEDURE add_version_to_actor (IN table_name VARCHAR(40) )
BEGIN
DECLARE colName TEXT;
SELECT column_name INTO colName
FROM information_schema.columns
WHERE table_schema = 'database_name'
AND table_name = table_name
AND column_name = 'column';
IF colName is null THEN
Alter Table database_name.table_name ADD `sequence` INT( 10 ) NOT NULL;
END IF;
END$$
DELIMITER ;
CALL add_version_to_actor('table1');
DROP PROCEDURE add_version_to_actor;
Currently 'table1' is a single parameter. I need to pass multiple table names as string like this CALL add_version_to_actor('table1','table2');and loop through all of them.How can this be achieved.
Thanks in advance.

MySQL trigger issue on insert

I'm running an insert into a members table and when a new row is added I want to run a trigger to update the username field of the members table but it wont let me due to constraints due to possible deadlock situations.
DELIMITER //
CREATE TRIGGER tr_add_member
AFTER INSERT ON td_members
FOR EACH ROW BEGIN
IF mem_username = '' THEN
SET mem_username = CONCAT('user' , mem_id);
END IF;
END//
DELIMITER ;
I've tried using the OLD and NEW keywords but they don't work, I've removed the NEW and OLD keywords above but get the below error with this trigger.
ERROR 1193 (HY000): Unknown system variable 'mem_username'
Should I be calling a procedure from the trigger to do what I want it and just run a simple UPDATE statement from within the procedure?
You have to use BEFORE INSERT trigger, but not an AFTER INSERT.
And if mem_id is auto incremented primary key field, then find its
next auto increment value from information_schema.tables and use it.
Change your trigger code as follows:
DELIMITER //
DROP TRIGGER IF EXISTS tr_add_member //
CREATE TRIGGER tr_add_member
BEFORE INSERT ON td_members
FOR EACH ROW
BEGIN
DECLARE _mem_id INT DEFAULT 0;
IF length( trim( NEW.mem_username ) ) = 0 THEN
SELECT AUTO_INCREMENT INTO _mem_id
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'td_members'
AND TABLE_SCHEMA = DATABASE();
SET NEW.mem_username = CONCAT( 'user', _mem_id );
END IF;
END;
//
DELIMITER ;

Determining if MySQL table index exists before creating

Our system's automated database migration process involves running .sql scripts containing new table definitions and their accompanying indexes.
I require the ability to create these tables and indexes only if they don't already exist. Tables are taken care of by using IF NOT EXISTS but no such syntax exists when creating indexes.
I've tried to write a stored procedure, shown below, but this fails presumably as you can't select from a show statement.
DELIMITER $$
DROP PROCEDURE IF EXISTS csi_add_index $$
CREATE PROCEDURE csi_add_index(in theTable varchar(128), in theIndexName varchar(128), in theIndexColumns varchar(128) )
BEGIN
IF(((SELECT COUNT(*) FROM (SHOW KEYS FROM theTable WHERE key_name = theIndexName)) tableInfo = 0) THEN
SET #s = CONCAT('CREATE INDEX ' , theIndexName , ' ON ' , theTable, '(', theIndexColumns, ')');
PREPARE stmt FROM #s;
EXECUTE stmt;
END IF;
END $$
I've considered dropping and recreating but the process, as it exists, assumes that it'll encounter no errors hence me wanting to check for existence first.
Is there another way to retrieve the indexes of a table to check if an index already exists before creating or can anyone suggest a better approach to managing this?
EDIT: Please note that this is an automated procedure, no human intervention.
SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE
`TABLE_CATALOG` = 'def' AND `TABLE_SCHEMA` = DATABASE() AND
`TABLE_NAME` = theTable AND `INDEX_NAME` = theIndexName
After some more banging my head off the wall and intense googling I found the information_schema.statistics table. This contains the index_name for a table.
My stored procedure is now
DELIMITER $$
DROP PROCEDURE IF EXISTS csi_add_index $$
CREATE PROCEDURE csi_add_index(in theTable varchar(128), in theIndexName varchar(128), in theIndexColumns varchar(128) )
BEGIN
IF((SELECT COUNT(*) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name =
theTable AND index_name = theIndexName) = 0) THEN
SET #s = CONCAT('CREATE INDEX ' , theIndexName , ' ON ' , theTable, '(', theIndexColumns, ')');
PREPARE stmt FROM #s;
EXECUTE stmt;
END IF;
END $$
and works as expected.
Thanks for the suggestions.
Use SHOW INDEX FROM mytable FROM mydb; and check if the index is present - each of the returned rows represents one part of an index; the column that would probably interest you most is Key_name, as it contains the name of the index. Documentation here.
You can query infomration_schema database for this and many more useful information
http://dev.mysql.com/doc/refman/5.0/en/information-schema.html
Since Text and Blobs need a size, I added it to the stored procedure.
DROP PROCEDURE IF EXISTS createIndex;
DELIMITER $$
-- Create a temporary stored procedure for checking if Indexes exist before attempting to re-create them. => can be called
$$
CREATE PROCEDURE `createIndex`(
IN `tableName` VARCHAR(128),
IN `indexName` VARCHAR(128),
IN `indexColumns` VARCHAR(128),
IN `indexSize` VARCHAR(128)
)
BEGIN
IF((SELECT COUNT(*) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() AND table_name = tableName AND index_name = indexName) = 0) THEN
IF(indexSize > 0) THEN
SET #sqlCommand = CONCAT('CREATE INDEX ' , indexName , ' ON ' , tableName, '(', indexColumns, '(' , indexSize, '))');
ELSE
SET #sqlCommand = CONCAT('CREATE INDEX ' , indexName , ' ON ' , tableName, '(', indexColumns, ')');
END IF;
PREPARE _preparedStatement FROM #sqlCommand;
EXECUTE _preparedStatement;
END IF;
END $$
DELIMITER ;
You could just create another table with the correct indices, copy everything from the old table and then drop it and rename the new table back to what the old one used to be. A bit hackish and might be a bit heavy for big tables but still fairly straightforward.
Not a new version but a more complete solution that includes a call to create 2 indexes.
USE MyDatabaseName;
DELIMITER $$
-- Create a temporary stored procedure for checking if Indexes exist before attempting to re-create them.
DROP PROCEDURE IF EXISTS `MyDatabaseName`.`spCreateIndex` $$
CREATE PROCEDURE `MyDatabaseName`.`spCreateIndex` (tableName VARCHAR(128), in indexName VARCHAR(128), in indexColumns VARCHAR(128))
BEGIN
IF((SELECT COUNT(*) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() AND table_name = tableName AND index_name = indexName) = 0) THEN
SET #sqlCommand = CONCAT('CREATE INDEX ' , indexName , ' ON ' , tableName, '(', indexColumns, ')');
PREPARE _preparedStatement FROM #sqlCommand;
EXECUTE _preparedStatement;
END IF;
END $$
DELIMITER ;
-- Create the Indexes if they do not exist already.
CALL spCreateIndex('MyCustomers', 'idxCustNum', 'CustomerNumber');
CALL spCreateIndex('MyProducts', 'idxProductName', 'ProductName');
DELIMITER $$
-- Drop the temporary stored procedure.
DROP PROCEDURE IF EXISTS `MyDatabaseName`.`spCreateIndex` $$
DELIMITER ;