DevLearningTools

LEARN · SQL

Creating Tables and Constraints

CREATE TABLE defines a table's columns, types, and rules. Learn NOT NULL, UNIQUE, CHECK, and DEFAULT constraints, and how to alter a table after it already has data.

Every table used throughout this course started with a CREATE TABLE statement. This lesson covers it properly, column definitions, and the constraints beyond primary and foreign keys that stop bad data from ever being stored in the first place.

Learning Objectives

  • Write a CREATE TABLE statement with columns, types, and constraints.
  • Apply NOT NULL, UNIQUE, CHECK, and DEFAULT to enforce rules at the database level.
  • Modify an existing table's structure with ALTER TABLE.

Define Columns

Name, type, per column

Add Constraints

NOT NULL, UNIQUE, CHECK, DEFAULT

Table Created

Rules enforced from day one

A Full CREATE TABLE Example

Every piece here, column types, NOT NULL, UNIQUE, CHECK, DEFAULT, and the foreign key, is standard SQL and works with this same syntax across MySQL, PostgreSQL, SQL Server, and SQLite.

MySQL / PostgreSQL / SQLite / SQL Server (standard SQL)
CREATE TABLE employees (
  id INTEGER PRIMARY KEY,
  first_name VARCHAR(100) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  salary DECIMAL(10,2) CHECK (salary >= 0),
  status VARCHAR(20) DEFAULT 'active',
  department_id INTEGER,
  FOREIGN KEY (department_id) REFERENCES departments(id)
);

The Core Constraints

ConstraintWhat it enforces
NOT NULLThis column must always have a value, never NULL
UNIQUENo two rows can share the same value in this column
CHECK (condition)Every value must satisfy a custom condition
DEFAULT valueIf no value is provided on INSERT, use this one automatically

CHECK Constraints

CHECK lets you enforce rules that go beyond type and uniqueness, a salary can never be negative, a status must be one of a known set of values, directly in the schema rather than only in application code.

MySQL / PostgreSQL / SQLite / SQL Server
salary DECIMAL(10,2) CHECK (salary >= 0),
status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'on_leave'))
NOTE

MySQL versions before 8.0.16 (released 2019) accepted CHECK constraint syntax but silently never enforced it. Every version in common use today enforces it correctly, but it's a real historical gotcha worth knowing if you ever encounter an older MySQL instance.

Altering an Existing Table

ALTER TABLE changes a table's structure after it already exists, often after it already has data. Adding a column is well supported everywhere, but more complex changes, dropping a column, changing a column's type, used to be far more limited in SQLite specifically than in the other three databases, worth checking your SQLite version if a more advanced ALTER operation fails.

MySQL / PostgreSQL / SQL Server
ALTER TABLE employees
ADD COLUMN hire_date DATE;
SQLite
ALTER TABLE employees
ADD COLUMN hire_date DATE;
-- SQLite supports adding a column this way, but has historically had
-- limited support for other ALTER operations like dropping or renaming
-- a column, improved significantly in SQLite 3.25+ (2018) and later.

Dropping a Table

DROP TABLE removes the table entirely, structure and all data, with no undo outside of a backup. IF EXISTS prevents an error if the table doesn't exist, useful in setup scripts that might run more than once.

MySQL / PostgreSQL / SQLite / SQL Server
DROP TABLE IF EXISTS employees;

Dropping an Entire Database

DROP TABLEDROP DATABASE
RemovesOne table, its structure and dataEvery table in the entire database
ScopeInside the database you're already connected toThe whole database itself
Typical useRemoving one table during development or cleanupTearing down an entire test or temporary database
MySQL / PostgreSQL / SQLite / SQL Server
DROP DATABASE IF EXISTS company_db;
NOTE

SQLite doesn't have a DROP DATABASE statement at all, since a SQLite "database" is just a single file, deleting that file from the filesystem is the equivalent action. On MySQL, PostgreSQL, and SQL Server, DROP DATABASE is an enormously destructive, irreversible statement, it removes every table, every row, and every view inside that database in one command, with no confirmation prompt.

