Convert MySQL to postgres and retain default value - mysql

I have a MySQL database that I wish to convert into Postgres. One issue I encountered is to convert tinyint(1) (synonym to boolean) columns into "true" boolean and retain the default value of the MySQL column which can be either 0 or 1 but in Postgres the respective values are true or false. The SQL I'm trying:
ALTER TABLE "payments" ALTER COLUMN "is_automatic" TYPE boolean USING CAST("is_automatic" as boolean);
The error message:
ERROR: default for column "is_automatic" cannot be cast automatically to type boolean
I would think it would be possible to cast this value somehow. Is this possible to do or do I have to manually add this to the migration script?
Edit: I realise I might have explained the issue a bit vaguely, sorry about that. I am using this script (https://github.com/lanyrd/mysql-postgresql-converter) to convert the MySQL database. The values are converted into "true" postgres boolean using this script just fine but the columns themselves that where originally booleans in MySQL (represented by tinyint(1)) gets their default value dropped. This happens on row 157 in the script and removing the "DROP DEFAULT" part of the command generates the error above, because it can't be casted (I guess). My question is better asked this way: In the process of converting a tinyint(1) column, can the default value be "remembered" and later applied again with a "SET DEFAULT" command?

The postgresql ALTER TABLE reference page has an example exactly covering this scenario:
.. when the column has a default expression that won't automatically
cast to the new data type:
ALTER TABLE foo
ALTER COLUMN foo_timestamp DROP DEFAULT,
ALTER COLUMN foo_timestamp TYPE timestamp with time zone
USING
timestamp with time zone 'epoch' + foo_timestamp * interval '1 second',
ALTER COLUMN foo_timestamp SET DEFAULT now();
So, you need to drop the old default, alter the type, then add the new default.
Note that the USING expression does not have any bearing on the default. It is purely used to convert existing values in the table. But in any case, there is no direct cast between integer and boolean, so you need a slightly more advanced USING expression.
Your statement might look like this:
ALTER TABLE payments
ALTER COLUMN is_automatic DROP DEFAULT,
ALTER COLUMN is_automatic TYPE BOOLEAN
USING is_automatic!=0,
ALTER COLUMN is_automatic SET DEFAULT TRUE;
The using expression might need a little tweaking, I am assuming here that your existing data has a value of 0 for false and something else for true.

Related

mysql - Invalid date error when creating stored generated column

I am trying to add a generated column for an existing table.
Within the table, there is a varchar column containing data like 321njkfvds_10911342
If I add the generated column as VIRTUAL, it works well!.
ALTER TABLE my_table
ADD COLUMN `PartitionKey` INT
GENERATED ALWAYS AS (IFNULL(TO_DAYS(SUBSTRING_INDEX(`Sequence`, '_', -1)), 0)) VIRTUAL
AFTER `Sequence`;
But if I try to add it as STORED generated column, it fails.
ALTER TABLE my_table
ADD COLUMN `PartitionKey` INT
GENERATED ALWAYS AS (IFNULL(TO_DAYS(SUBSTRING_INDEX(`Sequence`, '_', -1)), 0)) STORED
AFTER `Sequence`;
Error Code: 1292
Incorrect datetime value: '10911342'
I know 10911342 is not a valid date, but at least its generated column is 0 when VIRTUAL specified.
But why can't I add the generated column as STORED while VIRTUAL works? is there some way to fix it?
##version
---------------------
10.3.27-MariaDB-log
Generated (Virtual and Persistent/Stored) Columns :: Making Stored
Values Consistent
...
When a generated column is PERSISTENT or indexed, the value of the expression needs to be consistent regardless of the SQL Mode flags in the current session. If it is not, then the table will be seen as corrupted when the value that should actually be returned by the computed expression and the value that was previously stored and/or indexed using a different sql_mode setting disagree.
...
See dbfiddle.

Mysql set default value to a json type column

I heard that mysql version prior to 8.0.13 accept default value for json type column, so I using the cmd:
ALTER TABLE templates CHANGE COLUMN values JSON NOT NULL DEFAULT '{}' ;
but receive error:
Error Code: 1101. BLOB, TEXT, GEOMETRY or JSON column 'values' can't have a default value
So how do I fix it?
I'm using mysql version 8.0.19 and client tool Workbench
From version 8.0.13 onwards, the documentation says (emphasis is mine):
The BLOB, TEXT, GEOMETRY, and JSON data types can be assigned a default value only if the value is written as an expression, even if the expression value is a literal.
You can make your default an expression by surrounding the literal value with parentheses:
ALTER TABLE templates CHANGE COLUMN values JSON NOT NULL DEFAULT ('{}') ;
Or:
ALTER TABLE templates CHANGE COLUMN values JSON NOT NULL DEFAULT (JSON_OBJECT()) ;
Prior to version 8.0.13 of MySQL, it was not possible to set a default value on a JSON column, as the 8.0 documentation points out a few paragraphs later :
The BLOB, TEXT, GEOMETRY, and JSON data types cannot be assigned a default value.
According to the laravel docs:
$table->json('movies')->default(new Expression('(JSON_ARRAY())'));
The default modifier accepts a value or an Illuminate\Database\Query\Expression instance. Using an Expression instance will prevent Laravel from wrapping the value in quotes and allow you to use database specific functions. One situation where this is particularly useful is when you need to assign default values to JSON columns
https://laravel.com/docs/8.x/migrations#default-expressions
MySql syntax is a bit different than Oracle/Postgres, hence to make say JSON_Array as default, the query would be -
ALTER TABLE table_name ALTER column_name SET DEFAULT (JSON_ARRAY());
Further reference here

Is it common to bind default values in 'CREATE TABLE' statements?

Should I exec directly (pseudo code)...
q = "CREATE TABLE `usermood` { `id` INT, `name` TEXT, `mood` VARCHAR DEFAULT 'gloomy' }";
exec(q);
...or bind to a (un)named placeholder?
q = "CREATE TABLE `usermood` { `id` INT, `name` TEXT, `mood` VARCHAR DEFAULT :mood }";
prepare(q);
bind(q, ":mood", 'gloomy');
exec(q);
I've never seen it in any example code.
It's less about the security of escaping (because I control the create statements) but rather about converting the value into a database compatible format (automatic selection of content representation by type).
I'm using MySQL as well as SQLite3.
Are there database drivers that don't support binding in create statements?
If anyone is interested: I'm using QSqlQuery with QVariant as value.
You would use parameter binding when:
You are using a value from an unknown source, and you want to protect against SQL injection.
You want to prepare a statement and execute it repeatedly using different values for the parameter.
Neither of these is likely for your CREATE TABLE example.
I have never used a parameter in any DDL statement.
P.S.: You can't set a DEFAULT for a TEXT column regardless of whether it's a bound parameter or a literal value in the DDL statement, but I'm guessing your example above is artificial.
SQLite explicitly forbids binding default values:
from the SQLite docs:
An explicit DEFAULT clause may specify that the default value is NULL, a string constant, a blob constant, a signed-number, or any constant expression enclosed in parentheses. A default value may also be one of the special case-independent keywords CURRENT_TIME, CURRENT_DATE or CURRENT_TIMESTAMP. For the purposes of the DEFAULT clause, an expression is considered constant if it does contains no sub-queries, column or table references, bound parameters, or string literals enclosed in double-quotes instead of single-quotes.
(emphasis by me)

phpmyadmin mySQL create column with value "AS" not happening

need:
to enter integer IP value in ip_n and use internal INET_NTOA() function of mysql to fill in ip_a column. but using phpMyAdmin, i am not even able to create the table correctly.
I did this:
but it is wrong: for IP: 192.168.1.1
the second column is:
inspiration comes from here: http://databaseblog.myname.nl/2011/07/working-with-ips-in-mysql-and-mariadb.html
You're trying to declare a default value for a column based on the contents of another column.
Unfortunately, You Can't Do That.®
It appears that the column is being given a DEFAULT value of a string literal. Perfectly valid to assign a string literal, in this case, 'INET_NTOA(ip_n)'. That may look like a reference to a MySQL function, but MySQL is seeing that as a literal.
If we ran an INSERT statement and allowed MySQL to assign the default value, we'd expect that exact literal to be stored in the column; and that's exactly what we'd expect to be returned in a SELECT.
(The behavior described as observed by OP.)
Note that MySQL only allows a literal value to be specified as the DEFAULT value for a VARCHAR column. Actually, this is true for all datatypes, with the exception of TIMESTAMP, which allows for CURRENT_TIMESTAMP as a default. (More recent releases support a similar type of default for DATETIME as well.) But for a VARCHAR column, default is either NULL or a literal.
To get a value automatically assigned to the column, you could use MySQL BEFORE INSERT trigger (and a BEFORE UPDATE trigger)
Something like this perhaps:
DELIMITER $$
CREATE TRIGGER mytable_bi
BEFORE INSERT ON mytable
FOR EACH ROW
BEGIN
SET NEW.ip_a = INET_NTOA(NEW.ip_n);
END$$
DELIMITER ;
If you want to keep the values "in sync", you'd want a corresponding BEFORE UPDATE trigger, in the event that the value of ip_n is changed by an UPDATE statement.

Converting column type in MySQL

I had to bring in a whole bunch of tables from CSV files. A lot of these files had column that were INT but had null values. To speed up the import I just made all of the column VARCHAR. Now I have all this data in the tables but need to change the type. I'm able to do this in the MySQL workbench except for one problem -- It error's because of the null/blank values. Is there some sort of SQL magic that will allow me to convert these column types and ignore the nulls or replace them with the correct 'null value' for that data type?
You can update the columns to set blank fields as NULL as follows:
UPDATE mytable SET mycolumn=NULL WHERE TRIM(mycolumn,' ')='';
Then do your normal table alters as follows:
ALTER TABLE mytable MODIFY mycolumn VARCHAR(255);
The 'DEFAULT NULL' is optional as fields by default allow null. This should allow you to convert the columns to whatever data types you wish without any problem except in the case where there is mixed data -- such as numbers, and strings, and you wish to make that column FLOAT.
The above example also does not take into account removing carriage returns, etc, in the event that a column contains a "\n" or "\r\n" and nothing else, it will not set it to NULL, but you can modify the "TRIM(mycolumn, ' ')" to meet those requirements if you have them : aka ...
UPDATE mytable SET mycolumn=NULL WHERE TRIM(mycolumn,"\n")='';