skip to content

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

level: middleimportance: should knowfreq 40%

answer

  1. One keyword, two unrelated features
  2. Look at what follows the word AS
  3. One draws from a counter, one from the row
  4. IDENTITY versus a parenthesised expression

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.

solid answer

~50 s

The shared prefix is a genuine trap in the grammar. `GENERATED ALWAYS AS IDENTITY` — no parentheses around an expression — attaches a **sequence generator**: values come from a counter and have nothing to do with the row's contents. `GENERATED ALWAYS AS (qty * unit_price) STORED` — parentheses, an expression — declares a **generated (computed) column**: its value is derived from other columns of the same row and is recomputed whenever they change. ```sql CREATE TABLE order_lines ( line_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, qty INTEGER NOT NULL, unit_price NUMERIC(10,2) NOT NULL, line_total NUMERIC(12,2) GENERATED ALWAYS AS (qty * unit_price) STORED ); ``` What they share is the write rule: `ALWAYS` means you cannot supply a value. For a generated column that is absolute — there is no `OVERRIDING` escape at all; the INSERT must omit the column or write `DEFAULT`. The expression may reference only columns of the same row and must be deterministic.

go deeper

for a junior

Recognise both forms on sight: the bare word IDENTITY versus a parenthesised expression after AS. Know that in either case your INSERT omits the column.

for a middle

Explain that identity values are row-independent and assigned once, while a computed column tracks its inputs on every UPDATE, and that only identity has an OVERRIDING escape.

for a senior

Show judgment about which values belong in a generated column at all — deterministic, same-row, cheap — and what the alternative is when the derivation crosses rows or is non-deterministic.

for a principal

Frame it as where derivation lives: schema-enforced derived values versus application-computed ones, and the migration cost of changing a generated column's expression on a large live table.

## Two declarations, one keyword prefix SQL reuses `GENERATED ALWAYS AS` for two unrelated features, and the disambiguator is what follows: ```sql id BIGINT GENERATED ALWAYS AS IDENTITY -- sequence generator line_total NUMERIC(12,2) GENERATED ALWAYS AS (qty * price) STORED -- computed column ``` Read the token after `AS`. The bare keyword `IDENTITY` means "take the next number from a counter". A parenthesised expression means "compute this from the rest of the row". ## Where the value comes from An identity column's value is **row-independent**. The generator knows nothing about `qty` or `price`; it hands out 1, 2, 3 in insertion order and never looks at the data. Two rows with identical contents get different identity values — that is the entire point of a surrogate key. A generated column's value is **row-determined**. It is a function of other columns in the *same* row, so two rows with identical inputs necessarily get identical results. It cannot reference other rows, cannot contain a subquery, and cannot call a non-deterministic function such as one returning the current time or a random number — those restrictions exist so the value stays reproducible from the row alone. ## When the value is produced An identity value is assigned once, at INSERT, and then it just sits there — nothing in the row can change it, and no later UPDATE recomputes it. A generated column has no independent existence: it tracks its inputs. Change `qty` with an UPDATE and `line_total` changes with it, without the UPDATE mentioning `line_total` at all. That is what makes it useful — the derived value cannot drift out of sync with its sources, which is exactly what happens when the same value is maintained by application code. ## The write rule they share, and where it differs Both carry `ALWAYS`, and in both cases that means the statement may not supply a value: ```sql INSERT INTO order_lines (qty, unit_price) VALUES (3, 10.00); -- fine INSERT INTO order_lines (qty, unit_price, line_total) VALUES (3, 10.00, 999.00); -- error INSERT INTO order_lines (qty, unit_price, line_total) VALUES (3, 10.00, DEFAULT); -- fine UPDATE order_lines SET line_total = 0 WHERE line_id = 1; -- error ``` The difference is the escape hatch. An identity column has one: `OVERRIDING SYSTEM VALUE` on the INSERT, and the alternative declaration `GENERATED BY DEFAULT AS IDENTITY` which accepts supplied values outright. A generated column has neither. There is no `BY DEFAULT` form of a computed column and no per-statement override, because a stored value that contradicted the expression would make the column a lie. The only legal ways to name it in an INSERT are to omit it or to write `DEFAULT`, and in an UPDATE you may write `DEFAULT` (or, more usually, just leave it alone). ## The trailing storage keyword A generated column's declaration usually ends with a storage keyword — `STORED` means the computed value is materialised with the row; `VIRTUAL` means it is computed when read. MySQL accepts both and treats `VIRTUAL` as the default when neither is written; other engines accept a subset, so check yours before assuming. Which one to pick is a storage-and-index tradeoff rather than a question about the language; syntactically, the point is simply that this keyword slot exists on the expression form and has no counterpart on the identity form. Identity's parenthesised slot holds something entirely different — sequence options like `START WITH` and `INCREMENT BY`. ## Choosing between them They are not alternatives, and a well-formed table often has both, as the example above shows. The question to ask is what the value *means*: - "This row needs an identifier no client should choose" → identity column. - "This value is a pure function of other columns and must never disagree with them" → generated column. - "This value is derived but not deterministic, or depends on other rows" → neither; a generated column will be rejected, and you need a different mechanism. The misuse to avoid is trying to make an identity column meaningful — encoding a year, a region code or a checksum into it. The generator has no access to the row, so anything of that shape has to be a generated column, a `DEFAULT` expression, or ordinary application logic. ## Common mistakes Writing `GENERATED ALWAYS AS (IDENTITY)` with parentheses, which parses as an expression form and fails; expecting a supplied `line_total` to be accepted "as an override"; putting a `SELECT` or a current-timestamp call inside the expression; and assuming a generated column can be updated directly to correct bad data, when the fix has to be applied to its inputs.

  • Can you supply a value for a generated (computed) column with OVERRIDING SYSTEM VALUE?
    No. The OVERRIDING clauses exist only for identity columns. A computed column has no override and no `BY DEFAULT` variant — the INSERT must omit it or write `DEFAULT`, and an UPDATE cannot assign to it. A stored value contradicting the expression would defeat the column's guarantee.
  • What kinds of expression are rejected in a generated column definition?
    Anything not reproducible from the row alone: subqueries, references to other rows or tables, and non-deterministic calls such as the current timestamp or a random value. Engines also differ on whether one generated column may reference another, so check yours before relying on it.
  • How would you correct a wrong value in a generated column?
    You cannot assign to it. Update the columns the expression reads and the derived value follows automatically. If the value itself is wrong for every row, the expression is wrong — that is a schema change to the column definition, not a data fix.

saying these in an interview costs you the question

  • Writes GENERATED ALWAYS AS (IDENTITY) with parentheses
  • Thinks OVERRIDING SYSTEM VALUE works on a computed column
  • Puts a subquery or current timestamp in the expression
  • Expects a generated column to be updatable directly
  • Tries to encode business meaning into identity values

context