All Five, Side by Side

It's worth being explicit about that last row: there's no such thing as DELETE DATABASE or TRUNCATE DATABASE in SQL. DELETE and TRUNCATE are both scoped to one table at a time, no matter how many rows or how much of it they remove. DROP DATABASE is the only statement in this course that operates on an entire database at once, which is exactly why it's the most dangerous one here.

StatementRemovesStructure kept?
DELETE FROM table WHERE ...Specific rows you choose, in one tableYes, the table and its unmatched rows remain
TRUNCATE TABLEEvery row in one tableYes, the empty table remains
DROP TABLEOne table entirelyNo, the table itself is gone
DROP DATABASEEvery table in a databaseNo, the whole database is gone
"DELETE DATABASE" / "TRUNCATE DATABASE"Neither existsDELETE and TRUNCATE both only ever operate on a single table, never a whole database, on any of the four databases in this course

Frequently Asked Questions

Can I add a NOT NULL constraint to a column that already has NULL values in it?

No, every database rejects this until the existing NULLs are resolved, either by updating them to a real value first, or by adding the constraint together with a DEFAULT that backfills existing rows, depending on the database's specific ALTER TABLE syntax.

What's the difference between a column-level and table-level constraint?

A column-level constraint is written directly after that column's type, like email VARCHAR(255) UNIQUE. A table-level constraint is written as its own line, like UNIQUE (first_name, last_name), used when a constraint spans more than one column.

Does DEFAULT apply retroactively to existing rows when added later?

No, it only applies to future INSERTs that omit that column. Existing rows keep whatever value (including NULL) they already had, unless you also run an UPDATE to backfill them.

Common Beginner Mistakes

Relying only on application code to validate data, with no database-level constraints

Application code can have bugs, can be bypassed by a direct database connection, or might not be the only thing writing to the table someday. Constraints at the database level are a last line of defense that holds regardless of what wrote the data.

Assuming CHECK constraints are enforced on every MySQL version

They're silently ignored on MySQL versions before 8.0.16. If a CHECK constraint seems to be doing nothing, confirm the MySQL version first.

Running DROP TABLE without IF EXISTS in a script that might run more than once

The second run fails with a table-doesn't-exist error and stops the script. IF EXISTS makes the statement safely re-runnable.

Best Practices

  • Enforce rules at the database level with constraints, don't rely solely on application code to catch bad data.
  • Use CHECK for any rule that's genuinely about the data's validity, not just business logic that might change often.
  • Use DEFAULT for columns that have an obvious, sensible value most of the time, like a status defaulting to 'active'.
  • Always use DROP TABLE IF EXISTS and CREATE TABLE IF NOT EXISTS in setup/migration scripts meant to be re-runnable.

Interview Questions

Write a CREATE TABLE for a products table where price can't be negative and name is required and unique.

CREATE TABLE products (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, price DECIMAL(10,2) CHECK (price >= 0));

Why might a CHECK constraint silently do nothing on a production MySQL server?

If the server is running a MySQL version older than 8.0.16, CHECK constraints are parsed and accepted without error, but never actually enforced, a known historical gap in MySQL's constraint support that was fixed in that release.

What happens if you try to add a NOT NULL constraint to a column that currently contains NULL values?

The ALTER TABLE statement fails, the database can't retroactively enforce a rule that existing data already violates. The NULLs have to be resolved first, either by updating them to real values or by combining the constraint change with a default/backfill, before NOT NULL can be applied.

Summary

This lesson covered a full CREATE TABLE statement with NOT NULL, UNIQUE, CHECK, and DEFAULT constraints, a historical MySQL gotcha with CHECK, modifying an existing table with ALTER TABLE, and safely dropping a table with DROP TABLE IF EXISTS.

What's Next?

The next lesson covers indexes, how they make queries fast, and the tradeoffs that come with adding them.

</>

Ready to Practice SQL?

Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.

SQLite / PostgreSQL

Fast, no server, no ads.

Open DB Fiddle ↗

MySQL / SQL Server

Switch databases with one dropdown.

Open SQL Fiddle ↗

Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.