Generated Columns in PostgreSQL

Learn how to use PostgreSQL GENERATED ALWAYS AS ... STORED to create columns whose values are automatically computed from an expression on other columns.

4 min read · PostgreSQL 12+ · Back to overview

Quick Answer

A PostgreSQL generated column has its value automatically calculated from an expression involving other columns. Declare it with GENERATED ALWAYS AS (expression) STORED. The value is recomputed and stored on every INSERT or UPDATE. You cannot write to a generated column directly.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

A generated column in PostgreSQL is a column whose value is automatically computed from an expression based on other columns in the same row. This keeps derived values consistent without requiring application logic or triggers. PostgreSQL currently supports only stored generated columns — the computed value is written to disk on each INSERT or UPDATE, analogous to a materialized view at the row level.

Syntax

Declare a generated column in CREATE TABLE:

CREATE TABLE table_name (
  ...
  column_name type GENERATED ALWAYS AS (expression) STORED,
  ...
);

Add one to an existing table:

ALTER TABLE table_name
  ADD COLUMN column_name type GENERATED ALWAYS AS (expression) STORED;

Expression requirements:

  • Must use only immutable functions (no now(), random(), or subqueries).
  • Cannot reference other generated columns.
  • Cannot have a default value or an identity definition.
  • Cannot be part of a partition key.

Practical Example

Create an order_lines table where the line total and a discounted price are computed automatically:

CREATE TABLE order_lines (
  line_id       SERIAL         PRIMARY KEY,
  order_id      INT            NOT NULL,
  product_name  VARCHAR(200)   NOT NULL,
  quantity      INT            NOT NULL,
  unit_price    NUMERIC(10, 2) NOT NULL,
  discount_pct  NUMERIC(5, 2)  NOT NULL DEFAULT 0,
  line_total    NUMERIC(12, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
  net_total     NUMERIC(12, 2) GENERATED ALWAYS AS (
                  quantity * unit_price * (1 - discount_pct / 100)
                ) STORED
);

Insert rows — line_total and net_total are computed automatically:

INSERT INTO order_lines (order_id, product_name, quantity, unit_price, discount_pct)
VALUES
  (101, 'Wireless Keyboard', 3, 49.99, 10.00),
  (101, 'USB Hub',           2, 29.99,  0.00)
RETURNING *;
 line_id | order_id | product_name      | qty | unit_price | discount_pct | line_total | net_total
---------+----------+-------------------+-----+------------+--------------+------------+-----------
       1 |      101 | Wireless Keyboard |   3 |      49.99 |        10.00 |     149.97 |    134.97
       2 |      101 | USB Hub           |   2 |      29.99 |         0.00 |      59.98 |     59.98

Verify the generated column definition using the system catalog:

SELECT attname, attgenerated
FROM pg_attribute
WHERE attrelid = 'order_lines'::regclass
  AND attgenerated = 's';

Add an index on a generated column for fast filtering:

CREATE INDEX idx_order_lines_net_total ON order_lines (net_total);

Trying to update a generated column raises an error:

UPDATE order_lines SET line_total = 0 WHERE line_id = 1;
-- ERROR: column "line_total" can only be updated to DEFAULT

Testing with Vela

Adding a generated column to a large table requires a full table rewrite. Before applying this on production, use Vela database branching to clone the database, run the ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS ... STORED statement, measure how long the rewrite takes, and validate the expression output against known values — all without touching the live system.

Production Tips

  • Generated columns eliminate the need for triggers or application code to keep derived values in sync.
  • Adding a generated column to a populated table triggers a full rewrite — plan it as a maintenance operation on large tables.
  • You can index a generated column to speed up queries filtering or sorting on computed values.
  • Only immutable expressions are permitted; for values dependent on the current time or session state, use a trigger instead.
  • You cannot reference a generated column from another generated column’s expression in the same table.
  • Generated column values are stored and counted toward row size limits, so avoid very wide expressions on wide tables.

Continue in Managing Tables: Alter Table.

Related in this section: PostgreSQL Data Types · Create Table · Select Into

Frequently Asked Questions

What is a generated column in PostgreSQL?
A generated column is one whose value is automatically computed from an expression over other columns in the same row. It is declared with GENERATED ALWAYS AS (expression) STORED and is updated on every INSERT and UPDATE. You cannot write a value directly to a generated column.
Does adding a generated column lock the table and block queries?
Adding a generated column with ALTER TABLE requires a full table rewrite in PostgreSQL because all existing rows must have the expression evaluated and the result stored. This takes an ACCESS EXCLUSIVE lock and can be slow on large tables. Plan it during a maintenance window.
What happens to dependent objects when a generated column is dropped?
Dropping a generated column removes the column, its computed values, and any indexes on it. If a view references the generated column, you must drop the view first or use CASCADE.
Can I create an index on a generated column?
Yes. Because generated column values are physically stored, you can create any index type (B-tree, GIN, etc.) on them, just like any other column. This is one of the main benefits over using a trigger.
What is the safest way to add a generated column to a large production table?
Test the operation on a Vela branch with production-sized data first to estimate how long the table rewrite takes. Schedule it during a maintenance window or use pg_repack if zero-downtime is required. Also verify the expression is immutable before applying.