Is it possible to cast from one enum to another in PostgreSQL - postgresql

I need to populate a new table in a second schema from an existing one, but having problems casting the "schema1.a.disclosure_level" column enum to the "schema2.b.disclosure_level" enum. A cast via ::text or :: varchar did not help. Casting to ::schema1.a.disclosure_level raises a cross-database reference error.
INSERT INTO schema1.a (id, disclosure_level)
SELECT schema2.b.id, schema2.b.disclosure_level
FROM schema2.b;
Any ideas?

#Bergi showed me the solution.
INSERT INTO schema1.a (id, disclosure_level)
SELECT schema2.b.id, schema2.b.disclosure_level::text:schema1.disclosure_level_enum
FROM schema2.b;
where my fault was to use the column name instead of the enum type definition in the cast: schema1.disclosure_level_enum (type) instead of schema1.a.disclosure_level (column)!

From here:
https://www.postgresql.org/docs/current/datatype-enum.html
8.7.3. Type Safety
Each enumerated data type is separate and cannot be compared with other enumerated types.
Example:
CREATE TYPE animal AS ENUM ('dog', 'cat', 'rabbit');
CREATE TYPE animal_2 AS ENUM ('dog', 'cat', 'rabbit');
create table enum_test(id integer, a animal, a2 animal_2);
insert into enum_test values (1, 'dog', 'cat');
select a::animal from enum_test ;
a
-----
dog
select a::animal_2 from enum_test ;
ERROR: cannot cast type animal to animal_2
LINE 1: select a::animal_2 from enum_test ;
So the answer is no you can't cast one enum to another.

It can be possible if you cast to VARCHAR as first and second to another enum
"valueType" = NEW."valueType"::VARCHAR::"enum_second"

Related

Alter column type with non-trivial usage of "using" clause

Assume we have this table named "mytable":
name [varchar]
eating_habits [int]
Anna
1
Roland
3
Sepp
1
Katrin
2
Lukas
4
Hedwig
3
Now I realize I want to change the colum vegetarian to be specific. I create my own enum type:
CREATE TYPE diet AS ENUM ('vegetarian', 'vegan', 'omni');
What I want is to change the type of the column "eating_habits" to "diet". To do this I also want to map between the types like this
1 --> 'vegan'
2 --> 'vegetarian'
rest --> 'omni'
How would I go about this? I know that one can use the USING clause like this:
ALTER TABLE mytable
ALTER COLUMN eating_habits TYPE diet
USING (
<Expression>
)
But I can't figure out what "Expression" should be, and the vast majority of examples online are trivial casts.
You need a CASE expression:
ALTER TABLE mytable
ALTER COLUMN eating_habits TYPE diet
USING (
case eating_habits
when 1 then 'vegan'::diet
when 2 then 'vegetarian'::diet
else 'omni'::diet
end
)

liquibase cannot cast type enum to enum

I currently have an enum type in my DB created via liquibase with the following script:
- changeSet:
id: id_1
author: my_team
changes:
- sql: CREATE TYPE my_team.letters AS ENUM ('A', 'B', 'C')
As I need to add letter D to the enum, I create a new enum
- changeSet:
id: id_2
author: my_team
changes:
- sql: CREATE TYPE my_team.letters_2 AS ENUM ('A', 'B', 'C', 'D')
And I update the type
- changeSet:
id: id_3
author: my_team
changes:
- modifyDataType:
columnName: letter
newDataType: my_team.letters_2
schemaName: my_team
tableName: table_name
And I get the following error when executing the Liquibase scripts
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.MigrationFailedException: Migration failed for change set db/changelog/ddl-my_team-v.0.0.15.my_team::id_3::my_team team:
Reason: liquibase.exception.DatabaseException: ERROR: cannot cast type my_team.letters to my_team.letters_2
Position: 89 [Failed SQL: (0) ALTER TABLE my_team.table_name ALTER COLUMN case_status TYPE my_team.letters_2 USING (letter::my_team.letters_2)]
I can't understand why, since the destination type includes all the values of the original one.
Any way this can be done?
Thanks in advance,
I'm not a Postgres expert, but I think the following could do the trick:
Rename your column letter to letter_copy.
Create a new column letter of type letters_2.
Copy all values from letter_copy to letter. Perhaps you'll have to copy values from letter_copy as text like update table_name set letter = letter_copy::text::letters;.
Drop column letter_copy.
Now table_name.letter column should have a type of letters_2 enum with all the values converted from enum letters to enum letters_2.
There is no need to create another enum and jump through hoops to get everything straightened. Just alter the existing enum:
ALTER letter ADD VALUE 'D' after 'C';
This was failing for me
ALTER TYPE status_type ADD VALUE IF NOT EXISTS 'NEW_ENUM_VALUE';
and below worked
--liquibase formatted sql
ALTER TYPE status_type ADD VALUE IF NOT EXISTS 'NEW_ENUM_VALUE';
I think the reason for the above query to work is that liquibase does not support direct updation of enum types. In order to run query natively have to use --liquibase formatted sql
https://docs.liquibase.com/concepts/changelogs/sql-format.html

