skip to content

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

level: middleimportance: should knowfreq 38%

answer

  1. Identity's generator is welded to one column
  2. Sometimes you need the number before the row
  3. Two tables can share one stream of numbers
  4. A named schema object you can draw from anywhere

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.

solid answer

~60 s

An identity column is a sequence generator welded to a single column: convenient, but you can only get a value by inserting a row into that table. `CREATE SEQUENCE` makes the generator a **schema object in its own right**, so any statement can draw from it: ```sql CREATE SEQUENCE order_seq AS BIGINT START WITH 1000 INCREMENT BY 1 NO CYCLE; INSERT INTO orders (order_id, customer_id) VALUES (NEXT VALUE FOR order_seq, 42); ``` That unlocks three things identity cannot do. You can obtain the number **before or apart from** the insert and use it in several statements — parent row plus child rows, or a value returned to the caller first. You can share **one numbering across multiple tables**, so a document number is unique across invoices and credit notes. And you can use it in any value expression, including a column `DEFAULT`. The cost is that nothing ties the sequence to the column, so keeping them consistent is on you. For an ordinary surrogate key, identity is the simpler and safer default.

go deeper

for a junior

Know that a sequence is a named object you create separately and draw from with an expression, whereas an identity column's generator is invisible and tied to one column.

for a middle

Give the concrete reasons to prefer a sequence — needing the value before the insert, sharing one numbering across tables — and name the spelling differences between engines.

for a senior

Weigh the loss of binding: orphaned sequences, environment restores that leave the generator behind the data, and the fact that uniqueness still comes from the constraint, not the generator.

for a principal

Own the convention for document numbering and cross-table identifiers, including how sequences survive restores and environment copies, and what portability constraints a sequence-dependent schema imposes.

## Two shapes of the same generator An identity column and a sequence object are the same machinery exposed differently. Identity binds the generator to one column of one table; you cannot name it in a query, and you get a value only as a side effect of inserting. `CREATE SEQUENCE` promotes the generator to a first-class schema object with a name you can use anywhere a value expression is allowed. ```sql CREATE SEQUENCE order_seq AS BIGINT START WITH 1000 INCREMENT BY 1 MINVALUE 1000 NO MAXVALUE NO CYCLE; ``` The options are the same set an identity column takes in parentheses, which is the clearest sign the two features are one mechanism. ## Drawing a value The ANSI expression is `NEXT VALUE FOR <sequence name>`: ```sql INSERT INTO orders (order_id, customer_id, total) VALUES (NEXT VALUE FOR order_seq, 42, 99.50); ``` Spellings diverge sharply here, and this is the one place portability really bites. PostgreSQL uses a function, `nextval('order_seq')`; Oracle uses `order_seq.NEXTVAL`; SQL Server uses the ANSI `NEXT VALUE FOR`. MySQL has no sequence objects at all, so a schema depending on them does not port there without redesign. Re-reading the value your session last generated is likewise engine-specific, so avoid building on it if portability matters. ## Case 1 — you need the key before the row The classic driver. An order and its lines are written in one transaction, and the lines need the order's id: ```sql -- pseudocode around plain SQL SELECT NEXT VALUE FOR order_seq; -- say it returns 1007 INSERT INTO orders (order_id, ...) VALUES (1007, ...); INSERT INTO order_lines (order_id, ...) VALUES (1007, ...), (1007, ...); ``` With an identity column the id does not exist until the parent INSERT has run, so the application must read it back before it can write the children. Taking the number up front lets you build the whole object graph — or hand the identifier to a caller, or stamp it into a message — before touching the table. It also composes cleanly with multi-row inserts where you want all children to carry a value you already hold. ## Case 2 — one numbering across several tables A business rule such as "every financial document has a unique document number, whether it is an invoice or a credit note" cannot be expressed with two identity columns, which count independently and will collide. One sequence, referenced from both tables, gives a single stream of numbers: ```sql CREATE TABLE invoices (doc_no BIGINT DEFAULT (NEXT VALUE FOR doc_seq) PRIMARY KEY, ...); CREATE TABLE credit_notes (doc_no BIGINT DEFAULT (NEXT VALUE FOR doc_seq) PRIMARY KEY, ...); ``` (Engines differ on whether a sequence draw is permitted in a `DEFAULT` clause and on the exact spelling — PostgreSQL's legacy `serial` type is precisely a column whose `DEFAULT` calls `nextval` on a sequence it created for you.) ## Case 3 — the value is not a key at all Batch numbers, run ids, correlation ids stamped onto a set of rows, a per-import job identifier written into every loaded row: none of these is "the identity of one row in one table", and all of them want a named generator you can draw from wherever you like. ## What you give up The binding disappears. With identity, the generator is created, owned and dropped with the column, and the engine will not let you leave it dangling. With a sequence you have three independent objects — the sequence, the table, and whatever draws from it — and keeping them coherent is your job: dropping the table leaves the sequence behind, restoring one environment from another can restore a table without its sequence at the right position, and nothing stops a statement inserting a value that never came from the sequence at all. Uniqueness is likewise not the sequence's promise. A sequence hands out distinct values as long as it is not cycled or reset; the guarantee that the *column* holds no duplicates still comes from the `PRIMARY KEY` or `UNIQUE` constraint. Declaring `NO CYCLE` matters for exactly this reason — a cycled sequence eventually re-offers numbers that are already stored. ## The default choice For a plain surrogate key, prefer the identity column: fewer objects, an explicit binding, and the write authority controls (`ALWAYS` / `BY DEFAULT`) come with it. Reach for `CREATE SEQUENCE` when one of the cases above actually applies — most often the pre-allocated key or the shared numbering. "Because I might need it later" is not one of them.

  • How do you draw from a sequence on different engines?
    The ANSI expression is `NEXT VALUE FOR seq`, which SQL Server uses. PostgreSQL spells it `nextval('seq')` and Oracle `seq.NEXTVAL`. MySQL has no sequence objects at all, so a design that depends on one needs a different approach there.
  • Does a sequence guarantee the column holds no duplicates?
    No. A sequence hands out distinct values while it is not cycled or reset, but nothing stops a statement inserting a value that never came from it, and a restore can leave the sequence behind the data. Uniqueness in the column still comes from PRIMARY KEY or UNIQUE.
  • What breaks if you drop the table but not the sequence?
    With a standalone sequence, nothing warns you — the sequence survives as an orphan, still at its old position, and a later table recreated against it may or may not line up with existing data. An identity column's generator is owned by the column and goes with it.

An identity column is a ticket printer bolted to one counter — you get a number only by queueing there. A sequence is the shared ticket roll on the wall: any counter can tear off the next number, and you can take one before you decide where to go.

saying these in an interview costs you the question

  • Says a sequence guarantees the column has no duplicates
  • Assumes NEXT VALUE FOR works on every engine
  • Reaches for a sequence for an ordinary single-table key
  • Uses SELECT MAX(id)+1 instead of a generator
  • Forgets the sequence survives when the table is dropped

context