skip to content

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

level: middleimportance: should knowfreq 40%

answer

  1. a query copy and a definition copy differ
  2. the clause goes inside the parentheses
  3. opt in to the parts you want
  4. defaults need an explicit option
  5. neither form copies any rows

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.

solid answer

~50 s

There are two different "copy a table" statements and they copy different things. `CREATE TABLE t2 AS SELECT * FROM t1 WITH NO DATA` copies the shape of a **query result**: column names and inferred types, with no defaults, no keys, no indexes, and in PostgreSQL not even `NOT NULL`. The `LIKE` table element copies the **column definition**: `CREATE TABLE staging_orders (LIKE orders);` brings across names, types and not-null declarations, and `INCLUDING DEFAULTS`, `INCLUDING CONSTRAINTS`, `INCLUDING INDEXES` or `INCLUDING ALL` extend that to the parts you name. Neither form copies any rows. Spelling varies: PostgreSQL uses the parenthesised `LIKE ... INCLUDING ...` form, MySQL uses `CREATE TABLE new LIKE orders` with no parentheses and copies indexes as part of it, and Microsoft SQL Server's structure-only idiom is `SELECT * INTO new FROM orders WHERE 1 = 0`. Pick `LIKE` when you want the definition, CTAS when you want the result shape.

code

sql · 6 lines
sql
-- PostgreSQL: definition copy, plus the load bookkeeping columns
CREATE TABLE staging_orders (
  LIKE orders INCLUDING DEFAULTS,
  loaded_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  batch_id  BIGINT    NOT NULL
);

go deeper

for a junior

Know that a structure-only copy is a different statement from a data copy, and that the LIKE form is the one that can bring column defaults with it.

for a middle

Explain the split between copying a result shape and copying a column definition, and which INCLUDING options add defaults, constraints and indexes.

for a senior

Choose deliberately for the job at hand — staging tables that skip indexes for load speed, archive tables that mirror the original fully — and know the dialect spellings you will actually meet.

for a principal

Decide whether cloned tables belong in the schema at all: a copy created by LIKE has no checked-in definition of its own, so set the expectation that anything long-lived gets real DDL in version control.

## Two statements, two meanings of "copy" Asked to clone a table, most people reach for `CREATE TABLE ... AS SELECT`. That is often the wrong tool, because CTAS is defined over a query result. It gives you the columns the select list produced, with the types the engine inferred, and none of the definition around them. If the intent was "give me another table shaped like this one", the `LIKE` table element is the construct that actually says that. ```sql -- shape of a result set: no defaults, no keys, no indexes CREATE TABLE staging_orders AS SELECT * FROM orders WITH NO DATA; -- shape of the table definition, defaults included CREATE TABLE staging_orders (LIKE orders INCLUDING DEFAULTS); ``` ## The LIKE table element `LIKE source_table` appears *inside* the parenthesised table-element list, in the same position a column definition would occupy. That is why it is written `CREATE TABLE new (LIKE old)` and not `CREATE TABLE new LIKE old` in standard-shaped SQL. It expands, at creation time, into copies of the source table's column definitions: names, data types, and not-null declarations. It is a one-time expansion, not a link — later changes to the source do not propagate. Because it sits among the table elements, you can mix it with your own: ```sql CREATE TABLE staging_orders ( LIKE orders INCLUDING DEFAULTS, loaded_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, batch_id BIGINT NOT NULL ); ``` That is the idiomatic way to build a staging table: everything the target has, plus the load bookkeeping the target does not want. ## The INCLUDING options By default `LIKE` is conservative — columns, types and nullability only. PostgreSQL's options let you opt in to more: - `INCLUDING DEFAULTS` — copy the `DEFAULT` clauses - `INCLUDING CONSTRAINTS` — copy `CHECK` constraints - `INCLUDING INDEXES` — copy indexes and the unique/primary-key constraints backing them - `INCLUDING ALL` — everything the engine supports, with `EXCLUDING <option>` to carve pieces back out A staging table usually wants `INCLUDING DEFAULTS` and deliberately *not* `INCLUDING INDEXES`, because indexes slow the load down and can be added after it. A table that must behave like the original for testing purposes wants `INCLUDING ALL`. Note what `LIKE` does not do even at `INCLUDING ALL`: it does not create foreign keys pointing out to other tables, and it obviously does not make other tables point at the new one. Referential structure has to be declared explicitly. ## No rows, ever Neither `LIKE` form copies data. That is a feature — the two concerns are separated cleanly. If you want the structure *and* the rows, create with `LIKE` and then load: ```sql CREATE TABLE orders_archive (LIKE orders INCLUDING ALL); INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < DATE '2025-01-01'; ``` That two-statement form is strictly better than a single CTAS whenever the destination will be queried or written to seriously, because the destination ends up with the same keys, defaults and constraints as the source rather than a bare pile of columns. ## Dialect spellings This is one of the places where engines genuinely diverge, so check yours: - **PostgreSQL** uses the parenthesised table element with the `INCLUDING`/`EXCLUDING` options described above. - **MySQL** spells it `CREATE TABLE new_table LIKE orders;` — no parentheses, and it copies column attributes and indexes as a unit rather than offering per-part options. - **Microsoft SQL Server** has no `LIKE` clause; the traditional structure-only idiom is `SELECT * INTO new_table FROM orders WHERE 1 = 0`, which produces columns and types but no keys, indexes or defaults. - Engines without any of these need the definition written out, which is another argument for keeping the real DDL in version control. ## Choosing between them Ask what you want to preserve. If the answer is "the shape of this query's output", CTAS is correct and `WITH NO DATA` or `WHERE 1 = 0` gets you the empty version. If the answer is "another table that behaves like this one", `LIKE` with the appropriate `INCLUDING` options is correct, and it is the only one of the two that can bring defaults and constraints along.

  • Does the LIKE table element keep the new table in sync with the source afterwards?
    No. It is expanded once, when the `CREATE TABLE` runs, into ordinary column definitions on the new table. Later `ALTER TABLE` statements on the source have no effect on the copy, and vice versa. If you need the two to stay aligned, that is a discipline in your migration scripts, not something the clause provides.
  • For a bulk-load staging table, which INCLUDING options would you choose?
    Usually `INCLUDING DEFAULTS` and deliberately not `INCLUDING INDEXES`. You want the same column semantics as the target so the loaded rows are already correct, but index maintenance on every inserted row makes a large load far slower. Add the indexes after the load, once, when the engine can build them in bulk.
  • How would you get both the structure and a subset of the rows into a new table?
    Two statements: `CREATE TABLE orders_archive (LIKE orders INCLUDING ALL);` then `INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < DATE '2025-01-01';`. The pair beats a single CTAS whenever the destination matters, because it arrives with the source's keys, defaults and constraints rather than bare columns.

saying these in an interview costs you the question

  • Thinks CTAS with no rows preserves the defaults
  • Believes the copy stays in sync with the source
  • Assumes LIKE brings indexes without an option
  • Expects LIKE to copy the table's rows too
  • Assumes one spelling works on every engine

context