PostgreSQL Lesson 10 – Creating Tables | Dataplexa

Creating Tables

Tables are the heart of every PostgreSQL database. Every piece of data you store, query, and analyse lives in a table. In this lesson you will learn how to design and create tables properly — choosing column names, assigning the right data types, and building the five tables that make up the Dataplexa Store dataset you already have loaded. Understanding how these tables were built gives you the foundation to design your own tables confidently from scratch.

The CREATE TABLE Statement

You create a table using CREATE TABLE followed by the table name and a list of column definitions inside parentheses. Each column definition has three parts — the column name, the data type, and any optional constraints. The statement ends with a semicolon.

-- Basic structure of CREATE TABLE
CREATE TABLE table_name (
  column_name  data_type  constraints,
  column_name  data_type  constraints,
  column_name  data_type  constraints
);

-- Simple example — a basic products table
CREATE TABLE products (
  id          SERIAL        PRIMARY KEY,  -- auto-incrementing unique ID
  name        VARCHAR(100)  NOT NULL,     -- product name, required
  price       NUMERIC(10,2) NOT NULL,     -- price with 2 decimal places, required
  stock       INTEGER       DEFAULT 0,    -- quantity in stock, defaults to 0
  is_active   BOOLEAN       DEFAULT TRUE  -- whether product is listed, defaults to true
);
CREATE TABLE

A few things to notice here. SERIAL PRIMARY KEY on the id column means PostgreSQL automatically generates a unique number for every new row — you never have to supply an ID manually. NOT NULL means the column must always have a value — you cannot leave it empty. DEFAULT 0 means if you do not supply a value when inserting, PostgreSQL fills it in with 0 automatically.

Checking a Table Was Created

After creating a table you can immediately inspect its structure in psql using \d tablename. This shows every column, its type, whether it allows nulls, and any defaults or constraints.

-- Inspect the table structure
\d products
             Table "public.products"
  Column   |      Type       | Nullable |    Default
-----------+------------------+----------+-------------------
 id        | integer          | not null | nextval('products_id_seq')
 name      | character varying| not null |
 price     | numeric(10,2)    | not null |
 stock     | integer          |          | 0
 is_active | boolean          |          | true
Indexes:
    "products_pkey" PRIMARY KEY, btree (id)

Creating the Dataplexa Store Tables

Now let us walk through the actual tables in your course dataset. This shows you real-world table design — how columns are chosen, why certain types are used, and how tables are structured to work together. Even though these tables are already loaded in your database, reading through the CREATE statements helps you understand the design decisions behind them.

The customers table stores everyone who has made a purchase. Every customer has a unique ID, contact details, their US state, and a record of when they joined.

CREATE TABLE customers (
  id           SERIAL        PRIMARY KEY,           -- unique customer ID, auto-generated
  first_name   VARCHAR(50)   NOT NULL,              -- first name, required
  last_name    VARCHAR(50)   NOT NULL,              -- last name, required
  email        VARCHAR(100)  NOT NULL UNIQUE,       -- email must be unique across all customers
  city         VARCHAR(100),                        -- city, optional
  state        VARCHAR(2),                          -- US state code e.g. 'CA', 'NY'
  joined_date  DATE          DEFAULT CURRENT_DATE   -- date they signed up, defaults to today
);
CREATE TABLE

Notice the UNIQUE constraint on email — no two customers can share the same email address. The state column uses VARCHAR(2) because US state codes are always exactly two characters like CA, NY, or TX. The joined_date defaults to CURRENT_DATE so new customers are automatically stamped with today's date.

The products table stores the store's catalogue — every item available for purchase with its price, category, and stock level.

CREATE TABLE products (
  id           SERIAL         PRIMARY KEY,          -- unique product ID
  name         VARCHAR(100)   NOT NULL,             -- product name
  category     VARCHAR(50),                         -- e.g. 'Electronics', 'Office'
  price        NUMERIC(10,2)  NOT NULL,             -- exact price in USD
  stock_qty    INTEGER        DEFAULT 0,            -- units available, defaults to 0
  is_active    BOOLEAN        DEFAULT TRUE          -- whether the product is listed
);
CREATE TABLE

The employees table stores everyone who works at the store — their role, department, salary, and start date.

CREATE TABLE employees (
  id           SERIAL        PRIMARY KEY,           -- unique employee ID
  first_name   VARCHAR(50)   NOT NULL,              -- first name
  last_name    VARCHAR(50)   NOT NULL,              -- last name
  email        VARCHAR(100)  NOT NULL UNIQUE,       -- work email, must be unique
  department   VARCHAR(50),                         -- e.g. 'Sales', 'Engineering'
  job_title    VARCHAR(100),                        -- e.g. 'Account Manager'
  salary       NUMERIC(10,2),                       -- annual salary in USD
  hire_date    DATE          DEFAULT CURRENT_DATE   -- date they were hired
);
CREATE TABLE

