PostgreSQL Sequences

Learn how to create and manage PostgreSQL sequences with CREATE SEQUENCE, and use nextval(), currval(), and setval() to generate unique integer values across tables.

5 min read · Back to overview

Quick Answer

A PostgreSQL sequence is a database object that generates a series of unique integers. Use CREATE SEQUENCE to define one, nextval() to advance it, currval() to read the current value in the session, and setval() to reset it. Sequence advances are not rolled back by transaction rollbacks.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

A PostgreSQL sequence is a special database object that generates a series of unique integers. Sequences are the underlying engine powering SERIAL columns and GENERATED AS IDENTITY columns, but they can be created independently to generate numbers following custom patterns — for example, starting at 1000 and incrementing by 10, or cycling through a fixed range.

Syntax

CREATE SEQUENCE [IF NOT EXISTS] sequence_name
  [AS { SMALLINT | INT | BIGINT }]
  [INCREMENT [BY] increment]
  [MINVALUE minvalue | NO MINVALUE]
  [MAXVALUE maxvalue | NO MAXVALUE]
  [START [WITH] start]
  [CACHE cache]
  [[ NO ] CYCLE]
  [OWNED BY { table_name.column_name | NONE }];

Key options:

  • INCREMENT — step size per call; positive for ascending, negative for descending. Default: 1.
  • START — first value from nextval(). Default: 1 for ascending.
  • CYCLE — wraps around at the limit. Default NO CYCLE raises an error at the limit.
  • CACHE — pre-allocates values in memory per session for faster access.
  • OWNED BY — ties the sequence lifetime to a column; auto-dropped when the column is dropped.

Practical Example

Create an ascending sequence for event ticket numbers, starting at 1000 and incrementing by 1:

CREATE SEQUENCE ticket_number_seq
  START 1000
  INCREMENT 1
  NO CYCLE;

Use it in a table:

CREATE TABLE event_tickets (
  ticket_number BIGINT       NOT NULL DEFAULT nextval('ticket_number_seq'),
  event_id      INT          NOT NULL,
  holder_name   VARCHAR(200) NOT NULL,
  issued_at     TIMESTAMPTZ  NOT NULL DEFAULT now(),
  PRIMARY KEY (ticket_number)
);

INSERT INTO event_tickets (event_id, holder_name)
VALUES (42, 'Alice Tan')
RETURNING ticket_number;

 ticket_number
---------------
          1000

Create a cycling descending sequence for countdown IDs:

CREATE SEQUENCE countdown_seq
  INCREMENT -1
  MINVALUE 1
  MAXVALUE 5
  START 5
  CYCLE;

SELECT nextval('countdown_seq'); -- 5
SELECT nextval('countdown_seq'); -- 4
SELECT nextval('countdown_seq'); -- 3

List all sequences in the current database:

SELECT relname AS sequence_name
FROM pg_class
WHERE relkind = 'S'
ORDER BY relname;

Reset a sequence after a bulk data import:

SELECT setval('ticket_number_seq', MAX(ticket_number))
FROM event_tickets;

Drop a sequence:

DROP SEQUENCE IF EXISTS ticket_number_seq CASCADE;

Testing with Vela

Sequence gaps and exhaustion issues are notoriously difficult to reproduce in development. Vela database branching lets you clone production with its real sequence state, run bulk imports or load tests, and verify setval() resets work correctly before touching the live database.

Production Tips

  • Sequence advances are not transaction-safe: a rolled-back transaction permanently skips the consumed value. Never build logic that assumes gap-free IDs.
  • Use CACHE greater than 1 to reduce sequence lock contention in high-concurrency workloads, at the cost of larger gaps between sessions.
  • Use OWNED BY table.column to bind the sequence so it is automatically dropped when the column or table is removed.
  • After bulk data imports with manual IDs, always resync the sequence with setval() to prevent future duplicate-key errors.
  • For standard auto-increment IDs, prefer SERIAL or GENERATED AS IDENTITY over manually created sequences — they wire up the OWNED BY relationship and column default automatically.
  • Call currval() only after nextval() has been called in the same session — it raises an error otherwise.

Continue in Managing Tables: Identity Column.

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

Frequently Asked Questions

What does a PostgreSQL sequence do?
A sequence generates unique, monotonically increasing (or decreasing) integers on demand. It is the underlying mechanism behind SERIAL and GENERATED AS IDENTITY columns, but it can also be used independently to produce unique numbers that span multiple tables or follow custom patterns.
Does calling nextval() lock the table or block queries?
No. Sequence operations use a lightweight lock that is independent of table locks. Multiple sessions can call nextval() concurrently without blocking each other. The sequence lock is released as soon as the value is returned, even before the surrounding transaction commits.
What happens to dependent objects when a sequence is dropped?
Dropping a sequence removes it and its default expression from any column that references it with nextval(). If a column's default relied on the sequence, subsequent inserts that omit the column will fail. Use OWNED BY when creating a sequence to tie its lifetime to a specific column.
Can I use IF EXISTS with DROP SEQUENCE?
Yes. DROP SEQUENCE IF EXISTS sequence_name suppresses the error when the sequence does not exist and issues a NOTICE instead. This is useful in idempotent migration scripts.
What is the safest way to reset a sequence in production?
Use SELECT setval('seq_name', MAX(id)) FROM table; to set the sequence to the current maximum value, then verify with SELECT nextval('seq_name') in a transaction you roll back. This prevents duplicate-key errors on subsequent inserts after a bulk data load.