MarzleyTech Learn

Home / Learn / SQL / Creating tables: data types and constraints

Creating tables: data types and constraints

So far you've used tables that already existed. Now you'll design and create your own, with rules (constraints) that stop bad data getting in.

CREATE TABLE

SQL · runs live in the interactive lesson
CREATE TABLE Students (
  StudentID   INTEGER PRIMARY KEY,
  AdmNo       TEXT    NOT NULL UNIQUE,
  Name        TEXT    NOT NULL,
  Form        INTEGER CHECK (Form BETWEEN 1 AND 4),
  Phone       TEXT,
  FeesBalance INTEGER DEFAULT 0,
  JoinedOn    TEXT    DEFAULT (DATE('now'))
);

INSERT INTO Students (AdmNo, Name, Form, Phone) VALUES ('ADM001', 'Amina Hassan', 2, '0712345678');
INSERT INTO Students (AdmNo, Name, Form) VALUES ('ADM002', 'Brian Otieno', 3);
SELECT * FROM Students;

Common data types

KindMySQLSQLiteExample
Whole numbersINT, BIGINTINTEGER42
Money / exact decimalsDECIMAL(10,2)NUMERIC1500.50
Decimals (approximate)FLOAT, DOUBLEREAL3.14
Short textVARCHAR(100)TEXT'Nairobi'
Long textTEXTTEXTa description
Date / timeDATE, DATETIMETEXT ('2026-09-28')
True/falseBOOLEAN (TINYINT)INTEGER 0/1

For money use DECIMAL (or store whole cents as integers). Never FLOAT: it can't store 0.10 exactly.

Constraints: rules the database enforces

ConstraintMeaning
PRIMARY KEYUnique id for each row (auto-numbers in SQLite; use AUTO_INCREMENT in MySQL)
NOT NULLMust have a value
UNIQUENo duplicates (e.g. emails, admission numbers)
DEFAULTValue used if none is given
CHECKMust pass a condition
FOREIGN KEYMust match a row in another table

Watch the database refuse bad data (each failing line shows an error, so run them one at a time by deleting the others):

SQL
INSERT INTO Students (AdmNo, Name, Form) VALUES ('ADM001', 'Copycat', 1);   -- UNIQUE fails
INSERT INTO Students (AdmNo, Form) VALUES ('ADM009', 2);                   -- NOT NULL fails
INSERT INTO Students (AdmNo, Name, Form) VALUES ('ADM010', 'Zed', 7);      -- CHECK fails

Foreign keys: linking tables safely

SQL · runs live in the interactive lesson
PRAGMA foreign_keys = ON;   -- SQLite needs this. MySQL (InnoDB) checks foreign keys automatically.

CREATE TABLE Students (StudentID INTEGER PRIMARY KEY, Name TEXT NOT NULL);
CREATE TABLE Payments (
  PaymentID  INTEGER PRIMARY KEY,
  StudentID  INTEGER NOT NULL REFERENCES Students(StudentID) ON DELETE CASCADE,
  Amount     INTEGER NOT NULL CHECK (Amount > 0),
  MpesaCode  TEXT UNIQUE,
  PaidOn     TEXT NOT NULL
);

INSERT INTO Students (Name) VALUES ('Amina'), ('Brian');
INSERT INTO Payments (StudentID, Amount, MpesaCode, PaidOn) VALUES
  (1, 15000, 'SJK1A2B3C4', '2026-09-01'),
  (1, 5000,  'SJK5D6E7F8', '2026-09-15'),
  (2, 20000, 'SJL9G8H7I6', '2026-09-02');

SELECT s.Name, SUM(p.Amount) AS Paid, COUNT(*) AS Payments
FROM Students s JOIN Payments p ON p.StudentID = s.StudentID
GROUP BY s.Name;

ON DELETE CASCADE means: if a student is deleted, their payments are deleted too. Other options: RESTRICT (refuse to delete) or SET NULL.

Changing and removing tables

SQL · runs live in the interactive lesson
CREATE TABLE Suppliers (SupplierID INTEGER PRIMARY KEY, Name TEXT NOT NULL);
ALTER TABLE Suppliers ADD COLUMN Phone TEXT;
ALTER TABLE Suppliers RENAME TO Vendors;
INSERT INTO Vendors (Name, Phone) VALUES ('Bidco', '0700111222');
SELECT * FROM Vendors;
DROP TABLE Vendors;              -- deletes the table and ALL its data. Careful!
  • DROP TABLE IF EXISTS x avoids an error if it's already gone.
  • There's no undo. On real systems, back up first.

The same table in MySQL