The orders table records every purchase made. It links to the customers table using a customer_id foreign key — this is the relationship that lets you answer questions like "show me all orders placed by customers in Texas."

CREATE TABLE orders (
  id            SERIAL        PRIMARY KEY,          -- unique order ID
  customer_id   INTEGER       NOT NULL,             -- links to customers.id
  order_date    DATE          DEFAULT CURRENT_DATE, -- when the order was placed
  status        VARCHAR(20)   DEFAULT 'pending',    -- 'pending', 'shipped', 'delivered'
  total_amount  NUMERIC(10,2),                      -- total value of the order in USD
  FOREIGN KEY (customer_id) REFERENCES customers(id)  -- enforces the link to customers
);
CREATE TABLE

The FOREIGN KEY line at the bottom is what creates the relationship between orders and customers. PostgreSQL will now reject any order that references a customer_id that does not exist in the customers table — keeping your data consistent.

The order_items table stores the individual line items within each order — which product was purchased, how many, and at what price. One order can have many order items.

CREATE TABLE order_items (
  id          SERIAL        PRIMARY KEY,            -- unique line item ID
  order_id    INTEGER       NOT NULL,               -- links to orders.id
  product_id  INTEGER       NOT NULL,               -- links to products.id
  quantity    INTEGER       NOT NULL DEFAULT 1,     -- how many units, at least 1
  unit_price  NUMERIC(10,2) NOT NULL,               -- price at time of purchase
  FOREIGN KEY (order_id)   REFERENCES orders(id),  -- link to the order
  FOREIGN KEY (product_id) REFERENCES products(id) -- link to the product
);
CREATE TABLE

This table has two foreign keys — one linking to orders and one linking to products. This is how a many-to-many relationship between orders and products is handled in a relational database. Each row in order_items is one product line within one order.

IF NOT EXISTS — Safe Table Creation

If you run a CREATE TABLE statement for a table that already exists, PostgreSQL throws an error. Adding IF NOT EXISTS makes it safe — PostgreSQL silently skips the creation if the table is already there. This is useful in setup scripts that might be run more than once.

-- Safe version — no error if table already exists
CREATE TABLE IF NOT EXISTS products (
  id    SERIAL       PRIMARY KEY,
  name  VARCHAR(100) NOT NULL,
  price NUMERIC(10,2)
);
NOTICE: relation "products" already exists, skipping

Creating a Table from Another Table

PostgreSQL lets you create a new table by copying the structure and data from an existing query. This is useful for creating summary tables, backup copies, or test tables with a subset of real data.

-- Create a new table from a SELECT query
-- This copies both structure AND data
CREATE TABLE high_value_orders AS
SELECT id, customer_id, total_amount, order_date
FROM orders
WHERE total_amount > 500;

-- Check what was created
SELECT COUNT(*) FROM high_value_orders;
SELECT 47

count
-------
    47

Dropping a Table

Dropping a table permanently deletes it along with all its data. There is no undo. If other tables have foreign keys pointing to the table you are dropping, PostgreSQL will refuse to drop it unless you use CASCADE.

-- Drop a table permanently
DROP TABLE high_value_orders;

-- Drop only if it exists — no error if it does not
DROP TABLE IF EXISTS high_value_orders;

-- Drop a table and all tables that depend on it
DROP TABLE customers CASCADE;
DROP TABLE
DROP TABLE

The Dataplexa Store — Table Relationships at a Glance

Table Key Columns Links To Purpose
customers id, email, state All store customers
products id, price, category Product catalogue
employees id, department, salary Store staff records
orders id, customer_id, status customers Purchase records
order_items order_id, product_id, qty orders, products Line items per order

🧪 Practice Questions

Answer based on what you learned in this lesson.

1. Which data type automatically generates a unique incrementing number — commonly used for primary key columns?




2. What clause do you add to CREATE TABLE to prevent an error if the table already exists?




3. What keyword forces a DROP TABLE to also remove all dependent tables and foreign key references?




4. What constraint links a column in one table to the primary key of another table?




5. What syntax creates a new table and fills it with data from an existing query?



🎯 Quiz — Test Your Understanding

Q1. What does the NOT NULL constraint on a column mean?







Q2. In the Dataplexa Store dataset, which table connects orders and products together?







Q3. What happens when you define a FOREIGN KEY from orders.customer_id to customers.id?







Q4. What does CREATE TABLE new_table AS SELECT ... do?







Q5. Which constraint on the email column ensures no two customers can register with the same email address?






Next up: Constraints and keys — the rules that keep your data clean and trustworthy.