skip to content

Identity, Sequences and Generated Columns

How SQL auto-generates values: standard IDENTITY columns, explicit sequences, and computed columns derived from other columns. Interviewers ask this to check you know the ANSI way to mint surrogate keys, not just a vendor's AUTO_INCREMENT.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

5

How do you declare an auto-numbered surrogate key column in standard SQL?

level: juniorimportance: must knowfreq 70%

answer

  1. The engine mints the value, not you
  2. One keyword phrase in the column definition
  3. Sequence options in parentheses after it
  4. GENERATED … AS IDENTITY, ALWAYS or BY DEFAULT

basics

~20 s

Standard SQL uses an identity column: id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY. The engine supplies each value from an attached sequence generator, whose START WITH, INCREMENT BY, CYCLE and MINVALUE/MAXVALUE options you set in parentheses.

solid answer

~50 s

The ANSI way is an **identity column**, written directly in the column definition: ```sql CREATE TABLE orders ( order_id BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, placed_at TIMESTAMP NOT NULL ); ``` `GENERATED ALWAYS AS IDENTITY` attaches an internal sequence generator to the column; every INSERT that does not supply the column gets the next value. The parenthesised options are the same sequence options `CREATE SEQUENCE` takes — `START WITH`, `INCREMENT BY`, `MINVALUE`/`MAXVALUE`, `CYCLE`/`NO CYCLE`. The variant `GENERATED BY DEFAULT AS IDENTITY` lets an INSERT override the generated value. An identity column is implicitly `NOT NULL`, and at most one identity column is allowed per table. Vendors spell the same idea differently — MySQL `AUTO_INCREMENT`, SQL Server `IDENTITY(1,1)`, SQLite `INTEGER PRIMARY KEY` — so the ANSI form is the portable answer to reach for.

go deeper

for a junior

Be ready to write the column definition from memory and to say that the engine, not your code, supplies the value. Know that omitting the column in the INSERT is what triggers generation.

for a middle

Explain the sequence options (START WITH, INCREMENT BY, CYCLE), why the type must be an exact numeric with scale zero, and why the PRIMARY KEY constraint is a separate declaration from the generator.

for a senior

Show that you pick BIGINT deliberately, choose NO CYCLE for keys, and know how the ANSI form maps onto each engine you deploy on so a schema migration does not surprise you.

for a principal

Own the convention: whether surrogate keys are mandated across the schema, whether ALWAYS or BY DEFAULT is the house default, and what that implies for data imports, multi-engine portability and key exposure.

