The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Protect an identity columnid bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEYView examples
Allow identity overridesid bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEYView examples
Configure the identity sequenceGENERATED ALWAYS AS IDENTITY (START WITH 1000 INCREMENT BY 1 CACHE 20 )View examples
Import explicit identity valuesINSERT INTO app.accounts OVERRIDING SYSTEM VALUE (id, name) VALUES (42, 'Ada');View examples
Regenerate imported identitiesINSERT INTO app.accounts OVERRIDING USER VALUE SELECT * FROM staging.accounts;View examples
Store a generated valuetotal numeric GENERATED ALWAYS AS (quantity * unit_price) STOREDView examples
Compute a virtual valuefull_name text GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUALView examples
Allocate a sequence valueSELECT nextval('app.ticket_number_seq'::regclass);View examples
Read this session's valueSELECT currval('app.ticket_number_seq'::regclass);View examples
Create an owned sequenceCREATE SEQUENCE app.ticket_number_seq AS bigint CACHE 20 OWNED BY app.tickets.number;View examples
Grant allocation accessGRANT USAGE ON SEQUENCE app.ticket_number_seq TO app_writer;View examples
Find an attached sequenceSELECT pg_get_serial_sequence('app.accounts', 'id');View examples
Plan a sequence repairSELECT setval(pg_get_serial_sequence('app.accounts','id'), max(id), true) FROM app.accounts;View examples
Inspect generated attributesSELECT column_name, is_identity, identity_generation, is_generated FROM information_schema.columns WHERE table_schema = 'app';View examples

Identity columns, generated columns, and sequences solve different automation problems. Identity columns attach a sequence-backed default to a column, generated columns derive a value from the current row, and standalone sequences allocate concurrent numbers independently of transactions. Choose by semantics, add explicit uniqueness where required, and treat overrides or sequence resets as controlled migration operations.

Step by step

Detailed examples

01

Use identity columns for sequence-backed table keys

GENERATED ALWAYS prevents accidental explicit values, while BY DEFAULT permits them and is useful only when overriding is part of the data contract. Identity implies NOT NULL but not uniqueness, so add PRIMARY KEY or UNIQUE separately. Partitions inherit the partitioned table's identity properties; ordinary inheritance does not copy them automatically.

Create a protected bigint identity
CREATE TABLE app.accounts (
  id bigint GENERATED ALWAYS AS IDENTITY
    (START WITH 1000 CACHE 20),
  email text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT accounts_pkey PRIMARY KEY (id),
  CONSTRAINT accounts_email_key UNIQUE (email)
);
Back to quick reference ↑
02

Make identity overrides visible in migration code

OVERRIDING SYSTEM VALUE is required to insert an explicit value into a GENERATED ALWAYS identity. OVERRIDING USER VALUE does the opposite: it ignores supplied identity values and generates replacements. Explicit imports do not automatically advance the underlying sequence, so reconcile it after loading and before concurrent writers resume.

Choose whether imported keys are retained
-- Preserve verified legacy keys.
INSERT INTO app.accounts OVERRIDING SYSTEM VALUE (id, email)
SELECT legacy_id, email FROM staging.accounts;

-- Or generate fresh destination keys.
INSERT INTO app.accounts OVERRIDING USER VALUE
SELECT * FROM staging.accounts;
Back to quick reference ↑
03

Derive same-row values with generated columns

A generated expression may use only immutable functions, cannot contain subqueries, and cannot reference another generated column. STORED values are computed on writes; in PostgreSQL 18, VIRTUAL values are computed on reads and are the default, but virtual expressions have additional restrictions on user-defined types and functions. Generated columns cannot also have a default or identity and cannot be written except with DEFAULT.

Persist a deterministic line total
CREATE TABLE app.invoice_lines (
  invoice_id bigint NOT NULL,
  quantity numeric NOT NULL CHECK (quantity > 0),
  unit_price numeric NOT NULL CHECK (unit_price >= 0),
  line_total numeric GENERATED ALWAYS AS (quantity * unit_price) STORED
);

INSERT INTO app.invoice_lines (invoice_id, quantity, unit_price)
VALUES (101, 2, 19.95);
Back to quick reference ↑
04

Treat sequence allocation as nontransactional

nextval is atomic across sessions, but values consumed by aborted transactions or ON CONFLICT attempts are not reclaimed. currval is session-local and errors until that session has called nextval for the sequence. setval changes are visible immediately and are not rolled back. Sequences therefore provide distinct allocation, not gapless numbering or commit-order timestamps.

Allocate and retrieve within one session
SELECT nextval('app.ticket_number_seq'::regclass) AS allocated;
SELECT currval('app.ticket_number_seq'::regclass) AS same_session_value;
Back to quick reference ↑
05

Use standalone sequences for explicit allocation contracts

A standalone sequence can serve multiple statements or tables, but sharing increases coupling. OWNED BY links its lifecycle to one column. CACHE reduces sequence contention but can create larger gaps and values may appear out of order across sessions. Grant USAGE for ordinary nextval/currval callers; UPDATE is needed for setval and should remain administrative.

Attach a sequence-backed default explicitly
CREATE SEQUENCE app.ticket_number_seq AS bigint CACHE 20;
ALTER TABLE app.tickets
  ALTER COLUMN number SET DEFAULT nextval('app.ticket_number_seq'::regclass);
ALTER SEQUENCE app.ticket_number_seq OWNED BY app.tickets.number;
GRANT USAGE ON SEQUENCE app.ticket_number_seq TO app_writer;
Back to quick reference ↑
06

Repair sequence position only under a controlled write boundary

After explicit-ID imports, determine the attached sequence with pg_get_serial_sequence and compare table keys with sequence state. Do not run max(id)-based setval while concurrent inserts can allocate values. For an empty table, setval needs an explicitly chosen initial value and is_called setting; blindly using max(id) yields null and does not repair anything.

Reconcile after a quiesced import
-- Run only after blocking concurrent writers and verifying the imported maximum.
SELECT pg_get_serial_sequence('app.accounts', 'id') AS sequence_name;
SELECT max(id) AS imported_max FROM app.accounts;
SELECT setval(
  pg_get_serial_sequence('app.accounts', 'id'),
  (SELECT max(id) FROM app.accounts),
  true
);
Back to quick reference ↑
07

Audit metadata and portability before deployment

Use information_schema.columns and pg_sequences to inventory generation rules and sequence configuration. Virtual generated columns are a PostgreSQL 18 addition, so use STORED or another design when supporting older majors. Test dump/restore, replication publication settings for stored generated columns, privileges on expression functions, and sequence exhaustion alarms as part of deployment.

Inventory identities, generated expressions, and sequences
SELECT table_schema, table_name, column_name, is_identity,
       identity_generation, is_generated, generation_expression
FROM information_schema.columns
WHERE table_schema = 'app'
  AND (is_identity = 'YES' OR is_generated = 'ALWAYS')
ORDER BY table_name, ordinal_position;

SELECT schemaname, sequencename, data_type, start_value, max_value, cache_size
FROM pg_sequences
WHERE schemaname = 'app'
ORDER BY sequencename;
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. PostgreSQL Global Development GroupPostgreSQL: Identity Columnspostgresql.org
  2. PostgreSQL Global Development GroupPostgreSQL: Generated Columnspostgresql.org
  3. PostgreSQL Global Development GroupPostgreSQL: Sequence Manipulation Functionspostgresql.org
  4. PostgreSQL Global Development GroupPostgreSQL: CREATE SEQUENCEpostgresql.org
  5. PostgreSQL Global Development GroupPostgreSQL: CREATE TABLEpostgresql.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback