skip to content

Schema Definition (DDL)

The statements that create and evolve tables: CREATE/ALTER/DROP, the ANSI type system, identity generation, and constraint declarations. Interviewers ask these to see whether you can design a schema in portable SQL rather than clicking through a GUI.

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

questions

27

What value do existing rows get when you run ALTER TABLE ... ADD COLUMN on a populated table?

level: juniorimportance: must knowfreq 72%

answer

  1. the column must hold something for old rows
  2. nothing is invented from nowhere
  3. the DEFAULT decides what old rows read
  4. NOT NULL plus no DEFAULT on a full table

basics

~20 s

Existing rows get NULL unless the new column declares a DEFAULT, in which case every existing row is populated with that default value. Adding a NOT NULL column with no DEFAULT to a table that already holds rows is rejected.

solid answer

~40 s

`ALTER TABLE orders ADD COLUMN tracking_code VARCHAR(40)` adds a nullable column, and every row already in the table reads NULL for it — the statement invents no data. If the definition carries a default, as in `ADD COLUMN status VARCHAR(20) DEFAULT 'pending'`, then the existing rows are populated with `'pending'`: the default is applied to the rows that are already there, not just to future inserts. The combination that fails is `NOT NULL` with no `DEFAULT` on a non-empty table, because the rows that exist would immediately violate the constraint; supply a default, or add the column nullable, backfill it, and tighten it afterwards. The new column is appended at the end of the table's column order unless the engine offers positional syntax.

code

sql · 8 lines
sql
-- existing rows read NULL
ALTER TABLE orders ADD COLUMN tracking_code VARCHAR(40);

-- existing rows are populated with 'pending'
ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending';

-- rejected on a non-empty table: existing rows would violate NOT NULL
ALTER TABLE orders ADD COLUMN shipped_at TIMESTAMP NOT NULL;

go deeper

for a junior

Be ready to state the two outcomes cleanly: no DEFAULT means existing rows read NULL, a DEFAULT means existing rows are populated with it. Knowing that NOT NULL with no default fails on a non-empty table is the third of three facts.

for a middle

Explain why the NOT NULL case fails — the stored rows would violate the constraint the instant it exists — and give the nullable-add, backfill, tighten sequence. Contrast ADD COLUMN ... DEFAULT (backfills) with ALTER COLUMN SET DEFAULT (does not).

for a senior

Show judgment about the backfill value: a fabricated sentinel becomes indistinguishable from real data forever, so prefer NULL plus an explicit UPDATE when no default is truthful. Be aware engines differ on whether NOT NULL with no default errors or silently substitutes.

for a principal

Own the convention: which shape of ADD COLUMN is allowed in a migration by default, whether a backfill value must be reviewable, and what the team's rule is on positional column consumption that a new trailing column can break.

