CREATE and ALTER
On This Lesson 11 topics
Lesson Overview
Lesson 1 split SQL into DDL (structure) and DML (rows). This lesson is the first half of DDL: building the shelves. You will turn the tables you designed in BEG-049 into real tables with CREATE TABLE, and change them later with ALTER TABLE. You will also meet DROP TABLE, the statement that removes a shelf together with every book on it.
In the library picture, CREATE TABLE is the carpenter's order: "one shelf called books, with slots for a title, an author, and a year." The database will refuse anything that does not fit those slots, which is exactly why they are worth getting right.
By the end you will have the four-table library schema (authors, books, members, loans) that the rest of the module queries.
This lesson builds on Lesson 1 (what DDL is) and BEG-049 (tables, primary keys, foreign keys).
Learning Objectives
By the end of this lesson you will be able to:
- Write a
CREATE TABLEstatement with column names, types, and a primary key. - Choose a sensible type for numbers, text, dates, and yes/no values.
- Use
NOT NULL,DEFAULT, andREFERENCES(a foreign key) in a column definition. - Create tables in an order that lets foreign keys work.
- Add, rename, and drop a column with
ALTER TABLE, and predict when adding a column fails. - Explain what
DROP TABLEdoes and why it is dangerous.
Written Explanation
CREATE TABLE
The general shape:
CREATE TABLE table_name (
column_name type [constraints],
column_name type [constraints],
...
);Each column gets a name, a type, and optional constraints: rules the database enforces. Columns are separated by commas, and there is no comma after the last one.
A first complete example, from the library:
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
country TEXT
);Reading it line by line: id holds whole numbers and is the primary key (unique, never empty); name holds text and must be filled in; country holds text and may be empty (NULL).
Types you need now
| Type | Holds | Library example |
|---|---|---|
INTEGER | Whole numbers | published_year, ids |
TEXT | Text of any length | title, name |
DATE | A calendar date | loaned_on, due_on |
BOOLEAN | true or false | active |
NUMERIC(p, s) | Exact decimals | A fine of 1.50 |
PostgreSQL has many more types (VARCHAR(n) for text with a length limit, TIMESTAMP for date and time, BIGINT for large whole numbers). Other databases name and treat some types differently, so check your product. Choose the type that matches the meaning: a year is a number, a date is a DATE, never a text like '4 May', because the database can then compare and sort it correctly.
Constraints you need now
| Constraint | Meaning |
|---|---|
PRIMARY KEY | Unique and not empty: identifies one row (BEG-049) |
NOT NULL | The column must always have a value |
DEFAULT value | The value used when an INSERT leaves the column out |
REFERENCES table (column) | A foreign key: values must exist in that other table's column |
An example with a foreign key:
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
author_id INTEGER NOT NULL REFERENCES authors (id),
published_year INTEGER
);author_id can only hold an id that exists in authors. That is what keeps a book from pointing at an author who does not exist.
Order matters. A foreign key points at a table that must already exist, so create the "one" side before the "many" side: authors before books, and books and members before loans.
ALTER TABLE
Requirements change after a table is created. ALTER TABLE changes the definition without recreating the table. The PostgreSQL manual lists these operations: add columns, remove columns, add and remove constraints, change default values, change data types, rename columns, and rename tables. The common ones:
ALTER TABLE publishers ADD COLUMN website TEXT;
ALTER TABLE publishers RENAME COLUMN city TO hq_city;
ALTER TABLE publishers DROP COLUMN founded;Adding a column to a table that already has rows. The new column appears in every existing row. It holds the DEFAULT if you gave one, otherwise NULL. That has a consequence: ADD COLUMN ... NOT NULL with no default cannot work on a table that already holds rows, because the existing rows would have to be NULL. The fix is to give a default (or to add the column as nullable, fill it with UPDATE, then add NOT NULL).
DROP COLUMN deletes that column's data in every row. Nothing asks "are you sure".
DROP TABLE
DROP TABLE publishers;This removes the table, its structure, and all of its rows. It is the DDL statement to treat with the most respect. PostgreSQL will refuse if another table's foreign key still depends on the target, and says so; the hint offers CASCADE, which also removes the dependent constraints. Do not reach for CASCADE just to make an error go away: read what it would remove first.
CREATE TABLE IF NOT EXISTS and DROP TABLE IF EXISTS skip the work (with a notice) instead of raising an error. They are handy in setup scripts, but they can also hide the fact that a table was not what you thought it was.
Examples
All examples were run on PostgreSQL 18.3 with psql.
Example 1: the library schema (simple)
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
country TEXT
);
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
author_id INTEGER NOT NULL REFERENCES authors (id),
published_year INTEGER
);
CREATE TABLE members (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
joined_on DATE NOT NULL
);
CREATE TABLE loans (
id INTEGER PRIMARY KEY,
book_id INTEGER NOT NULL REFERENCES books (id),
member_id INTEGER NOT NULL REFERENCES members (id),
loaned_on DATE NOT NULL,
due_on DATE NOT NULL,
returned_on DATE
);Each statement answered CREATE TABLE. Note that returned_on is the only column without NOT NULL on loans: a book that is still out has no return date yet, and NULL is how we record that. This is the schema from BEG-049 (books, authors, members, loans) written as SQL, and the rest of the module uses it.
Example 2: growing a table with ALTER (practical)
A new requirement arrives: the library wants to record publishers. Create the table, then extend it.
CREATE TABLE publishers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
city TEXT
);
ALTER TABLE publishers ADD COLUMN website TEXT;psql's \d publishers command (a psql feature, not SQL) describes the table:
Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+---------
id | integer | | not null |
name | text | | not null |
city | text | | |
website | text | | |
Indexes:
"publishers_pkey" PRIMARY KEY, btree (id)PostgreSQL created an index to enforce the primary key, which you can ignore for now. After a row exists, we try more changes:
INSERT INTO publishers (id, name, city) VALUES (1, 'Orbit Press', 'Cairo');
ALTER TABLE publishers ADD COLUMN founded INTEGER DEFAULT 2000;
SELECT * FROM publishers; id | name | city | website | founded
----+-------------+-------+---------+---------
1 | Orbit Press | Cairo | | 2000
(1 row)The existing row received the default, 2000, and its website is NULL. Next, NOT NULL with no default:
ALTER TABLE publishers ADD COLUMN email TEXT NOT NULL;ERROR: column "email" of relation "publishers" contains null valuesThe row already exists, so the new column would have to be NULL there. The version with a default works:
ALTER TABLE publishers ADD COLUMN active BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE publishers RENAME COLUMN city TO hq_city;
ALTER TABLE publishers DROP COLUMN founded;
SELECT * FROM publishers; id | name | hq_city | website | active
----+-------------+---------+---------+--------
1 | Orbit Press | Cairo | | t
(1 row)The t is how psql displays true.
Example 3: errors you will meet and DROP (realistic)
Creating a table whose foreign key points to something that does not exist, then dropping a parent table that others depend on:
CREATE TABLE copies (id INTEGER PRIMARY KEY, book_id INTEGER REFERENCES nobooks (id));
DROP TABLE authors;ERROR: relation "nobooks" does not exist
ERROR: cannot drop table authors because other objects depend on it
DETAIL: constraint books_author_id_fkey on table books depends on table authors
HINT: Use DROP ... CASCADE to drop the dependent objects too.Both errors protect you: the first stops a foreign key from pointing at nothing, the second stops you from orphaning books. Repeating a drop shows the IF EXISTS behaviour:
DROP TABLE publishers;
DROP TABLE publishers;
DROP TABLE IF EXISTS publishers;DROP TABLE
ERROR: table "publishers" does not exist
NOTICE: table "publishers" does not exist, skipping
DROP TABLEBackend Insight
In a real project the CREATE TABLE statements live in files that are saved in version control, not typed into a console. They are the record of what the database should look like, and they are applied the same way on every developer's machine and on the server. That is also why ALTER is usually written as a numbered script ("add publishers table", "add website column") rather than edited by hand: each change can be reviewed, repeated, and traced.
Types and constraints are your first line of defence. A NOT NULL on name and a foreign key on author_id mean the database itself rejects bad data, even if a bug in the application code tries to save it. Constraints written in the schema protect every program that ever connects, not just the one you are writing today.
Common Mistakes
Mistake 1: A trailing comma or a missing comma
A comma after the last column, or a missing comma between two columns, gives a syntax error pointing near the spot. Check the line above the one the error names.
Mistake 2: Creating tables in the wrong order
REFERENCES needs the other table to exist already, as in Example 3. Create parents first, or add the foreign key later with ALTER TABLE.
Mistake 3: Adding NOT NULL without a default on a table with rows
It fails if any rows exist. Use a DEFAULT, or add the column as nullable, fill it with UPDATE, then add NOT NULL.
Mistake 4: Storing dates or numbers as TEXT
A TEXT year sorts '1000' before '999', and a text date cannot be compared reliably. Use INTEGER and DATE.
Mistake 5: Using DROP to "start over" on a real database
DROP TABLE and DROP COLUMN destroy data with no confirmation. On practice databases it is a fine way to reset; on a real one, it is an incident.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Write CREATE TABLE for shelves with id (integer primary key), label (text, required), and floor (integer, optional).
Show solution A1
A1.
CREATE TABLE shelves (
id INTEGER PRIMARY KEY,
label TEXT NOT NULL,
floor INTEGER
);A2. Which type would you choose for: (a) a member's birthday, (b) the number of pages, (c) whether a book is a reference copy, (d) a book title?
Show solution A2
A2. (a) DATE. (b) INTEGER. (c) BOOLEAN. (d) TEXT (or VARCHAR(n)).
A3. What does NOT NULL do? What does DEFAULT 0 do?
Show solution A3
A3. NOT NULL makes the column required: an insert or update that leaves it empty is rejected. DEFAULT 0 supplies 0 whenever an insert omits the column.
Level B: Application
B1. Create reviews with id, book_id (must exist in books), rating (integer, required), and comment (optional text). In which order relative to books do you create it?
Show solution B1
B1.
CREATE TABLE reviews (
id INTEGER PRIMARY KEY,
book_id INTEGER NOT NULL REFERENCES books (id),
rating INTEGER NOT NULL,
comment TEXT
);Create it after books, because the foreign key needs books to exist.
B2. publishers already contains rows. Add a text column country that is required, defaulting to 'Unknown'. Then say what the existing rows hold.
Show solution B2
B2. ALTER TABLE publishers ADD COLUMN country TEXT NOT NULL DEFAULT 'Unknown'; Existing rows hold 'Unknown'.
B3. Rename shelves.label to title, then remove the floor column. What happens to the data in floor?
Show solution B3
B3.
ALTER TABLE shelves RENAME COLUMN label TO title;
ALTER TABLE shelves DROP COLUMN floor;The floor data is deleted from every row with the column.
Level C: Challenge
C1. members has 500 rows. You must add a required email column. Describe a safe sequence of steps that never needs a fake default to stay forever.
Show solution C1
C1. (1) ALTER TABLE members ADD COLUMN email TEXT; (nullable, so existing rows get NULL). (2) Fill in real emails with UPDATE, from a source such as the application or a spreadsheet. (3) Once no NULL remains, run ALTER TABLE members ALTER COLUMN email SET NOT NULL;, which the manual says checks existing data immediately.
C2. A teammate runs DROP TABLE authors; on a database where books has a foreign key to it, gets an error with a CASCADE hint, and wants to add CASCADE "to fix it". What do you tell them, and what do you check first?
Show solution C2
C2. Do not add it blindly. The error is protecting books. First read the DETAIL line to see what depends on authors, and ask whether the table really should be dropped at all. CASCADE removes the dependent foreign-key constraints, which changes books' guarantees. If the drop is intended, preserve a backup and make the change deliberately.
Mini Challenge
Design and create a fines table for the library: each fine belongs to one loan, has an amount (exact decimal, required), a paid flag that defaults to false, and an optional note. Then, after creating it, add a fined_on date column that is required and defaults to the date '2025-01-01' for any old rows.
Write the statements, run them if you have a PostgreSQL client available, and describe the table with your client's table-describing feature (menus and commands differ between tools).
Check your work: loan_id references loans (id) and loans was created first; the amount uses NUMERIC; paid is BOOLEAN NOT NULL DEFAULT FALSE; fined_on was added with ALTER TABLE ... ADD COLUMN.
Summary & Next Lesson
Key takeaways
CREATE TABLEdefines columns with a name, a type, and optional constraints.- Choose types by meaning:
INTEGER,TEXT,DATE,BOOLEAN,NUMERIC. PRIMARY KEY,NOT NULL,DEFAULT, andREFERENCESmake the database enforce your design.- Create parent tables before the tables that reference them.
ALTER TABLEadds, renames, and drops columns; addingNOT NULLwithout a default fails when rows already exist.DROP TABLEandDROP COLUMNdelete data permanently.
Next lesson
The shelves exist but are empty. The next lesson, "INSERT, UPDATE, DELETE", moves to DML: adding, changing, and removing rows, and the WHERE habit that keeps those changes safe.
Self-check
- Which part of a column definition says it must always have a value?
- Why create
authorsbeforebooks? - What happens to existing rows when you add a column with
DEFAULT 'Unknown'? - Why does
ADD COLUMN email TEXT NOT NULLfail on a table with rows? - What does
DROP TABLEremove?
Show answers
NOT NULL.bookshas a foreign key toauthors, which must exist first.- They all receive
'Unknown'. - Existing rows would need a value for the new column, but none is given, so they would be
NULL. - The table, its structure, and every row in it.
References
- PostgreSQL documentation, Modifying Tables. Supports the list of
ALTER TABLEoperations, new columns taking the default orNULL, andSET NOT NULLbeing checked against existing data. - PostgreSQL documentation, Data Types. Supports the type names
integer,numeric,boolean,date,timestamp,text, andvarchar(n). - All statements and error messages were produced by running them on PostgreSQL 18.3. The advice about migration scripts is general practice, not taken from a cited page.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.