Copy Table in PostgreSQL

Learn how to copy a PostgreSQL table with CREATE TABLE AS TABLE, copy structure only with WITH NO DATA, and handle indexes and constraints that are not copied automatically.

4 min read · Back to overview

Quick Answer

Use CREATE TABLE new_table AS TABLE existing_table to copy both the structure and all data. Add WITH NO DATA to copy only the schema. Indexes, primary keys, foreign keys, and other constraints are NOT copied — add them manually with ALTER TABLE after the copy.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

PostgreSQL provides several ways to copy a table using CREATE TABLE ... AS. All forms transfer column definitions and data, but none copy indexes, primary keys, foreign key constraints, or check constraints — you must add those manually after the copy.

Syntax

Copy structure and all data:

CREATE TABLE new_table AS TABLE existing_table;

Copy structure only, with no rows:

CREATE TABLE new_table AS TABLE existing_table WITH NO DATA;

Copy structure and a filtered subset of rows:

CREATE TABLE new_table AS
SELECT * FROM existing_table
WHERE condition;

Practical Example

Create and populate a source table:

CREATE TABLE products (
  product_id  SERIAL PRIMARY KEY,
  sku         VARCHAR(50)    NOT NULL UNIQUE,
  name        VARCHAR(200)   NOT NULL,
  unit_price  NUMERIC(10,2)  NOT NULL,
  is_active   BOOLEAN        NOT NULL DEFAULT TRUE
);

INSERT INTO products (sku, name, unit_price)
VALUES
  ('WID-001', 'Basic Widget',    9.99),
  ('WID-002', 'Premium Widget', 24.99),
  ('WID-003', 'Deluxe Widget',  49.99),
  ('WID-004', 'Budget Widget',   4.99);

Copy the full table:

CREATE TABLE products_backup AS TABLE products;

Verify the data was copied:

SELECT * FROM products_backup ORDER BY product_id;

Inspect the copy — note the absence of indexes:

\d products_backup
         Table "public.products_backup"
   Column   |          Type          | Nullable
------------+------------------------+----------
 product_id | integer                |
 sku        | character varying(50)  |
 name       | character varying(200) |
 unit_price | numeric(10,2)          |
 is_active  | boolean                |
(No indexes listed)

Recreate the primary key and unique constraint on the copy:

ALTER TABLE products_backup ADD PRIMARY KEY (product_id);
ALTER TABLE products_backup ADD UNIQUE (sku);

Create an empty structural clone for schema testing:

CREATE TABLE products_staging AS TABLE products WITH NO DATA;

Copy only active products:

CREATE TABLE active_products AS
SELECT *
FROM products
WHERE is_active = TRUE;

Full Structural Clone with pg_dump

When you need a complete copy including all constraints and indexes:

-- In the shell:
-- pg_dump -t products mydb | psql mydb

-- Or dump to a file and restore under a new name:
-- pg_dump -t products -f products_schema.sql mydb

For a rename-based clone entirely in SQL, script each constraint:

CREATE TABLE products_v2 AS TABLE products;
ALTER TABLE products_v2 ADD PRIMARY KEY (product_id);
ALTER TABLE products_v2 ADD UNIQUE (sku);
ALTER TABLE products_v2 ALTER COLUMN is_active SET DEFAULT TRUE;
ALTER TABLE products_v2 ALTER COLUMN unit_price SET NOT NULL;
-- etc.

Testing with Vela

When creating archive or staging copies of production tables, Vela database branching provides an instant full copy of the entire database for free — no CREATE TABLE AS TABLE required. Use branching for schema testing and migration rehearsal; reserve CREATE TABLE AS for runtime copies (ETL snapshots, audit tables, reporting caches) within a single database.

Production Tips

  • After copying, always run \d new_table to confirm which indexes and constraints are missing and must be added.
  • For large tables, use CREATE INDEX CONCURRENTLY after the copy to avoid long lock windows.
  • CREATE TABLE AS TABLE ... WITH NO DATA is useful for creating an empty schema clone for load testing or migration rehearsal.
  • If you need a full structural clone with all constraints and indexes, use pg_dumpCREATE TABLE AS only copies columns and data.
  • Copying with a WHERE clause is ideal for creating time-bounded archive tables (e.g., rows from a specific year) before purging old data from the main table.
  • The copy does not inherit SERIAL or identity column behavior — the sequence is not carried over. Add a new sequence or identity constraint if the copy needs auto-increment IDs.

Continue in Managing Tables: Back to tutorial overview.

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

Frequently Asked Questions

What does CREATE TABLE AS TABLE do in PostgreSQL?
CREATE TABLE new_table AS TABLE existing_table creates a new table with the same column definitions as the source and copies all of its rows. It is equivalent to CREATE TABLE new_table AS SELECT * FROM existing_table. Indexes and constraints are not transferred.
Does CREATE TABLE AS TABLE lock the source table and block queries?
It holds a shared lock on the source table for the duration of the copy, just like a SELECT. Concurrent reads on the source table are not blocked, but DDL on the source table is blocked until the copy completes.
What happens to indexes and constraints when I copy a table?
They are not copied. Only column names, data types, and row data are transferred. You must recreate primary keys, unique constraints, foreign keys, check constraints, and indexes on the new table with ALTER TABLE and CREATE INDEX.
Can I copy only the structure without any rows?
Yes. Append WITH NO DATA: CREATE TABLE new_table AS TABLE existing_table WITH NO DATA. This creates an empty table with identical column definitions but zero rows.
What is the safest way to make a full structural copy of a table including all constraints?
Use pg_dump -t source_table | psql target_db to copy the complete schema including all indexes, constraints, and sequences. CREATE TABLE AS only copies columns and data — pg_dump is the correct tool when you need an exact structural clone.