## What the statement does `ALTER TABLE <table> ADD COLUMN <name> <type> [column constraints]` extends an existing table's definition with one more column. Everything that may appear in a column definition inside `CREATE TABLE` — a data type, `DEFAULT`, `NOT NULL`, `UNIQUE`, a `CHECK`, a references clause — may in principle appear here too, subject to what the engine allows. The interesting part is not the syntax but what happens to the rows that were already stored before the column existed. ## Existing rows without a DEFAULT A column has to hold something for every row. When no default is declared, that something is NULL: ```sql ALTER TABLE orders ADD COLUMN tracking_code VARCHAR(40); -- every pre-existing order now reads NULL for tracking_code ``` This is why `ADD COLUMN` on its own is the least disruptive schema change there is: the column is nullable, the old rows are semantically "unknown", and code that does not mention the column is unaffected. ## Existing rows with a DEFAULT A declared default is not merely a rule for future inserts — for `ADD COLUMN` it also determines what the already-stored rows read: ```sql ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending'; -- every pre-existing order now reads 'pending' ``` That is the single most common misunderstanding in this area, and it is worth stating explicitly because the sibling operation behaves the opposite way: `ALTER TABLE orders ALTER COLUMN status SET DEFAULT 'new'` changes the default for a column that already exists and leaves every stored value untouched. Adding a column with a default backfills; changing a default does not. ## NOT NULL with no default ```sql ALTER TABLE orders ADD COLUMN shipped_at TIMESTAMP NOT NULL; -- rejected if rows exist ``` The engine has nothing to put in the existing rows, and NULL is exactly what the constraint forbids, so a conforming engine refuses the statement. On an empty table the same statement succeeds, which is why this bites in production but never in a fresh test database. Engines differ on the failure mode — some raise an error, some substitute an implicit type default such as `0` or the empty string, which is arguably worse because it silently manufactures data — so do not rely on the rejection as a safety net. The portable forms are: ```sql -- one statement, backfilled from the default ALTER TABLE orders ADD COLUMN shipped_at TIMESTAMP NOT NULL DEFAULT TIMESTAMP '1970-01-01 00:00:00'; -- or three steps when there is no honest default value ALTER TABLE orders ADD COLUMN shipped_at TIMESTAMP; -- nullable UPDATE orders SET shipped_at = ordered_at WHERE shipped_at IS NULL; ALTER TABLE orders ALTER COLUMN shipped_at SET NOT NULL; -- spelling varies by engine ``` The three-step form is preferable whenever no default is truthful: a fabricated sentinel like `1970-01-01` looks like real data forever afterwards, while NULL says "we do not know". ## Column order and expressions The new column is appended after the last existing column. Nothing in the standard lets you insert it in the middle; MySQL adds `FIRST` and `AFTER <column>` placement clauses as an extension. This matters for anything that consumes columns positionally — `SELECT *` into a fixed-shape struct, `INSERT INTO t VALUES (...)` without a column list, a CSV export — all of which are reasons to write explicit column lists in the first place. What may appear in the `DEFAULT` expression is also engine-dependent. A literal constant is universally accepted; function calls and parenthesised expressions are not. SQLite, for instance, requires the default in `ADD COLUMN` to be a constant and rejects `CURRENT_TIMESTAMP` there, even though the same default is legal in `CREATE TABLE`. When in doubt, add the column with a constant default (or none) and populate computed values with a subsequent `UPDATE`. ## What ADD COLUMN does not do It does not touch any other table, it does not change what existing queries return unless they use `SELECT *`, and it does not alter the meaning of the rows — a NULL in a newly added column means "this column did not exist when the row was written", which is often exactly right and occasionally needs a backfill to become useful.

  • Where in the table's column order does the new column appear, and why should you care?
    At the end, after every existing column; the standard offers no way to place it elsewhere, though MySQL adds FIRST and AFTER clauses. It matters for anything positional: `SELECT *` consumed by index, `INSERT INTO t VALUES (...)` with no column list, fixed-shape exports. Naming columns explicitly in queries removes the exposure entirely.
  • If you later run ALTER TABLE ... ALTER COLUMN ... SET DEFAULT, do the stored rows change?
    No. Changing the default on an existing column is a catalog change only: it affects inserts that omit the column from then on, and every value already stored stays exactly as it is. That asymmetry with ADD COLUMN ... DEFAULT, which does populate existing rows, catches people out regularly. To change stored values you need an UPDATE.
  • Can the DEFAULT in ADD COLUMN be an expression such as CURRENT_TIMESTAMP?
    Engines differ, so treat it as non-portable. A literal constant is accepted everywhere; function calls and parenthesised expressions are not — SQLite, for example, requires a constant default in ADD COLUMN and rejects CURRENT_TIMESTAMP there. The portable pattern is to add the column with a constant default or none, then populate computed values with an UPDATE.