PostgreSQL fails on empty constructor arrays

I have encountered a strange situation with arrays, which looks like a bug in PostgreSQL, unless I'm missing something...
According to PostgreSQL documentation, constructor and string presentations of arrays are interchangeable, i.e. we can either write ARRAY[1,2,3] or '{1,2,3}', which is the same.
However, I have found one case when they are not treated the same.
I am using an automatic SQL generator for multi-row updates that spits out the following:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[]))
AS v("id", "data") WHERE t.id=v.id
The table is as follows:
CREATE TABLE myTable(
id serial PRIMARY KEY,
data int[] NULL
);
Executing that query produces error - cannot determine type of empty array, even though we are clearly casting the column type.
And if I replace array[] with the equivalent '{}', then it suddenly works.
I've never seen this happen before, this is first time, perhaps a unique situation, but from what I see, it goes against PostgreSQL documentation for interchangeable array presentation.
More examples, to explain the issue:
These work:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[1,2,3]))
AS v("id", "data") WHERE t.id=v.id
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, '{1,2,3}'))
AS v("id", "data") WHERE t.id=v.id
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, '{}'))
AS v("id", "data") WHERE t.id=v.id
This one doesn't work:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[]))
AS v("id", "data") WHERE t.id=v.id
So the type casting for array constructor works, for as long as the array isn't empty, just as it is empty, then the type casting stops working.
It is supported with type info:
pokus1=# select array[];
ERROR: cannot determine type of empty array
LINE 1: select array[];
^
HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[].
pokus1=# select array[]::integer[];
array
-------
{}
(1 row)

How to create a PostgreSQL column that is a set of enum values?

I like that I can create new enum types in PostgreSQL. But what if I want a column value that is a set of enum values. Do I need to implement that manually with an integer column type and bitwise operators, or is there a way to keep using the enums by name?
CREATE TYPE foo AS ENUM ('none', 'loud', 'bright', 'cheap')
CREATE TABLE t (
id serial,
properties [set of foo?]
)
...
SELECT * FROM t;
1 loud
2 loud, cheap
3 bright
4 none
...
You can use an array:
CREATE TYPE foo AS ENUM ('none', 'loud', 'bright', 'cheap');
CREATE TABLE t (
id serial,
properties foo[]
);

SQL query to get all values a enum can have

Postgresql got enum support some time ago.
CREATE TYPE myenum AS ENUM (
'value1',
'value2',
);
How do I get all values specified in the enum with a query?
If you want an array:
SELECT enum_range(NULL::myenum)
If you want a separate record for each item in the enum:
SELECT unnest(enum_range(NULL::myenum))
Additional Information
This solution works as expected even if your enum is not in the default schema. For example, replace myenum with myschema.myenum.
The data type of the returned records in the above query will be myenum. Depending on what you are doing, you may need to cast to text. e.g.
SELECT unnest(enum_range(NULL::myenum))::text
If you want to specify the column name, you can append AS my_col_name.
Credit to Justin Ohms for pointing out some additional tips, which I incorporated into my answer.
Try:
SELECT e.enumlabel
FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'myenum'
SELECT unnest(enum_range(NULL::your_enum))::text AS your_column
This will return a single column result set of the contents of the enum "your_enum" with a column named "your_column" of type text.
You can get all the enum values for an enum using the following query. The query lets you pick which namespace the enum lives in too (which is required if the enum is defined in multiple namespaces; otherwise you can omit that part of the query).
SELECT enumlabel
FROM pg_enum
WHERE enumtypid=(SELECT typelem
FROM pg_type
WHERE typname='_myenum' AND
typnamespace=(SELECT oid
FROM pg_namespace
WHERE nspname='myschema'))