## The problem an identity column solves Most tables need a surrogate key: a value with no business meaning whose only job is to identify the row. Nobody wants the application to compute it, because two concurrent sessions running `SELECT MAX(id) + 1` will hand out the same number. The database is the only place that can mint a unique number under concurrency, so SQL gives you a declaration that says "this column's values come from the engine". ## The ANSI syntax ```sql column_name data_type GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ] ``` A complete table: ```sql CREATE TABLE invoices ( invoice_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customer_id BIGINT NOT NULL, total NUMERIC(12,2) NOT NULL ); INSERT INTO invoices (customer_id, total) VALUES (42, 99.50); ``` The INSERT simply omits `invoice_id`; the engine fills it. The column type must be exact numeric with scale 0 — `SMALLINT`, `INTEGER`, `BIGINT`, or `NUMERIC(p,0)`. Picking `INTEGER` caps you near 2.1 billion values, which is why `BIGINT` is the common default for anything that might grow. ## The sequence options The parenthesised list is the same option set `CREATE SEQUENCE` accepts: ```sql ticket_no BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1000 INCREMENT BY 1 MINVALUE 1000 NO MAXVALUE NO CYCLE) ``` - `START WITH n` — the first value handed out. - `INCREMENT BY n` — the step; a negative step counts down. - `MINVALUE` / `MAXVALUE` (or `NO MINVALUE` / `NO MAXVALUE`) — the bounds. - `CYCLE` / `NO CYCLE` — whether the generator wraps around at the bound or raises an error. `NO CYCLE` is the sane choice for a key: cycling would eventually collide with the primary key constraint. Omit the parentheses entirely and you get the engine's defaults, which start at 1 and step by 1. ## What the declaration implies An identity column is implicitly `NOT NULL` — you do not need to write it, and you cannot make it nullable. The standard permits at most **one** identity column per table, which is a deliberate restriction: identity is meant for the surrogate key, not for arbitrary counters. And `PRIMARY KEY` is a *separate* declaration. `GENERATED ALWAYS AS IDENTITY` guarantees only that values arrive from a generator; it is the `PRIMARY KEY` or `UNIQUE` constraint that guarantees no duplicate ever lands in the column, which matters as soon as someone loads explicit values into it. ## ALWAYS versus BY DEFAULT, in one line `GENERATED ALWAYS` rejects an INSERT that supplies its own value for the column (unless the INSERT says `OVERRIDING SYSTEM VALUE`). `GENERATED BY DEFAULT` accepts a supplied value and only generates one when the column is omitted or given `DEFAULT`. Choose `ALWAYS` when nothing outside the database should ever choose an id; choose `BY DEFAULT` when a data load legitimately carries its own keys. ## Vendor spellings The idea is universal, the syntax is not: - MySQL: `id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY` - SQL Server: `id BIGINT IDENTITY(1,1) PRIMARY KEY` — the two arguments are seed and increment - SQLite: `id INTEGER PRIMARY KEY` is an alias for the rowid and auto-numbers; the extra `AUTOINCREMENT` keyword changes reuse behaviour - PostgreSQL and Oracle accept the ANSI `GENERATED … AS IDENTITY` form; PostgreSQL also carries the older `serial`/`bigserial` shorthand, which expands to a column with a `DEFAULT` drawing from a separate sequence object The practical consequence is that a schema written in ANSI form may still need one edit per engine, and that a column written as `serial` behaves like `BY DEFAULT` (it is only a default, easily overridden), not like `ALWAYS`. ## Common mistakes Declaring `INTEGER` for a high-volume table and running out of range years later; assuming the generated values are gap-free and contiguous, which no engine promises; assuming the identity declaration alone prevents duplicates when nothing enforces uniqueness; and adding a second identity column to a table, which the standard does not allow.

  • Can a table have two identity columns?
    No — the standard allows at most one identity column per table, and engines enforce that. Identity is intended for the surrogate key. If you need a second auto-numbered column, attach an explicit sequence to it with a `DEFAULT` drawing from `CREATE SEQUENCE`, or derive the value another way.
  • Is an identity column automatically the primary key?
    No. `GENERATED ALWAYS AS IDENTITY` only says where values come from; it does not enforce uniqueness. You still declare `PRIMARY KEY` or `UNIQUE` separately. That constraint is what catches a collision if explicit values are ever loaded into the column and the generator is left behind.
  • Which data types may an identity column have?
    Exact numeric types with scale zero — `SMALLINT`, `INTEGER`, `BIGINT`, or `NUMERIC(p,0)`. Floating-point and character types are rejected. `BIGINT` is the usual choice for anything that could grow, since `INTEGER` runs out just past two billion values and widening the column later on a large table is expensive.

saying these in an interview costs you the question

  • Says SELECT MAX(id)+1 is a fine way to assign keys
  • Claims AUTO_INCREMENT is standard SQL
  • Thinks the identity declaration alone prevents duplicate ids
  • Expects generated values to be contiguous with no gaps
  • Declares INTEGER for a table that will exceed two billion rows

context

open as a page

What is the difference between GENERATED ALWAYS AS IDENTITY and GENERATED BY DEFAULT AS IDENTITY?

level: middleimportance: must knowfreq 55%

basics

~20 s

GENERATED ALWAYS rejects an INSERT that supplies its own value for the column unless the statement says OVERRIDING SYSTEM VALUE. GENERATED BY DEFAULT accepts a supplied value and only generates one when the column is omitted or given DEFAULT.

open as a page

How does GENERATED ALWAYS AS IDENTITY differ from GENERATED ALWAYS AS (expression)?

level: middleimportance: should knowfreq 40%

basics

~20 s

They share a keyword but declare different things. AS IDENTITY draws each value from a sequence generator, independent of the row's data. AS (expression) computes the value from other columns of the same row every time those columns change.

open as a page

When would you use CREATE SEQUENCE and NEXT VALUE FOR instead of an identity column?

level: middleimportance: should knowfreq 38%

basics

~20 s

Use a standalone sequence when the number is needed independently of one table's INSERT — to obtain a key before the row exists, to share one numbering across several tables, or to feed a value into an expression. An identity column ties the generator to one column.

open as a page

An identity column collides with imported ids on the next INSERT — why, and how do you reseed it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Supplying explicit values never advances the generator, so after a load it still offers numbers the imported rows already occupy and the primary key rejects them. Fix it with ALTER TABLE ... ALTER COLUMN id RESTART WITH n, above the highest loaded value.

open as a page