saying these in an interview costs you the question

  • Thinks existing rows stay NULL even when a DEFAULT is declared
  • Believes ADD COLUMN NOT NULL always works on a populated table
  • Says a DEFAULT only ever applies to future inserts
  • Assumes the new column can be placed between existing columns portably
  • Confuses a column DEFAULT with a value the application supplies

context

open as a page

In CREATE TABLE, when must a constraint be written at table level instead of inline on a column?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Any rule covering more than one column — a composite PRIMARY KEY, UNIQUE or FOREIGN KEY, or a CHECK comparing two columns — must be written as a table constraint after the column list. Single-column rules may be written either way.

open as a page

What does DEFAULT CURRENT_TIMESTAMP in a CREATE TABLE column definition mean, and when is it evaluated?

level: juniorimportance: must knowfreq 70%

basics

~20 s

DEFAULT names the value the engine stores when an INSERT supplies none for that column. The expression is evaluated at insert time, so each row records its own insertion moment rather than one value frozen when the table was created.

open as a page

Why is FLOAT the wrong type for a money column, and what should you use instead?

level: juniorimportance: must knowfreq 82%

basics

~20 s

FLOAT and REAL store binary approximations, so a value such as 0.10 is never held exactly and the error accumulates across sums and multiplications. Money needs an exact type: DECIMAL/NUMERIC with a declared precision and scale.

open as a page

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

level: juniorimportance: must knowfreq 70%

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.

open as a page

In DROP TABLE, what does CASCADE do that RESTRICT does not?

level: middleimportance: must knowfreq 58%

basics

~20 s

RESTRICT refuses the drop while another object still depends on the table; CASCADE drops those dependent objects too — typically views over the table and foreign key constraints in other tables. Neither keyword deletes rows from any other table.

open as a page

How do you declare a composite FOREIGN KEY with ON DELETE CASCADE in CREATE TABLE?

level: middleimportance: must knowfreq 62%

basics

~20 s

As a table constraint: FOREIGN KEY (a, b) REFERENCES parent (x, y) ON DELETE CASCADE. The two column lists correspond by position, the referenced columns must carry a PRIMARY KEY or UNIQUE declaration, and ON UPDATE is a separate clause.

open as a page

What does CREATE TABLE AS SELECT copy from the source query, and what does it leave behind?

level: middleimportance: must knowfreq 62%

basics

~20 s

CREATE TABLE AS SELECT builds a new table from a query result: column names and types are derived from the select list, and the returned rows are inserted. Keys, indexes, defaults and constraints are not carried over.

open as a page

What does TIMESTAMP WITH TIME ZONE store that a plain TIMESTAMP does not?

level: middleimportance: must knowfreq 60%

basics

~20 s

A plain TIMESTAMP is a wall-clock reading with no zone, so it does not identify a moment in time. TIMESTAMP WITH TIME ZONE carries the zone offset, so it pins an actual instant and can be compared across regions.

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 UNIQUE (team_id, jersey_no) differ from separate UNIQUE (team_id) and UNIQUE (jersey_no) declarations?

level: juniorimportance: should knowfreq 52%

basics

~20 s

UNIQUE (team_id, jersey_no) requires the pair to be unique, so each value may repeat on its own. Two single-column constraints are far stricter: no team_id could ever appear twice, and neither could any jersey number.

open as a page

What is the difference between CHAR(10) and VARCHAR(10), and when is CHAR the better choice?

level: juniorimportance: should knowfreq 66%

basics

~10 s

CHAR(10) is fixed length: shorter values are blank-padded to ten characters. VARCHAR(10) is variable length with ten as a maximum. Choose CHAR only for genuinely fixed-width codes; VARCHAR for everything else.

open as a page

What can ALTER TABLE ... ALTER COLUMN change, and which of those changes touch existing rows?

level: middleimportance: should knowfreq 50%

basics

~20 s

ALTER COLUMN changes a column's default, its nullability and its data type. Setting or dropping a DEFAULT is metadata only and leaves stored values untouched; SET NOT NULL is checked against every existing row; a type change converts stored values and is rejected when they do not fit.

