When you have a series of applications all running the same database structure it can be annoying to roll out schema updates across all the databases. If you’ve got migrations then great - script their deployment, but when you’re dealing with an old legacy application you probably don’t have the luxury.

I was firmly in the latter class of devops when working on a project a couple of years ago so I wrote a handy little snippet of SQL to help me out. It allowed me to automatically assemble an ALTER TABLE query for MySQL - it’s gonna be a hack in case you’d not already guessed.

It is MySQL specific so to make it portable across MySQL installations I have used backticks to escape identifiers rather than the double quotes I advocate in the SQL style guide. tl;dr: double quotes as escape is not enabled in MySQL by default.

This query hinges on the information_schema tables and the GROUP_CONCAT functionality provided in MySQL, which will allow us to create one big query file in tandem with OUTFILE.

So without further ado here is the SQL code:

SET SESSION group_concat_max_len = 1000000;

SELECT `query`
  FROM (SELECT GROUP_CONCAT(
                   CONCAT('ALTER TABLE `', `isc`.`table_schema`, '`.`', `isc`.`table_name`, '` ALTER `', `isc`.`column_name`, '` DROP DEFAULT;', '\n'),
                   CONCAT('ALTER TABLE `', `isc`.`table_schema`, '`.`', `isc`.`table_name`, '` CHANGE COLUMN `', `isc`.`column_name`, '` `', `isc`.`column_name`, '` VARCHAR(300) NOT NULL;')
                   SEPARATOR '\n'
               ) AS `query`,
               1 AS `groupbyme`
          FROM `information_schema`.`columns` AS `isc`
         WHERE `isc`.`table_schema` NOT IN ('information_schema', 'mysql', 'performance_schema')
           AND `isc`.`table_name` = 'users'
           AND `isc`.`column_name` = 'ipAddress'
         GROUP BY `groupbyme`
       ) AS T
   INTO OUTFILE '/tmp/alter_table.sql'
       FIELDS TERMINATED BY '\n'
       OPTIONALLY ENCLOSED BY ''
       ESCAPED BY ''
       LINES TERMINATED BY '\n';

Great, you say, but what does it do? Well here is a brief breakdown of its constituent components.

SET SESSION group_concat_max_len = 1000000;

By default MySQL has a much smaller GROUP_CONCAT maximum length and as we could be operating on a lot of tables (meaning many results to concatenate) we need to up this default for our particular query.

SELECT `query`
  FROM (SELECT GROUP_CONCAT(
                   CONCAT('ALTER TABLE `', `isc`.`table_schema`, '`.`', `isc`.`table_name`, '` ALTER `', `isc`.`column_name`, '` DROP DEFAULT;', '\n'),
                   CONCAT('ALTER TABLE `', `isc`.`table_schema`, '`.`', `isc`.`table_name`, '` CHANGE COLUMN `', `isc`.`column_name`, '` `', `isc`.`column_name`, '` VARCHAR(300) NOT NULL;')
                   SEPARATOR '\n'
               ) AS `query`

Here GROUP_CONCAT concatenates the results of the queries together separated by a newline character \n. Inside there are two CONCAT statements that are building two ALTER TABLE queries. The first removes a DEFAULT declaration from a column and the second changes column to be a VARCHAR of 300 characters in length.

               1 AS `groupbyme`

This simply creates a column that whole lot can easily be grouped by as every row is ascribed the same value of 1.

         WHERE `isc`.`table_schema` NOT IN ('information_schema', 'mysql', 'performance_schema')
           AND `isc`.`table_name` = 'users'
           AND `isc`.`column_name` = 'ipAddress'

We don’t want to accidentally perform actions against the information_schema or other internal MySQL tables so we exclude them here. To ensure we only operate on the correct table we then specify its name and the relevant column name. This way we can be sure that the DB has our target table name in it and that the target table has our target column name in it.

         GROUP BY `groupbyme`

This refers to the simple column we setup earlier - it meant all rows had a value of 1 allowing the results to be easily grouped.

       ) AS T

After all that the result must be set against an alias so for no particular reason I chose T here.

   INTO OUTFILE '/tmp/alter_table.sql'
       FIELDS TERMINATED BY '\n'
       OPTIONALLY ENCLOSED BY ''
       ESCAPED BY ''
       LINES TERMINATED BY '\n';

Finally, the results are written to a file on disk with a few custom options. Importantly, the field are prevented from being enclosed or escaped - if these were left enabled then MySQL would break our query output by escaping new lines.

Now all you have to do is run contents of the file we just created and the ALTER TABLE queries will be executed against your database.