SQL
CREATE TABLE students (
  id INT AUTO_INCREMENT PRIMARY KEY,
  adm_no VARCHAR(20) NOT NULL UNIQUE,
  name VARCHAR(100) NOT NULL,
  form TINYINT CHECK (form BETWEEN 1 AND 4),
  fees_balance DECIMAL(10,2) DEFAULT 0,
  joined_on DATE DEFAULT (CURRENT_DATE)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

utf8mb4 supports all characters including emoji. Use it for every table.

Why table design matters

A well-designed database keeps data correct for years: no duplicate customers, no orders for products that don't exist, no negative prices, no missing phone numbers. A badly designed one causes endless bugs and reports nobody trusts. Designing tables (choosing columns, types, keys and constraints) is a core job for back-end developers and database administrators, and a common topic in interviews and university projects.

Designing tables step by step: a school example

  1. List the things (entities): Students, Classes, Subjects, Marks, Fee payments.
  2. List each entity's attributes: Student → admission number, name, date of birth, class, parent phone.
  3. Choose primary keys: StudentID (auto number), or the admission number if it never changes.
  4. Find relationships: one class has many students; a student has many marks; a mark belongs to one subject.
  5. Add foreign keys for those relationships.
  6. Add constraints: required fields, unique admission numbers, marks between 0 and 100.
SQL · runs live in the interactive lesson
CREATE TABLE Classes (
  ClassID   INTEGER PRIMARY KEY,
  Name      TEXT NOT NULL UNIQUE              -- e.g. 'Form 2 East'
);
CREATE TABLE Students (
  StudentID    INTEGER PRIMARY KEY,
  AdmissionNo  TEXT NOT NULL UNIQUE,
  FullName     TEXT NOT NULL,
  DateOfBirth  TEXT,
  ClassID      INTEGER NOT NULL REFERENCES Classes(ClassID),
  ParentPhone  TEXT CHECK (length(ParentPhone) = 10)
);
CREATE TABLE Subjects (
  SubjectID INTEGER PRIMARY KEY,
  Name      TEXT NOT NULL UNIQUE
);
CREATE TABLE Marks (
  StudentID INTEGER NOT NULL REFERENCES Students(StudentID),
  SubjectID INTEGER NOT NULL REFERENCES Subjects(SubjectID),
  Term      TEXT NOT NULL,
  Score     INTEGER NOT NULL CHECK (Score BETWEEN 0 AND 100),
  PRIMARY KEY (StudentID, SubjectID, Term)     -- one mark per student, subject and term
);

INSERT INTO Classes (Name) VALUES ('Form 2 East'), ('Form 2 West');
INSERT INTO Subjects (Name) VALUES ('Maths'), ('English');
INSERT INTO Students (AdmissionNo, FullName, ClassID, ParentPhone) VALUES
  ('ADM001', 'Baraka Mutua', 1, '0712000101'),
  ('ADM002', 'Neema Chebet', 2, '0712000102');
INSERT INTO Marks VALUES (1, 1, '2026T3', 78), (1, 2, '2026T3', 65), (2, 1, '2026T3', 91);

SELECT s.FullName, c.Name AS Class, sub.Name AS Subject, m.Score
FROM Marks m
JOIN Students s ON s.StudentID = m.StudentID
JOIN Classes c ON c.ClassID = s.ClassID
JOIN Subjects sub ON sub.SubjectID = m.SubjectID
ORDER BY s.FullName, sub.Name;

The composite primary key (StudentID, SubjectID, Term) stops the same mark being entered twice.

Watching constraints protect your data

SQL · runs live in the interactive lesson
CREATE TABLE Accounts (
  AccountID INTEGER PRIMARY KEY,
  Phone     TEXT NOT NULL UNIQUE,
  Balance   INTEGER NOT NULL DEFAULT 0 CHECK (Balance >= 0)
);
INSERT INTO Accounts (Phone) VALUES ('0712000001');
INSERT INTO Accounts (Phone, Balance) VALUES ('0712000002', 500);
SELECT * FROM Accounts;
-- Each line below would be rejected. Remove the -- from one line at a time and run:
-- INSERT INTO Accounts (Phone) VALUES ('0712000001')                (UNIQUE fails)
-- INSERT INTO Accounts (Phone, Balance) VALUES ('0712000003', -50)  (CHECK fails)
-- INSERT INTO Accounts (Balance) VALUES (100)                       (NOT NULL fails)

Try un-commenting one line at a time to see the error. Constraints are the database refusing bad data, even if the application code has a bug.

Choosing good data types

DataGood choiceWhy
MoneyINTEGER cents (SQLite) or DECIMAL(12,2) (MySQL/PostgreSQL)Floats cause rounding errors
Phone numbers, ID numbers, admission numbersTEXT / VARCHARLeading zeros; no maths is done on them
DatesDATE / DATETIME (or ISO text in SQLite)Correct sorting and date functions
Yes/noBOOLEAN (MySQL TINYINT(1), SQLite 0/1)Clear meaning
Long descriptionsTEXTNo small length limit
Fixed set of valuesTEXT with CHECK (Status IN ('pending','paid','cancelled')) or a lookup tablePrevents typos

ON DELETE: what happens to related rows

SQL
CREATE TABLE Orders (
  OrderID    INTEGER PRIMARY KEY,
  CustomerID INTEGER NOT NULL REFERENCES Customers(CustomerID) ON DELETE RESTRICT,
  ...
);
CREATE TABLE OrderItems (
  OrderID   INTEGER NOT NULL REFERENCES Orders(OrderID) ON DELETE CASCADE,
  ...
);
OptionMeaningUse
RESTRICT / NO ACTIONRefuse to delete a parent that has childrenCan't delete a customer who has orders
CASCADEDelete children automaticallyDeleting an order removes its items
SET NULLSet the foreign key to NULLKeep posts when an author account is removed

Note: SQLite only enforces foreign keys after PRAGMA foreign_keys = ON; (many apps run this on every connection).

Altering tables safely

SQL · runs live in the interactive lesson
CREATE TABLE Suppliers (SupplierID INTEGER PRIMARY KEY, Name TEXT NOT NULL);
ALTER TABLE Suppliers ADD COLUMN Phone TEXT;
ALTER TABLE Suppliers ADD COLUMN Active INTEGER NOT NULL DEFAULT 1;
ALTER TABLE Suppliers RENAME COLUMN Name TO CompanyName;
INSERT INTO Suppliers (CompanyName, Phone) VALUES ('Nairobi Electronics Ltd', '0722000000');
SELECT * FROM Suppliers;

On live systems, changes to tables are done through migrations: versioned scripts (Laravel, Django and Prisma all have migration tools) so every developer's and server's database stays in sync. Always back up before changing production tables.

Naming conventions

  • Pick one style and stick to it: PascalCase (Customers, OrderDate) or snake_case (customers, order_date).
  • Table names: plural (Customers) or singular (Customer), but consistent.
  • Primary key: ID or TableNameID (CustomerID).
  • Foreign key: same name as the primary key it refers to.
  • Avoid spaces and reserved words (Order, Group, User need quoting in some databases).

Common mistakes

MistakeFix
Phone numbers stored as INTEGERTEXT
Money as FLOATDECIMAL or integer cents
No primary keyEvery table needs one
Comma-separated lists in one column ("Maths,English")A separate linking table
Same data in several tablesNormalise: store once, link by ID
No constraints, trusting the appAdd NOT NULL, UNIQUE, CHECK, foreign keys

Practice

  1. Design tables for a chama: Members, Contributions (member, amount, date), Loans and Repayments.
  2. Add CHECK constraints so contributions are positive and loan status is one of 'active', 'repaid', 'defaulted'.
  3. Create a linking table for a many-to-many relationship between Students and Clubs.
  4. Add a CreatedAt column with a default of the current time (DEFAULT CURRENT_TIMESTAMP).
  5. Try inserting invalid data and read each error message.
Think about it: Why is storing "Maths,English,Kiswahili" in one Subjects column of a Students table a poor design?Show answer

You can't easily count students per subject, enforce valid subject names, add marks per subject, or update one subject without string manipulation. Queries become slow and error-prone. A separate linking table (StudentSubjects with StudentID and SubjectID) stores one row per combination and works with joins, constraints and indexes.

Check yourself

  1. Which constraint stops duplicate values in a column?

    Show answer

    UNIQUE

  2. Which constraint means a column must always have a value?

    Show answer

    NOT NULL

  3. Which MySQL data type should you use for money?

    Show answer

    DECIMAL

  4. Which command deletes a whole table and its data?

    Show answer

    DROP TABLE

  5. Which command adds a column to an existing table?

    Show answer

    ALTER TABLE

  6. What is a primary key made of two or more columns called? (two words)

    Show answer

    composite key

  7. Which ON DELETE option automatically deletes child rows?

    Show answer

    CASCADE

  8. Which SQLite command must be run to enforce foreign keys? (PRAGMA ...)

    Show answer

    PRAGMA foreign_keys = ON

  9. What data type should phone numbers use: INTEGER or TEXT?

    Show answer

    TEXT

Exercise

Create a table Books with BookID INTEGER PRIMARY KEY, Title TEXT NOT NULL and Price INTEGER, then SELECT * FROM Books.

Do this exercise in the live editor

Lesson 9 of 16 in SQL · Printable course notes