5

I want to create an enum from distinct values of a column in PostgreSQL. Instead of creating an enum from all labels, I want to create it using a query to get all possible values of the type. I am expecting something like:

CREATE TYPE genre_type AS ENUM
   (select distinct genre from movies);

But I am not allowed to do this. Is there any way to achieve this?

2
  • 4
    Do you really need an enum for this? Wouldn't a simple FK to a genres table work just as well? Commented May 12, 2013 at 18:47
  • 2
    @muistooshort It could be that the OP wants to have a constant enum, irrelevant of changes made into the table. FK is a good solution in most, but not all times. Commented May 12, 2013 at 19:17

4 Answers 4

9

If genre types are in any way dynamic, i.e. you create new ones and rename old ones from time to time, or if you want to save additional information with every genre type, @mu's advice and @Marcello's implementation would be worth considering - except you should just use type text instead of varchar(20) and consider ON UPDATE CASCADE for the FK constraint.

Other than that, here is the recipe you asked for:

DO
$$
BEGIN
   EXECUTE (
   SELECT format('CREATE TYPE genre_type AS ENUM (%s)'
               , string_agg(DISTINCT quote_literal(genre), ', '))
   FROM   movies
   );
END 
$$;

You need dynamic SQL for that. The simple way is a DO command (PostgreSQL 9.0+).
Make sure your strings are properly escaped with quote_literal().
I aggregate the string with string_agg() (PostgreSQL 9.0+).

Sign up to request clarification or add additional context in comments.

Comments

2

I think you can't do that by design.

http://www.postgresql.org/docs/9.1/static/datatype-enum.html

Enumerated (enum) types are data types that comprise a static, ordered set of values.

The "DISTINCT" keyword in your SELECT clause makes me think your schema is not fully normalized.

For example:

CREATE TABLE movies(
    ...
    genre VARCHAR(20)
    ...
);

SELECT DISTINCT genre FROM movies;

Should become:

CREATE TABLE genres(
    id SERIAL PRIMARY KEY,
    name VARCHAR(20)
);

CREATE TABLE movies (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200),
    genre INTEGER REFERENCES genres(id)
);

SELECT name FROM genres;

Comments

1

A trivial approach would be to execute the SELECT in a client and then copy the names from it.

I think that is about everything you can do. If you look at the documentation http://www.postgresql.org/docs/9.3/static/sql-createtype.html you will notice that there is written CREATE TYPE name AS ENUM ( [ 'label' [, ... ] ] ) so what is not there is the keyword expression. That means that after ENUM there may not be an expression but only a list of labels.

You may want to follow @muistooshort's advice and create a genres table (that you can fill with an INSERT ... SELECT ...) and then create a foreign key to that table.

Comments

1

It can be done in pgsql. Here's inline code to do it:

DO $$
  DECLARE temp text;
BEGIN
    SELECT INTO temp string_agg(DISTINCT quote_literal(genre),',') FROM movies;
    EXECUTE 'CREATE TYPE foo AS ENUM ('||temp||')';
END$$;

fiddle

2 Comments

DISTINCT outside of string_agg() is probably a typo?
@ErwinBrandstetter A little more than a typo, I forgot about it and then just put it somewhere when I saw op used it. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.