DEV Community

Tobias Haindl
Tobias Haindl

Posted on

Intro to Postgres enums

In most programming languages the use of enums is pretty common and heavily relied upon by developers. It took me quite some time to learn that this feature exists in the database realm as well.
Therefore, I will provide a short introductory summary of enumerated types in Postgres.

Enums are used for representing a limited number of possible values.
Status fields are a good example.
Let’s say we want to store account information inside a database table:

In our example an account can have one of the following status:

  • ACTIVE
  • DELETED
  • BANNED

We can create a database enum by executing:

CREATE TYPE ACCOUNT_STATUS AS ENUM ( 'ACTIVE', 'DELETED', 'BANNED');
Enter fullscreen mode Exit fullscreen mode

We can then use the generated enum in our table definition:

CREATE TABLE ACCOUNT (STATUS ACCOUNT_STATUS, USERNAME TEXT, EMAIL TEXT);
Enter fullscreen mode Exit fullscreen mode

Now we insert a new account:

INSERT INTO ACCOUNT VALUES ('ACTIVE', 'testuser','user@test.com');
Enter fullscreen mode Exit fullscreen mode

Since we set the datatype of the STATUS table to ACCOUNT_STATUS the database now takes care of validation.

If we try to insert an account with status INACTIVE

INSERT INTO ACCOUNT VALUES ('INACTIVE', 'testuser', 'user@test.com');
Enter fullscreen mode Exit fullscreen mode

The database returns the following error:

ERROR: invalid input value for enum account_status: "INACTIVE"
Enter fullscreen mode Exit fullscreen mode

Since "INACTIVE" is not valid for our defined enum.

Having a data validation mechanism like this on database level helps to enforce data integrity at all times.

Querying enums

Unfortunately the values of enum types can not be retrieved by the following query:

SELECT * FROM ACCOUNT_STATUS
Enter fullscreen mode Exit fullscreen mode

But one can make use of the built-in functions UNNEST and ENUM_RANGE

SELECT UNNEST(ENUM_RANGE(NULL::ACCOUNT_STATUS))
Enter fullscreen mode Exit fullscreen mode

This query returns one row for each enum value.
In our example:

  • ACTIVE
  • INACTIVE
  • BANNED

Alternatives to enums: check constraints

We can add a similar data validation mechanism to our database tables by adding a check constraint.
In our example the constraint would look like this:

CREATE TABLE ACCOUNT (STATUS TEXT CHECK (STATUS IN ('ACTIVE', 'INACTIVE', 'BANNED')), USERNAME TEXT, EMAIL TEXT);
Enter fullscreen mode Exit fullscreen mode

After defining the column name and type we add the constraint by using the CHECK keyword.
In the parenthesis following CHECK we define a boolean expression which must be true for every row of the table.
For better maintainability we can also give the check constraint a name:

CREATE TABLE ACCOUNT (STATUS TEXT CONSTRAINT ACCOUNT_STATUS_CONSTRAINT CHECK (STATUS IN ('ACTIVE', 'INACTIVE', 'BANNED')), USERNAME TEXT, EMAIL TEXT);
Enter fullscreen mode Exit fullscreen mode

Enum vs Check constraint

Advantages of enums

Once defined enums can be reused in different columns or tables.
Additionally, the usage of enums helps with understanding the database schema.
Because creation of a specific type conveys more purpose: e.g. column of type ACCOUNT_STATUS is more specific than a column of type TEXT.

Disadvantages of enums

With enums we defined a new data type for our column (note the CREATE TYPE syntax above).
Therefore, the built-in string operators and functions do not work on enums.
With a check constraint do not have to define our own data type, so any built-in operator and function for the used column data type can be used.

Like in other parts of software development, designing a database schema involves tradeoffs.
So it is quite handy to have different tools in your tool belt!

If you like this summary, feel free to follow me on Twitter or here for more content.

Sources:
Postgres docs

Top comments (0)