CREATE TABLE AS in PostgreSQL

Learn how to use PostgreSQL CREATE TABLE AS to create a new table from a query result, with TEMP, UNLOGGED, and IF NOT EXISTS options and practical examples.

4 min read · Back to overview

Quick Answer

CREATE TABLE new_table AS query creates a new table and populates it with the rows the query returns. Use IF NOT EXISTS to avoid errors on re-runs, TEMP for session-scoped tables, or UNLOGGED for fast staging tables that skip WAL overhead.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

CREATE TABLE AS is the SQL-standard way to create a new table and populate it from a query in one statement. It is preferred over SELECT INTO because it works inside PL/pgSQL functions, supports additional modifiers like UNLOGGED and IF NOT EXISTS, and makes the intent explicit.

Syntax

CREATE [TEMP | UNLOGGED] TABLE [IF NOT EXISTS] new_table_name
  [(column_name_list)]
AS query;

Key modifiers:

  • TEMP / TEMPORARY — table is dropped automatically at session end.
  • UNLOGGED — skips WAL logging for faster writes; the table is truncated on crash recovery.
  • IF NOT EXISTS — silently skips creation if the table already exists.
  • (column_name_list) — optional custom column names; count must match the query’s output.

Practical Example

Create a snapshot of high-value orders by joining two tables:

CREATE TABLE orders (
  order_id   SERIAL PRIMARY KEY,
  product_id INT NOT NULL,
  quantity   INT NOT NULL,
  unit_price NUMERIC(10,2) NOT NULL,
  placed_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE products (
  product_id   SERIAL PRIMARY KEY,
  product_name VARCHAR(200) NOT NULL,
  category     VARCHAR(100) NOT NULL
);

-- Create a permanent summary snapshot
CREATE TABLE high_value_orders AS
SELECT
  o.order_id,
  p.product_name,
  p.category,
  o.quantity * o.unit_price AS line_total,
  o.placed_at
FROM orders o
JOIN products p USING (product_id)
WHERE o.quantity * o.unit_price > 500
ORDER BY line_total DESC;

Verify the result:

SELECT * FROM high_value_orders LIMIT 3;

Create a summary table with explicit column names:

CREATE TABLE IF NOT EXISTS category_revenue (category, total_revenue) AS
SELECT category, SUM(quantity * unit_price)
FROM orders
JOIN products USING (product_id)
GROUP BY category;

Inspect the structure — note no indexes are copied:

\d high_value_orders

UNLOGGED Tables for Staging

CREATE UNLOGGED TABLE staging_import AS
SELECT * FROM orders WITH NO DATA;

-- Fast bulk load — no WAL overhead
INSERT INTO staging_import SELECT * FROM orders WHERE placed_at >= '2026-01-01';

-- Validate, then move to permanent table
INSERT INTO orders_archive SELECT * FROM staging_import;
DROP TABLE staging_import;

Testing with Vela

When building ETL pipelines or reporting tables with CREATE TABLE AS, Vela database branching lets you test the full transformation against a real copy of your production data before committing the pipeline to your live environment. Create a branch, run the CREATE TABLE AS query, validate row counts and data quality, then merge to production with confidence.

Production Tips

  • Explicitly name output columns when the SELECT list contains expressions or aggregates — auto-generated names like ?column? are unusable.
  • Add indexes and constraints after creation: ALTER TABLE new_table ADD PRIMARY KEY (id).
  • Use UNLOGGED for high-throughput staging tables where durability is not required, then copy to a logged table after processing.
  • Prefer CREATE TABLE AS over SELECT INTO in all new code — it is SQL standard and works without restriction in PL/pgSQL.
  • Use WITH NO DATA to create empty structural clones for schema testing: CREATE TABLE new_table AS TABLE existing_table WITH NO DATA.
  • Wrap the create + index steps in a transaction to avoid leaving a partially built table on failure.

Continue in Managing Tables: SERIAL.

Related in this section: PostgreSQL Data Types · Create Table · Select Into

Frequently Asked Questions

What does CREATE TABLE AS do in PostgreSQL?
CREATE TABLE AS executes a SELECT query and writes its result into a newly created table in a single statement. The new table's column names and types are derived from the query's output columns. No indexes or constraints are copied from the source.
Does CREATE TABLE AS lock the source table and block queries?
CREATE TABLE AS holds a shared lock on every source table it reads from, just like a regular SELECT. This does not block concurrent reads but does block DDL on those tables until the statement completes.
What happens to indexes and constraints from the source table?
They are not copied. Only column definitions and row data are transferred. Add primary keys, foreign keys, indexes, and other constraints manually with ALTER TABLE and CREATE INDEX after the new table is created.
Can I use IF NOT EXISTS with CREATE TABLE AS?
Yes. CREATE TABLE IF NOT EXISTS new_table AS query silently skips creation if the table already exists, making migration scripts idempotent.
What is the safest way to create a staging table for bulk processing in production?
Use CREATE UNLOGGED TABLE staging AS SELECT ... for fast writes without WAL overhead. After processing, copy the validated rows to a regular (logged) table and then drop the staging table. Wrap the whole operation in a transaction for safety.