This question already has answers here:
add column to mysql table if it does not exist
(16 answers)
Closed 4 years ago.
I keep getting an error when I run this on mysql5.7
What am I doing wrong. I basically just want to add a column if the column doesnt already exist
DROP PROCEDURE IF EXISTS ALIASCOLUMN;
DELIMITER //
CREATE PROCEDURE ALIASCOLUMN()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
ALTER TABLE 'human_api_procedure' ADD COLUMN 'alias' varchar(255);
END //
DELIMITER;
CALL ALIASCOLUMN();
DROP PROCEDURE ALIASCOLUMN;
Use the following in a stored procedure:
IF NOT EXISTS( SELECT *
FROM 'tablename'
WHERE table_name = 'tablename'
AND table_schema = 'db_name'
AND column_name = 'columnname') THEN
ALTER TABLE `human_api_procedure` ADD `alias` varchar(255) ;
END IF;
Check this link. It may help you. :)
DDL statements within a procedure have to be executed as dynamic SQL. (I think that's true, I'd need to check the docs to verify.)
Also, identifiers (table names, column names) should not be enclosed in single quotes. Single quotes are delimiters for string literals.
I'd expect this statement to fail with a syntax error:
ALTER TABLE 'human_api_procedure' ADD COLUMN 'alias' varchar(255)
This
ALTER TABLE human_api_procedure ADD COLUMN alias varchar(255)
^ ^ ^ ^
or this
ALTER TABLE `human_api_procedure` ADD COLUMN `alias` varchar(255)
^ ^ ^ ^
would be valid syntax. We could also use double quotes around identifiers, if ANSI_QUOTES is included in sql_mode.
To execute an ALTER TABLE statement in the context of a MySQL stored program, we could do something like this:
DELIMITER $$
CREATE PROCEDURE aliascolumn()
BEGIN
SET #sql = 'ALTER TABLE human_api_procedure ADD COLUMN alias varchar(255)';
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
MySQL permits routines to contain DDL statements, such as CREATE and DROP.
https://dev.mysql.com/doc/refman/5.7/en/create-procedure.html
The pattern demonstrated above, using the PREPARE/EXECUTE/DEALLOCATE would allow execution of for dynamic SQL, not just static SQL text.
Related
I'm trying to write a MySQL stored proceedure that loops through all existing tables in my database and creates a copy/clone of each table. I'm using a cursor to loop through the table names then create a new table like this:
DELIMITER //
CREATE PROCEDURE CopyTables()
BEGIN
DECLARE finished INT DEFAULT 0;
DECLARE tableName VARCHAR(100);
DECLARE copyTableName VARCHAR(100);
DECLARE curTables
CURSOR FOR
SELECT table_name FROM INFORMATION_SCHEMA.TABLES;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN curTables;
create_loop: LOOP
FETCH curTables INTO tableName;
IF finished THEN LEAVE create_loop; END IF;
SELECT concat('Processing table ', tableName);
SET copyTableName = CONCAT('copy_',tableName);
SELECT concat('Creating table ', copyTableName);
CREATE TABLE copyTableName LIKE tableName;
END LOOP;
CLOSE curTables;
END //
DELIMITER;
But I get an error when calling the stored procedure:
> call CopyTables()
[2020-12-08 18:16:03] 1 row retrieved starting from 1 in 77 ms (execution: 15 ms, fetching: 62 ms)
[2020-12-08 18:16:03] [S1000] Attempt to close streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming#7a714591 that was not registered. Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on any active result sets before attempting more queries.
Is the result set exception effectively complaining because I'm creating new tables which is effectively messing with the cursor/select? I've got additional table changes on both the original and copied table to perform, like adding new columns, creating triggers, modifying constraints.
The list of table names is not static, and this should be able to run on whatever database I need it.
Can you suggest another way to achieve this without the cursor perhaps?
The problem is that the procedure is returning multiple result sets, but your Java client is not handling that correctly.
Refer to How do you get multiple resultset from a single CallableStatement?
Another problem with your procedure is that you aren't creating tables the way you think you are.
This statement:
CREATE TABLE copyTableName LIKE tableName;
will only create a table named literally copyTableName that is like another table that is literally tableName. It will NOT use the values of variables by those names.
To do what you want, you need to use a prepared statement:
SET #sql = CONCAT('CREATE TABLE `', copyTableName, '` LIKE `', tableName, '`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This way the value of your variables is concatenated into an SQL statement.
Note that PREPARE only accepts a user-defined session variable, the type with the # sigil. It doesn't work with local variables you create in your procedure with DECLARE. Read https://dev.mysql.com/doc/refman/8.0/en/prepare.html and https://dev.mysql.com/doc/refman/8.0/en/user-variables.html
This is the query i am using:
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME =( N'CustomerVariable1Value'))
begin Alter table temp.DIM_BE_ACCOUNT drop column CustomerVariable1Value
It works fine the first time but when I run it again, it shows error.
How to make it error free and executes it many number of times?
Error message:
ALTER TABLE DROP COLUMN failed because column 'CustomerVariable1Value' does not exist in table 'DIM_BE_ACCOUNT'.
You are only looking for a column name out of all column names in the entire MySQL instance. You need to also filter by schema (=database) and table names:
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = N'CustomerVariable1Value' AND TABLE_NAME = 'MyTableName' AND TABLE_SCHEMA = 'MyDatabase')
Here is a solution that does not involve querying INFORMATION_SCHEMA, it simply ignores the error if the column does not exist.
DROP PROCEDURE IF EXISTS `?`;
DELIMITER //
CREATE PROCEDURE `?`
(
)
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
ALTER TABLE `table_name` DROP COLUMN `column_name`;
END //
DELIMITER ;
CALL `?`();
DROP PROCEDURE `?`;
P.S. Feel free to give it other name rather than ?
I want to update a MySQL database schema (with MySQL code) but I am unfortunately not sure of the state of the tables, as they are distributed..
Let's say some 'clients' have a table called "user" with a schema like
name VARCHAR(64) NOT NULL
password VARCHAR(64) NOT NULL
I want to add an email column, but it's possible that they already have an email column (depending on their installation version).
How can I run a command that ensures that there is a email column and does nothing if it's already there? Keep in mind I would be doing this for many tables that are more complex.
I know I could be creating temp tables and re-populating (and will if it's the only solution) but I figure there might be some kind of CREATE or UPDATE table command that has "oh you already have that column, skip" logic.
You can try like this:
DELIMITER $$
CREATE PROCEDURE Alter_MyTable()
BEGIN
DECLARE _count INT;
SET _count = ( SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'user' AND
COLUMN_NAME = 'email');
IF _count = 0 THEN
ALTER TABLE user
ADD COLUMN email varchar(512);
END IF;
END $$
DELIMITER ;
or rather make it a generic stored procedure like this:
create procedure AddColumnIfDoesntExists(
IN dbName tinytext,
IN tableName tinytext,
IN fieldName tinytext,
IN fieldDef text)
begin
IF NOT EXISTS (
SELECT * FROM information_schema.COLUMNS
WHERE column_name=fieldName
and table_name=tableName
and table_schema=dbName
)
THEN
set #ddl=CONCAT('ALTER TABLE ',dbName,'.',tableName,
' ADD COLUMN ',fieldName,' ',fieldDef);
prepare stmt from #ddl;
execute stmt;
END IF;
end;
//
delimiter ';'
If the column already exists the ALTER TABLE ADD COLUMN statement will throw an error, so if you are thinking that you might lose data because of trying to add a column that already exists that won't be the case, if any you need to handle error. See add column to mysql table if it does not exist
There are also resources telling you how to deal with these with store procedures, etc. See MySQL add column if not exist.
Hope it helps.
long time user, first time poster.
I have 2 tables;
a1_watchlists {id(PK),name,date}
a1_watchlist {id(PK),watchlists_id(FK(a1_watchlists.id)),company_name,asx_code,date}
I also have 2000 other tables that have been created with the name 'asx_'+[asx_code] (where asx_code is pulled from another table)
this table looks like;
asx_[asx_code] {date(PK),open,high,low,close,volume}
I want to select all from a1_watchlists and a1_watchlist and then select the latest date from the asx_[asx_code] table using the value from a1_watchlist.asx_code to generate the [asx_code] part of the table name.
The problem I have is that I want to use the value from a1_watchlist.asx_code as the table name prepending the string 'asx_' to this first.
Closest I have been able to get is;
DECLARE #TableName VARCHAR(100)
SELECT *
FROM a1_watchlist AS wl
JOIN a1_watchlists AS wls
ON wls.id = wl.watchlists_id
SET #TableName = 'asx_' + wl.asx_code
INNER JOIN (SELECT MAX(date),open,high,low,close,volume,amount_change,percent_change FROM #TableName)
This currently give the error:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DECLARE #TableName VARCHAR(100)
SELECT *
FROM a1_watchlist AS wl
' at line 1
The expected colums I need in the final result would be:
wl.id,wl.watchlists_id,wl.company_name,wl.asx_code,asx_[asx_code].date,asx_[asx_code].open,asx_[asx_code].high,asx_[asx_code].low,asx_[asx_code].close,asx_[asx_code].volume
Let me know if you require more information.
I'm not going to speak to what to do in the case where you have 2000+ tables that start with asx+ some code... (i live in a town with multiple bridges) or even whether what you're doing is the best way to get where you want to go. BUT, it does look like you're attempting to concatenate things together and create a dynamic statement. If that sounds right, then I'd recommend you look into prepared statements. Like the following. Hope this helps.
DELIMITER $$
DROP PROCEDURE IF EXISTS prRetrieveAllFromTable$$
CREATE PROCEDURE prRetrieveAllFromTable(tableName VARCHAR(64))
BEGIN
SET #s = CONCAT('SELECT * FROM ',tableName );
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
CALL prRetrieveAllFromTable('calendar');
http://dev.mysql.com/doc/refman/5.5/en/sql-syntax-prepared-statements.html
How To have Dynamic SQL in MySQL Stored Procedure
How can ALTER be used to drop a column in a MySQL table if that column exists?
I know I can use ALTER TABLE my_table DROP COLUMN my_column, but that will throw an error if my_column does not exist. Is there alternative syntax for dropping the column conditionally?
I'm using MySQL version 4.0.18.
For MySQL, there is none: MySQL Feature Request.
Allowing this is arguably a really bad idea, anyway: IF EXISTS indicates that you're running destructive operations on a database with (to you) unknown structure. There may be situations where this is acceptable for quick-and-dirty local work, but if you're tempted to run such a statement against production data (in a migration etc.), you're playing with fire.
But if you insist, it's not difficult to simply check for existence first in the client, or to catch the error.
MariaDB also supports the following starting with 10.0.2:
DROP [COLUMN] [IF EXISTS] col_name
i. e.
ALTER TABLE my_table DROP IF EXISTS my_column;
But it's arguably a bad idea to rely on a non-standard feature supported by only one of several forks of MySQL.
There is no language level support for this in MySQL. Here is a work-around involving MySQL information_schema meta-data in 5.0+, but it won't address your issue in 4.0.18.
drop procedure if exists schema_change;
delimiter ';;'
create procedure schema_change() begin
/* delete columns if they exist */
if exists (select * from information_schema.columns where table_schema = schema() and table_name = 'table1' and column_name = 'column1') then
alter table table1 drop column `column1`;
end if;
if exists (select * from information_schema.columns where table_schema = schema() and table_name = 'table1' and column_name = 'column2') then
alter table table1 drop column `column2`;
end if;
/* add columns */
alter table table1 add column `column1` varchar(255) NULL;
alter table table1 add column `column2` varchar(255) NULL;
end;;
delimiter ';'
call schema_change();
drop procedure if exists schema_change;
I wrote some more detailed information in a blog post.
I know this is an old thread, but there is a simple way to handle this requirement without using stored procedures. This may help someone.
set #exist_Check := (
select count(*) from information_schema.columns
where TABLE_NAME='YOUR_TABLE'
and COLUMN_NAME='YOUR_COLUMN'
and TABLE_SCHEMA=database()
) ;
set #sqlstmt := if(#exist_Check>0,'alter table YOUR_TABLE drop column YOUR_COLUMN', 'select ''''') ;
prepare stmt from #sqlstmt ;
execute stmt ;
Hope this helps someone, as it did me (after a lot of trial and error).
I just built a reusable procedure that can help making DROP COLUMN idempotent:
-- column_exists:
DROP FUNCTION IF EXISTS column_exists;
DELIMITER $$
CREATE FUNCTION column_exists(
tname VARCHAR(64),
cname VARCHAR(64)
)
RETURNS BOOLEAN
READS SQL DATA
BEGIN
RETURN 0 < (SELECT COUNT(*)
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA` = SCHEMA()
AND `TABLE_NAME` = tname
AND `COLUMN_NAME` = cname);
END $$
DELIMITER ;
-- drop_column_if_exists:
DROP PROCEDURE IF EXISTS drop_column_if_exists;
DELIMITER $$
CREATE PROCEDURE drop_column_if_exists(
tname VARCHAR(64),
cname VARCHAR(64)
)
BEGIN
IF column_exists(tname, cname)
THEN
SET #drop_column_if_exists = CONCAT('ALTER TABLE `', tname, '` DROP COLUMN `', cname, '`');
PREPARE drop_query FROM #drop_column_if_exists;
EXECUTE drop_query;
END IF;
END $$
DELIMITER ;
Usage:
CALL drop_column_if_exists('my_table', 'my_column');
Example:
SELECT column_exists('my_table', 'my_column'); -- 1
CALL drop_column_if_exists('my_table', 'my_column'); -- success
SELECT column_exists('my_table', 'my_column'); -- 0
CALL drop_column_if_exists('my_table', 'my_column'); -- success
SELECT column_exists('my_table', 'my_column'); -- 0
Chase Seibert's answer works, but I'd add that if you have several schemata you want to alter the SELECT thus:
select * from information_schema.columns where table_schema in (select schema()) and table_name=...
You can use this script, use your column, schema and table name
IF EXISTS (SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableName' AND COLUMN_NAME = 'ColumnName'
AND TABLE_SCHEMA = SchemaName)
BEGIN
ALTER TABLE TableName DROP COLUMN ColumnName;
END;
Perhaps the simplest way to solve this (that will work) is:
CREATE new_table AS SELECT id, col1, col2, ... (only the columns you actually want in the final table)
FROM my_table;
RENAME my_table TO old_table, new_table TO my_table;
DROP old_table;
Or keep old_table for a rollback if needed.
This will work but foreign keys will not be moved. You would have to re-add them to my_table later; also foreign keys in other tables that reference my_table will have to be fixed (pointed to the new my_table).
Good Luck...
I realise this thread is quite old now, but I was having the same problem.
This was my very basic solution using the MySQL Workbench, but it worked fine...
get a new sql editor and execute SHOW TABLES to get a list of your tables
select all of the rows, and choose copy to clipboard (unquoted) from the context menu
paste the list of names into another editor tab
write your query, ie ALTER TABLE x DROP a;
do some copying and pasting, so you end up with separate query for each table
Toggle whether the workbench should stop when an error occurs
Hit execute and look through the output log
any tables which had the table now haven't
any tables which didn't will have shown an error in the logs
then you can find/replace 'drop a' change it to 'ADD COLUMN b INT NULL' etc and run the whole thing again....
a bit clunky, but at last you get the end result and you can control/monitor the whole process and remember to save you sql scripts in case you need them again.