15 Arrays and ENUM
Two more PostgreSQL-specific types worth knowing early. Arrays let any column hold an ordered list of values of the same type — sometimes the right tool when a one-to-many relationship is so tightly scoped that a separate table is overkill (tags on a post, role names on a user). ENUM types give a column a closed set of named values, enforced by the database, and stored compactly. Both are non-standard but well-supported, and used widely in real-world Postgres apps.
What you’ll learn
- Declaring and querying array columns
- Array literals, indexing (1-based), and useful functions
- The
ANY/ALLoperators on arrays - Defining and using
ENUMtypes - When arrays/enums fit, and when a separate table is better
Example
Arrays in PostgreSQL are first-class: you can index them, use them in WHERE clauses, build them with ARRAY[], expand them to rows with unnest(), and query for containment with @>, <@, && (overlap). They are stored compactly inline (or in TOAST for very large ones).
The most important quirk: array indexes default to 1-based. tags[1] is the first element of the arrays here, though PostgreSQL also supports explicitly chosen lower bounds. The literal form '{a, b, c}' is a shorthand string-style literal; ARRAY['a', 'b', 'c'] is the constructor — both produce the same value.
ENUM types are excellent when you have a known small set of allowed values that should be enforced by the database ('open' | 'in_progress' | 'closed', 'small' | 'medium' | 'large'). They store as 4 bytes regardless of label length, and sort by declaration order rather than alphabetically — useful for status workflows where 'open' < 'in_progress' < 'closed' is the natural order.
Run the example
To run:
Common pitfalls
- 1-based indexing trips up developers from every other language.
tags[0]isNULL, not an error. - Modifying an ENUM (adding a value) requires
ALTER TYPE; removing a value is essentially impossible without recreating the type. For sets that change often, prefer a small lookup table joined by foreign key. - Arrays look enticing for “I just need a list” — but if you ever need to query for items, join, sort, or constrain individually, a separate table with foreign keys is almost always cleaner. Use arrays when the elements are part of the row, not entities of their own.
tag = 'x'does not work on an array column — use'x' = ANY(tag)ortag @> ARRAY['x'].
Index arrays with GIN: CREATE INDEX ON posts_example USING gin (tags); enables fast @>, <@, and && queries on the tags column. Without it, those queries scan the whole table.
Add a row with tags = ARRAY['intro','postgres','tips'] and run SELECT id, tag FROM posts_example, unnest(tags) AS tag; to expand each tag into its own row. Add a new ENUM value with ALTER TYPE ticket_status ADD VALUE 'reopened'; and insert a ticket with that status.
Downloads
SQL file: arrays-and-enum.sql · All lesson files · Setup guide