Creating Tables
CREATE TABLE is where a schema becomes real. It names the table, lists its columns with their types, and can attach constraints — rules the database will enforce on every row — right alongside each column. This page walks through the full syntax using the sample e-commerce schema (customers, products, orders, order_items) that recurs throughout these PostgreSQL pages, plus a few shortcuts for creating tables from existing structure or query results.
Basic syntax
At minimum, CREATE TABLE needs a name and a comma-separated list of columns, each with a data type. Constraints such as PRIMARY KEY, NOT NULL, UNIQUE, and CHECK can be written directly after a column's type (a column constraint), or as a separate line at the end of the list (a table constraint) when a rule spans more than one column.
The products table
CREATE TABLE products ( product_id SERIAL PRIMARY KEY, sku TEXT NOT NULL UNIQUE, name TEXT NOT NULL, description TEXT, unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0), stock_qty INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() );
Each piece here is doing real work: SERIAL PRIMARY KEY auto-generates a unique integer id, UNIQUE keeps sku values from colliding across rows, the CHECK constraint stops a negative price from ever being stored, and DEFAULT now() timestamps a row automatically at insert time if no value is supplied.
The same pattern extends across the rest of the sample schema — customers, orders, and order_items each declare their own columns and constraints, with foreign keys tying them together (covered in full on the foreign-key page).
The rest of the sample schema
CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE orders ( order_id SERIAL PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(customer_id), order_date TIMESTAMPTZ NOT NULL DEFAULT now(), status TEXT NOT NULL DEFAULT 'pending' ); CREATE TABLE order_items ( order_item_id SERIAL PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(order_id), product_id INTEGER NOT NULL REFERENCES products(product_id), quantity INTEGER NOT NULL CHECK (quantity > 0), unit_price NUMERIC(10, 2) NOT NULL );
IF NOT EXISTS
Running CREATE TABLE twice for the same name normally raises an error. Adding IF NOT EXISTS makes the statement a no-op when the table already exists, instead of failing — handy in setup scripts and migrations that might run more than once.
Safe to re-run
CREATE TABLE IF NOT EXISTS products ( product_id SERIAL PRIMARY KEY, sku TEXT NOT NULL UNIQUE, name TEXT NOT NULL );
NOTICE: relation "products" already exists, skipping CREATE TABLE
Copying structure with LIKE
CREATE TABLE ... (LIKE other_table) creates a new, empty table with the same column names and types as an existing one. It is a quick way to build a staging table or an archive table that mirrors a production table's shape without retyping every column.
Copying just the column structure
CREATE TABLE products_staging (LIKE products);
CREATE TABLE AS SELECT
CREATE TABLE ... AS SELECT (often abbreviated CTAS) builds a brand-new table populated with the result of a query, right at creation time. This is useful for snapshotting a report, materializing an expensive aggregation, or spinning up a working copy of a filtered subset of data.
Snapshotting high-value orders
CREATE TABLE big_orders AS SELECT order_id, customer_id, order_date FROM orders WHERE status = 'completed';
SELECT 1204
CREATE TABLE names a table and lists its columns, types, and constraints.
Column constraints (NOT NULL, UNIQUE, CHECK) attach right after a type; table constraints stand on their own line for rules spanning multiple columns.
IF NOT EXISTS makes table creation safe to re-run.
LIKE copies column structure from an existing table; CREATE TABLE AS SELECT populates a new table from a query.