open as a page

How do you drop a UNIQUE constraint that was declared inline in CREATE TABLE with no name?

level: middleimportance: should knowfreq 42%

basics

~20 s

ALTER TABLE ... DROP CONSTRAINT takes a name, so look up the system-generated one in information_schema.table_constraints for that table and drop it by that name. Generated names differ between engines and environments, which is why constraints should be named explicitly when declared.

open as a page

What exactly changes when you run ALTER TABLE ... RENAME COLUMN, and what keeps using the old name?

level: middleimportance: should knowfreq 38%

basics

~20 s

A rename edits the catalog entry only: the stored rows, the column's type, defaults, constraints and indexes are untouched and simply follow the new name. Nothing outside the database follows — application code, saved queries and reports still reference the old name and break.

open as a page

Why write CONSTRAINT uq_users_email before UNIQUE (email) instead of leaving the constraint unnamed?

level: middleimportance: should knowfreq 50%

basics

~20 s

The name is the handle you need later: dropping or altering a constraint takes its name, and violation errors quote it. Without one, the engine generates a name that is unpredictable and can differ between engines and environments.

open as a page

What does CREATE TABLE IF NOT EXISTS do when a table of that name already exists with different columns?

level: middleimportance: should knowfreq 44%

basics

~20 s

Nothing. The guard tests only whether the name is already taken in the target schema; if it is, the whole statement is skipped without error and without comparing or reconciling column definitions. The old, differently-shaped table survives.

open as a page

How do you create an empty table with the same structure as an existing one, keeping its column defaults?

level: middleimportance: should knowfreq 40%

basics

~20 s

Use the LIKE table element rather than a query: CREATE TABLE staging_orders (LIKE orders INCLUDING DEFAULTS). It copies the column definition — names, types, nullability — and the INCLUDING options decide whether defaults, constraints and indexes come too.

open as a page

In a column declared DECIMAL(8,2), what do the 8 and the 2 mean, and what happens to 1234.5678?

level: middleimportance: should knowfreq 52%

basics

~20 s

Precision 8 is the total count of significant decimal digits; scale 2 is how many sit right of the point, leaving six integer digits. 1234.5678 is rounded or truncated to two decimals and stored; only too many integer digits raise an error.

open as a page

How do you choose between SMALLINT, INTEGER and BIGINT, and what happens on overflow?

level: middleimportance: should knowfreq 45%

basics

~20 s

They are the same exact integer type at three widths — in mainstream engines 16, 32 and 64 bits — so pick the narrowest that comfortably covers the value's whole lifetime range. Exceeding it raises an out-of-range error; SQL does not wrap around.

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

What happens when ALTER TABLE ... DROP COLUMN targets a column a view depends on?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Under RESTRICT the engine refuses the drop and names the dependent view. Under CASCADE it drops the column and the view along with it. Indexes and constraints built on the column go automatically in either case.

open as a page

Why is CREATE TABLE orders_backup AS SELECT * FROM orders a weak rollback plan before a risky migration?

level: seniorimportance: should knowfreq 36%

basics

~20 s

It captures rows and inferred column types at one instant and nothing else — no keys, indexes, defaults, constraints or identity state. Restoring means rebuilding all of that by hand, and every write that lands after the snapshot is lost.

open as a page

How do you choose precision and scale for a DECIMAL money column in a multi-currency system?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Pick a scale at least as large as the most subdivided currency you support, a precision covering the largest amount in the weakest currency, and always store a currency code beside the amount, since the numeric type carries no unit.

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

In a composite FOREIGN KEY, what does MATCH FULL change compared with the default MATCH SIMPLE?

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

MATCH SIMPLE, the default, treats the constraint as satisfied as soon as any referencing column is NULL, skipping the parent lookup. MATCH FULL allows only all-NULL or all-non-NULL: a half-filled composite key is rejected.

open as a page