I'm using Firebird 4.0 and I would convert a column from smallint 0|1 to boolean.
So I have this kind of domain:
CREATE DOMAIN D_BOOL
AS SMALLINT
DEFAULT 0
NOT NULL
CHECK (VALUE IN (0,1))
;
This domain is used in my test table:
CREATE TABLE TBOOL
(
ID INTEGER,
INTVAL D_BOOL
);
How can I convert the column INTVAL to BOOLEAN?
I tried this query but I got an error:
alter table tbool
alter column INTVAL TYPE BOOLEAN,
alter column INTVAL SET DEFAULT FALSE
Error:
Error: *** IBPP::SQLException ***
Context: Statement::Execute( alter table tbool
alter column INTVAL TYPE BOOLEAN,
alter column INTVAL SET DEFAULT FALSE )
Message: isc_dsql_execute2 failed
SQL Message : -607
This operation is not defined for system tables.
Engine Code : 335544351
Engine Message :
unsuccessful metadata update
ALTER TABLE TBOOL failed
MODIFY RDB$RELATION_FIELDS failed
Unfortunately, this is an incompatible column change, because there is no conversion defined from SMALLINT to BOOLEAN. Altering the type of a column only works for a limited combination of types (and there is no combination that allows modification to or from BOOLEAN).
The only real option is to add a new column, populate it based on the value of the old column, drop the old column and rename the new column. This can have a huge impact if this column is used in triggers, procedures and/or views.
Your options are basically:
Keep existing columns as-is, and only use BOOLEAN moving forward for new columns
Do a very invasive change to change all your columns.
If you have a lot of columns that need to change, this is likely easier to do by creating a new database from scratch and pumping the data over, than by changing the database in-place.
The background of this limitation is that Firebird doesn't actually modify existing values when changing the type of a column. Instead, it will convert values on the fly when reading rows created with an older "format version" (inserts and updates will write the new "format version").
This makes for fast DDL, but all conversions must be known to succeed. This basically means only "widening" conversions between similar types are allowed (e.g. longer (VAR)CHAR, longer integer types, etc).
Related
Using EF-Core for PostgresSQL, I have an entity with a field of type byte but decided to change it to type byte[]. But when I do migrations, on applying the migration file generated, it threw the following exception:
Npgsql.PostgresException (0x80004005): 42804: column "Logo" cannot be
cast automatically to type bytea
I have searched the internet for a solution but all I saw were similar problems with other datatypes and not byte array. Please help.
The error says exactly what is happening... In some cases PostgreSQL allows for column type changes (e.g. int -> bigint), but in many cases where such a change is non-trivial or potentially destructive, it refuses to do so automatically. In this specific case, this happens because Npgsql maps your CLR byte field as PostgreSQL smallint (a 2-byte field), since PostgreSQL lacks a 1-byte data field. So PostgreSQL refuses to cast from smallint to bytea, which makes sense.
However, you can still do a migration by writing the data conversion yourself, from smallint to bytea. To do so, edit the generated migration, find the ALTER COLUMN ... ALTER TYPE statement and add a USING clause. As the PostgreSQL docs say, this allows you to provide the new value for the column based on the existing column (or even other columns). Specifically for converting an int (or smallint) to a bytea, use the following:
ALTER TABLE tab ALTER COLUMN col TYPE BYTEA USING set_bytea(E'0', 0, col);
If your existing column happens to contain more than a single byte (should not be an issue for you), it should get truncated. Obviously test the data coming out of this carefully.
Since Firebird 3, I can't modify a column type.
Before I use this kind of update:
update RDB$RELATION_FIELDS set
RDB$FIELD_SOURCE = 'MYTEXT'
where (RDB$FIELD_NAME = 'JXML') and
(RDB$RELATION_NAME = 'XMLTABLE')
because I get ISC error 335545030 ("UPDATE operation is not allowed for system table RDB$RELATION_FIELDS").
Maybe there is another way in Firebird 3?
Firebird 3 no longer allows direct updates to the system tables, as that was a way to potentially corrupt a database. See also System Tables are Now Read-only in the release notes. You will need to use DDL statements to do the modification.
It looks like you want to change the data type of a column to a domain. You will need to use alter table ... alter column ... for that. Specifically you will need to do:
alter table XMLTABLE
alter column JXML type MYTEXT;
This does come with some restrictions:
Changing the Data Type of a Column: the TYPE Keyword
The keyword TYPE changes the data type of an existing column to
another, allowable type. A type change that might result in data loss
will be disallowed. As an example, the number of characters in the new
type for a CHAR or VARCHAR column cannot be smaller than the existing
specification for it.
If the column was declared as an array, no change to its type or its
number of dimensions is permitted.
The data type of a column that is involved in a foreign key, primary
key or unique constraint cannot be changed at all.
This statement has been available since before Firebird 1 (InterBase 6.0).
Firebird 2.5 manual, chapter Data Definition (DDL) Statement, section TABLE:
ALTER TABLE tabname ALTER COLUMN colname TYPE typename
In my database I have several fields with INTEGER Type. I need to change some of them to BIGINT.
So my question is, can I just use the following command?
ALTER TABLE MyTable ALTER COLUMN MyIntegerColumn TYPE BIGINT;
Are the contained data be converted the correct way? After the convert is this column a "real" BIGINT column?
I know this is not possible if there are constraints on this column (Trigger, ForeingKey,...). But if there are no constraints is it possible to do it this way?
Or is it better to convert it by a Help-Column:
MyIntegerColumn -> MyIntegerColumnBac -> MyBigIntColumn
When you execute
ALTER TABLE MyTable ALTER COLUMN MyIntegerColumn TYPE BIGINT;
Firebird will not convert existing data from INTEGER to BIGINT, instead it will create a new format version for the table.
When inserting new rows or updating existing rows, the value will be stored as a BIGINT, but when reading Firebird will convert 'old' rows on the fly from INTEGER to BIGINT. This happens transparently for you as the user. This is to prevent needing to rewrite all existing rows, which could be costly (IO, garbage collection of old versions of rows, etc).
So please, do use ALTER TABLE .. ALTER COLUMN, do not do MyIntegerColumn -> MyIntegerColumnBac -> MyBigIntColumn. There are some exceptions to this rule, eg (potentially) lossy character set transformations are better done that way to prevent transliterations errors on select if a character does not exist in the new character set, or changing a (var)char column to be shorter (which can't be done with alter column).
To be a little more specific: when a row is written in the database it contains a format version (aka version count) of that row. The format version points to a description of a row (datatypes, etc) how Firebird should read that row. An alter table will create a new format version, and that format will be applied when writing new rows or updating existing rows. When reading an old row, Firebird will apply necessary transformation to present that row as the new format (for example adding new columns with their default values, transforming a data type of a column).
These format versions are also a reason why the number of alter tables are restricted: if you apply more than 255 alter tables on a single table you must backup and restore the database (the format version is a single byte) before further changes are allowed to that table.
I've recently been optimizing some of our Postgres tables by converting more complex data types to simpler ones where possible. In every case except one so far, this has been fairly straightforward, for instance:
ALTER TABLE products ALTER COLUMN price TYPE integer USING price::integer;
For converting text into custom enumerated data types, this has also been simple enough. I just wrote a PLPGSQL function that would convert text to the enum, then converted the column like so:
ALTER TABLE products ALTER COLUMN color TYPE color_enum USING text_to_color_enum(color);
This syntax fails though, in cases where I have to convert an integer to a boolean. These all fail:
ALTER TABLE products ALTER return_policy TYPE boolean USING return_policy > 0;
ALTER TABLE products ALTER return_policy TYPE boolean USING bool(return_policy);
ALTER TABLE products ALTER COLUMN return_policy TYPE boolean USING bool(return_policy);
ALTER TABLE products ALTER COLUMN return_policy TYPE boolean USING CASE WHEN return_policy <> 0 THEN TRUE ELSE FALSE END;
The error message is always the same:
ERROR: operator does not exist: boolean = integer
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
There are no null values in that column. All values are either zero or positive. SELECT pg_typeof(return_policy) FROM products LIMIT 1; returns integer. Creating a custom cast from integer to boolean fails, because apparently one already exists. The same thing happen in Postgres 9.4 and 9.5. What am I doing wrong here?
Verify if the column is a constraint, if yes you need to remove the constraint before change the type.
ALTER TABLE products ALTER COLUMN price DROP DEFAULT;
ALTER TABLE products ALTER price TYPE bool USING CASE WHEN price = 0 THEN FALSE ELSE TRUE END;
ALTER TABLE products ALTER COLUMN price SET DEFAULT FALSE;
One of my partial indexes had a condition WHERE return_policy = 30. (This number is meant to be the number of days the return policy is, but since we're giving everything either no return policy or a 30-day return policy, it doesn't make sense for it to be an int anymore.) Dropping the index allowed my original SQL code to run correctly.
I am using following query:
ALTER TABLE presales ALTER COLUMN code TYPE numeric(10,0);
to change the datatype of a column from character(20) to numeric(10,0) but I am getting the error:
column "code" cannot be cast to type numeric
You can try using USING:
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
So this might work (depending on your data):
alter table presales alter column code type numeric(10,0) using code::numeric;
-- Or if you prefer standard casting...
alter table presales alter column code type numeric(10,0) using cast(code as numeric);
This will fail if you have anything in code that cannot be cast to numeric; if the USING fails, you'll have to clean up the non-numeric data by hand before changing the column type.
If your VARCHAR column contains empty strings (which are not the same as NULL for PostgreSQL as you might recall) you will have to use something in the line of the following to set a default:
ALTER TABLE presales ALTER COLUMN code TYPE NUMERIC(10,0)
USING COALESCE(NULLIF(code, '')::NUMERIC, 0);
(found with the help of this answer)
Step 1: Add new column with integer or numeric as per your requirement
Step 2: Populate data from varchar column to numeric column
Step 3: drop varchar column
Step 4: change new numeric column name as per old varchar column