Module 1 › Constraints

Constraints: enforcing database integrity & business rules

Module 1, Session 5. NOT NULL, DEFAULT, CHECK, UNIQUE, composite rules and exclusion constraints. Primary and foreign keys are the next session.

Jo class mein bola, yahan nukte mein likha hai.
What I said in class, written here as key points.

← All NukteSELECT * FROM TheShiraverse

SQL Constraints

Rules the database itself enforces: NOT NULL, DEFAULT, CHECK, UNIQUE and EXCLUDE, so bad data is rejected before it ever reaches the table.

28
Interactive Scenes
1
Practice Arenas
12
Hands-on Drills
4
Interview Scenarios

Today’s Agenda — Constraints

Four pillars: rules the database enforces on every single write

Pillar 01
🛡️ Column Protectors
01 The Exam Hall Story & Why App Checks Fail
02 NOT NULL — Missing Data Defense
03 DEFAULT — Auto-Fill Fallbacks
Pillar 02
📏 Domain Boundaries
04 CHECK — Numeric & State Guardrails
05 The 3-Valued Logic NULL Trap (UNKNOWN)
06 Single & Composite UNIQUE
Pillar 03
🔒 Advanced Guards
07 Postgres 15+: UNIQUE NULLS NOT DISTINCT
08 EXCLUDE USING gist Interval Overlaps
09 DEFERRABLE — Check at COMMIT
Pillar 04
🎯 Practice & Next
10 Constraints Master Arena
11 Interview Questions & Self-Check
12 Next class: PRIMARY KEY & FOREIGN KEY
🛡️
The Enterprise Reality: In high-throughput databases (Flipkart, Amazon, Swiggy), application-level validation fails under race conditions. Database constraints are your last line of defense — enforced inside the engine on every write.

Where We Left Off

From physical byte containers to active engine guardrails

📦 SESSION 04 • COMPLETED

What Data Types Solved

-- Session 4: Hardware byte-level containers
CREATE TABLE orders (
id         BIGINT,         -- 8B (Never overflows)
amount     NUMERIC(10,2),  -- Base-10 (No drift)
created_at TIMESTAMPTZ     -- UTC timestamp
);
In Session 4, you eradicated naive All-TEXT tables. Every column now has a physical byte footprint on SSD & RAM, and raw garbage strings like 'abc' into NUMERIC are rejected at the write gate.
✓ Physical storage is typed, bounded, and memory-efficient
⚠️ SESSION 05 • THE MISSION

Why Data Types Alone Are Not Enough

-- Types check FORM, but NOT business MEANING:
INSERT INTO orders (id, amount)
VALUES (NULL, -5000.00);
⚠️ BIGINT allows NULL! NUMERIC allows -5000! 10 users grab same email!
An INT column happily accepts age = -47. A NUMERIC column happily accepts price = -5000. Types define how many bytes to allocate — constraints define what data is legally permitted to exist.
🔒 Today: Turn passive storage into active, unbypasable engine gatekeepers
🛡️
The Core Distinction: Data types define what form data takes. Constraints define what data is legally permitted to exist. Today, we install unbypasable guardrails directly into PostgreSQL.
🏫
The Exam Hall Without Rules WHAT HAPPENS WHEN A DATABASE HAS ZERO GUARDRAILS
NATIONAL UNIVERSITY • 50,000 STUDENTS EXAMPLE STORY • NAMES & NUMBERS MADE UP 🕘 09:00 AM • EXAM COMMENCES
STEP 01 • THE EXPERIMENT
university-registrar Morning of the exam
“We don't need strict rules on day one — everyone knows how exams work.”
A university conducted an entrance examination with 50,000 candidates with zero validation on admit cards. No uniqueness check, no required fields, and no roll number verification.
STEP 02 • TOTAL SYSTEM COLLAPSE
💥 Seat 12 Collision
No UNIQUE
Two students arrive with the same seat number — an immediate brawl breaks out.
💥 Roll -47 & Ghost Hall 99
No CHECK / FK
Admit cards print blank names (no NOT NULL), negative roll numbers, and Hall 99 which does not exist on campus!
STEP 03 • THE UNBYPASSABLE RULES
CONSTRAINTS
🐘 The Bulletproof Rules
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.exam_candidates,
  constraints_and_keys.halls CASCADE;
CREATE TABLE constraints_and_keys.halls (
  id        INT PRIMARY KEY
);
CREATE TABLE constraints_and_keys.exam_candidates (
  roll_no   INT PRIMARY KEY 
            CHECK (roll_no > 0),
  name      TEXT NOT NULL,
  seat_no   INT UNIQUE NOT NULL,
  hall_id   INT REFERENCES
              constraints_and_keys.halls(id)
);

-- Next exam: Zero collisions. Zero blank names.
-- Zero ghost halls. 100% physically guarded.
✓ 5 Constraints Enforced Zero Chaos
STEP 04 • CORE PRINCIPLE
The Moral: Application code can be bypassed by direct scripts and race conditions. Database constraints CANNOT. Those five rules (PRIMARY KEY, NOT NULL, CHECK, UNIQUE and FOREIGN KEY) are — the unbypasable foundation of every enterprise database.

Translating the Exam Hall to PostgreSQL

The Physical to Relational Protection Shield
ROSETTA STONE • 6 INTEGRITY PRIMITIVES

Every vulnerability in the Exam Hall disaster directly maps to a physical PostgreSQL constraint that rejects corrupted data at the hardware storage gate:

Exam Hall Vulnerability Protective SQL Constraint / Key Production Defense Guarantee
📝 Blank Name on Admit Card NOT NULL Write-time rejection (PostgreSQL Error 23502) — zero rows touch disk with missing mandatory data.
⚡ Missing Status / Creation Date DEFAULT Automated fallback — engine supplies 'registered' or now() when client inserts omit optional columns.
📏 Negative Roll No (-47) or Bad Score CHECK (roll_no > 0) Mathematical domain boundary — evaluates arithmetic conditions before writing to heap (Error 23514).
💥 Seat 12 Double-Booking Collision UNIQUE (seat_no) Atomic B-Tree index scanner — deflects duplicate values and guarantees 1 desk = 1 student (Error 23505).
🏢 Ghost Hall 99 (Does Not Exist) FOREIGN KEY (hall_id) Next class. Referential laser bridge — child row cannot reference non-existent parent hall; eliminates orphan rows (Error 23503).
🔑 Student Identity & Primary Anchor PRIMARY KEY (roll_no) Next class. Surrogate / natural anchor — combines UNIQUE + NOT NULL with an automatic unique B-Tree index for permanent row identity.
⚖️
Defense in Depth: Validate at the form UI for user speed; enforce at PostgreSQL constraints for mathematical certainty. Even if a rogue script or crashed service bypasses your API, the database engine drops the query on the floor.

NOT NULL: Preventing Empty Data and Application Crashes

In database storage, an unconstrained column permits NULL (the physical absence of a value). NOT NULL acts as an engine-level write gate that rejects missing data before it ever touches disk.

⚡ POSTGRES ENGINE WRITE GATE • NOT NULL INTERCEPTOR
1. INCOMING WRITE TUPLES
ORDER #101 • VALID NAME PRESENT
{ id: 101, name: "Priya Sharma", amount: ₹2,500 }
ORDER #102 • THREAT NAME IS NULL
{ id: 102, name: NULL, amount: ₹4,800 }
🛡️ NOT NULL
GATE
2. ENGINE PERSISTENCE / REJECTION
✓ ROW WRITTEN TO TABLE
Allocated Tuple • Commit Acknowledged
✖ ERROR 23502 (NOT_NULL_VIOLATION)
Write Aborted • 0 Bytes Written to Disk
🛡️
The Engine Storage Fact: In PostgreSQL, a row that holds any NULL carries a NULL bitmap (1 bit per column) in its header. Declaring NOT NULL eliminates missing-data bugs in APIs and documents the rule inside the schema itself.

NOT NULL — The Mandatory Column Guarantee

The simplest and most vital constraint in relational databases. Enforces that every single row must contain a real value — any INSERT or UPDATE attempting to write NULL is rejected immediately.

WRITE-TIME REJECTION
Zero Nulls Allowed
Rejects any INSERT or UPDATE that attempts to write an empty or unprovided value into the column.
APPLICATION SAFETY
Crash Immunity
Prevents downstream backend services from encountering unexpected null reference crashes.
IN-PLACE EVOLUTION
SET NOT NULL
Can be added to existing tables via ALTER TABLE, but requires updating existing dirty rows first.
NOT NULL in Action
▶ Run Query
PostgreSQL
SYNTAXusers — NOT NULL constraint definition & error behavior
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.users CASCADE;
CREATE TABLE constraints_and_keys.users (
  user_id    SERIAL PRIMARY KEY,
  username   VARCHAR(50) NOT NULL,
  email      VARCHAR(100) NOT NULL
);

-- Valid write: all required columns provided:
INSERT INTO constraints_and_keys.users (username, email) VALUES ('aarav', 'aarav@shop.com'); -- OK!
SELECT * FROM constraints_and_keys.users LIMIT 10;

-- FAILS: username is omitted and has no default:
INSERT INTO constraints_and_keys.users (email) VALUES ('missing@shop.com');
-- 💥 ERROR: null value in column "username" violates not-null constraint
-- DETAIL: Failing row contains (2, null, missing@shop.com).

-- In-Place Schema Evolution on Existing Tables:
-- Step 1: Pre-clean dirty rows (otherwise SET NOT NULL aborts!)
UPDATE constraints_and_keys.users SET username = 'anonymous' WHERE username IS NULL;
-- Step 2: Lock the column against future NULL writes:
ALTER TABLE constraints_and_keys.users ALTER COLUMN username SET NOT NULL;
⚡Topic Drill

Verify write-time rejection and safe column migration

DEFAULT: Automated Fallbacks & Timestamping

When application code or bulk imports omit a column during INSERT, DEFAULT instructs PostgreSQL to automatically fill a predefined value or expression before persisting the tuple.

⚙️ POSTGRES ENGINE TRANSFORMER • TWIN PISTON INJECTOR
1. APPLICATION INSERT PAYLOAD
OMITTED STATUS & TIMESTAMP
{ id: 501, amount: ₹2,500 }
Columns status and created_at left unmentioned
⚙️ TWIN INJECTOR PISTONS
AUTO-INJECTING:
'pending' • now()
2. FULL TUPLE WRITTEN TO DISK
✓ COMPLETE ROW PACKED
{ id: 501, amount: ₹2,500, status: 'pending', created_at: now() }
Zero NULLs • Guaranteed downstream sanity
⚙️
Production Rule: DEFAULT expressions like CURRENT_TIMESTAMP and gen_random_uuid() evaluate dynamically at the exact moment of INSERT execution, never when the table is created. Never hardcode static timestamps in defaults.

DEFAULT — Automated Value Fallbacks

Pre-configures standard values directly in the database table blueprint. Supports static literals, dynamic clock evaluations, and sequence increments without writing extra application logic.

STATIC LITERALS
Status & Numbers
DEFAULT 'pending', DEFAULT 0, DEFAULT true, DEFAULT 'standard'
DYNAMIC EXPRESSIONS
Server Functions
DEFAULT now(), DEFAULT CURRENT_DATE, DEFAULT gen_random_uuid()
FUTURE-PROOFING
Zero Table Lock
ALTER TABLE ... SET DEFAULT applies strictly to future writes, leaving existing historical rows intact.
DEFAULT in Action
▶ Run Query
PostgreSQL
SYNTAXorders — Auto-populating timestamps and default statuses
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.web_orders CASCADE;
CREATE TABLE constraints_and_keys.web_orders (
  order_id      SERIAL PRIMARY KEY,
  customer_name VARCHAR(100) NOT NULL,
  order_status  VARCHAR(20) NOT NULL DEFAULT 'pending',
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Provide ONLY the required column; defaults fill the rest automatically:
INSERT INTO constraints_and_keys.web_orders (customer_name) VALUES ('Priya');
-- order_status='pending', created_at=now() auto-populated!

-- Explicit override is still permitted:
INSERT INTO constraints_and_keys.web_orders (customer_name, order_status)
VALUES ('Aarav', 'shipped'); -- order_status becomes 'shipped'

-- Managing defaults on existing tables:
ALTER TABLE constraints_and_keys.web_orders ALTER COLUMN order_status SET DEFAULT 'placed';
ALTER TABLE constraints_and_keys.web_orders ALTER COLUMN order_status DROP DEFAULT;

-- See what the defaults filled in:
SELECT order_id, customer_name, order_status, created_at FROM constraints_and_keys.web_orders LIMIT 10;
⚡Topic Drill

Test automated value fallbacks and the explicit NULL trap

CHECK: Declarative Business Rules & The NULL Trap

CHECK constraints enforce mathematical and domain boundaries on every row. PostgreSQL evaluates the boolean expression before allocating storage. If the expression evaluates to FALSE, the write is aborted.

📏 POSTGRES BOUNDARY GAUGE • 3-VALUED LOGIC ANALYZER
EVALUATED RULE
CONSTRAINT DEFINITION
CHECK (unit_price > 0)
Testing: unit_price = 1200 → TRUE
≤ 0 (REJECT FALSE) NULL (UNKNOWN TRAP) > 0 (PASS TRUE)
✓ EXPRESSION TRUE • ROW WRITTEN TO HEAP
⚖️
The 3-Valued Logic Trap: In SQL, boolean comparisons with NULL return UNKNOWN. Since CHECK constraints only block writes when the condition evaluates strictly to FALSE, a bare CHECK allows NULL to slip through silently. Always pair CHECK with NOT NULL.

The CHECK Constraint

A custom boolean expression evaluated on every write. If the condition evaluates to FALSE, the write is aborted. Used for ranges, categorical dropdowns, and business boundaries.

NUMERIC RANGES
Boundary Defense
CHECK (price > 0), CHECK (rating BETWEEN 1 AND 5), CHECK (age >= 18)
CATEGORICAL LISTS
Allowed States
CHECK (status IN ('placed', 'shipped', 'delivered', 'cancelled'))
STRING FORMATS
Shape Defense
CHECK (length(pincode) = 6), CHECK (email LIKE '%@%.%')
CHECK Ranges & Domain Boundaries
▶ Run Query
PostgreSQL
SYNTAXorder_items — CHECK constraints on price and status
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.order_items CASCADE;
CREATE TABLE constraints_and_keys.order_items (
  item_id    SERIAL PRIMARY KEY,
  item_name  VARCHAR(100) NOT NULL,
  unit_price NUMERIC(10,2) NOT NULL CHECK (unit_price > 0),
  quantity   INT NOT NULL CHECK (quantity >= 1),
  status     VARCHAR(20) NOT NULL
    CHECK (status IN ('pending', 'packed', 'dispatched', 'delivered'))
);

INSERT INTO constraints_and_keys.order_items (item_name, unit_price, quantity, status)
VALUES ('Wireless Mouse', 799.00, 2, 'packed'); -- OK

-- Also try (FAILS): status 'in-transit' is not in the approved whitelist
-- INSERT INTO constraints_and_keys.order_items (item_name, unit_price, quantity, status)
-- VALUES ('Keyboard', 1299.00, 1, 'in-transit');
-- ERROR: new row violates check constraint "order_items_status_check"

-- FAILS: price cannot be zero or negative
INSERT INTO constraints_and_keys.order_items (item_name, unit_price, quantity, status)
VALUES ('Bad Item', -50.00, 1, 'packed');
-- ERROR: new row violates check constraint "order_items_unit_price_check"
⚡Topic Drill

Enforce arithmetic boundaries and avoid the 3-valued NULL trap

UNIQUE: Identity, Natural Keys & The B-Tree Collision Radar

A UNIQUE constraint guarantees that no two rows share the same non-NULL value. Under the hood, PostgreSQL automatically constructs a unique B-Tree index to verify uniqueness in O(log N) time.

🎯 POSTGRES B-TREE INDEX SCANNER • COLLISION RADAR
INCOMING IDENTITY PAYLOAD
CANDIDATE NATURAL KEY
{ email: "priya@shiraverse.com" }
Target table: users • Column: email
🌳 ROOT NODE [A — Z]
🖍️ BRANCH [M — S]
pankaj@...
⚠️ priya@shiraverse.com
rahul@...
✖ ERROR 23505: duplicate key value violates unique constraint "users_email_key"
⚡
The Performance Fact: In SQL, UNIQUE is not just a constraint — it creates a physical B-Tree index. This gives you mathematical duplication defense and instant O(log N) lookup speeds for WHERE queries.

Definition & Syntax: UNIQUE Constraints

Guarantees no two rows share identical non-null data. Single-column identity, multi-column composite combinations, and PostgreSQL 15+ NULL handling.

SINGLE-COLUMN DOMAIN
Natural Uniqueness
Guarantees distinct values for user attributes (email, phone). Backed automatically by an index scan B-Tree.
COMPOSITE MULTI-COLUMN
Compound Identity
Enforces uniqueness across 2+ columns combined: UNIQUE (tenant_id, slug). Individual columns may repeat freely.
POSTGRESQL 15+ STANDARD
NULLS NOT DISTINCT
Stops infinite NULL duplicates. Enforces that only a single row may hold NULL in the unique column.
Single, Composite & PG15 UNIQUE
▶ Run Query
PostgreSQL
SYNTAXUnique constraints across single and compound keys
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
-- 1. Single-column UNIQUE (email must be unique across all users):
-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.tax_filings,
  constraints_and_keys.tenant_workspaces,
  constraints_and_keys.accounts CASCADE;
CREATE TABLE constraints_and_keys.accounts (
  account_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email      VARCHAR(150) UNIQUE NOT NULL
);

-- 2. Multi-column composite UNIQUE (slug unique within the same tenant):
CREATE TABLE constraints_and_keys.tenant_workspaces (
  workspace_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  tenant_id    INT NOT NULL,
  slug         VARCHAR(50) NOT NULL,
  CONSTRAINT uq_tenant_slug UNIQUE (tenant_id, slug)
);

-- 3. PostgreSQL 15+ (allows at most ONE null row instead of unlimited NULL duplicates):
CREATE TABLE constraints_and_keys.tax_filings (
  filing_id  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  tax_id     VARCHAR(20) UNIQUE NULLS NOT DISTINCT
);

-- Same slug in two different tenants is fine:
INSERT INTO constraints_and_keys.tenant_workspaces (tenant_id, slug) VALUES (1, 'sales'), (2, 'sales');
SELECT tenant_id, slug FROM constraints_and_keys.tenant_workspaces LIMIT 10;
-- Now try (FAILS with Error 23505): the same slug twice in tenant 1
-- INSERT INTO constraints_and_keys.tenant_workspaces (tenant_id, slug) VALUES (1, 'sales');
⚡Topic Drill

Deflect natural key collisions and verify composite tuples

Exclusion Constraints: The Anti-Double-Booking Studio

Standard UNIQUE constraints only compare equality (=). When modeling hotel rooms, doctor appointments, or meeting spaces, standard UNIQUE cannot detect overlapping time intervals. EXCLUDE USING gist prevents schedule conflicts at the engine level.

⏱️ POSTGRES GIST EXCLUSION RADAR • RANGE OVERLAP DETECTOR
09:00 AM 10:00 AM 11:00 AM 12:00 PM 01:00 PM 02:00 PM 03:00 PM
ROOM 101 BOOKED:
🔒 Existing: 10:00 AM — 12:00 PM
INCOMING REQUEST:
💥 Collision: 11:00 AM — 01:00 PM (OVERLAPS!)
✖ ERROR 23P01: conflicting key value violates exclusion constraint "no_double_booking" • Transaction Aborted
📅
The PostgreSQL Superpower: Exclusion constraints use GiST indexing with range operators (&& for overlap). This eliminates complex, race-condition-prone application locking loops.

Exclusion Constraints (EXCLUDE USING gist)

Neither UNIQUE nor CHECK can stop overlapping time intervals (e.g. 10:00-11:30 vs 11:00-12:00) because start and end times differ. Exclusion Constraints guarantee no two rows share conflicting ranges.

THE GAP
UNIQUE & CHECK Fail
UNIQUE only checks exact equality (=). CHECK only inspects a single row.
RANGE TYPES
TSRANGE & DATERANGE
PostgreSQL models intervals as first-class types: tsrange(start, end).
OVERLAP OPERATOR
&& Overlaps
The mathematical && operator evaluates whether two time windows collide.
Exclusion Constraints (EXCLUDE USING gist)
Run this in your own PostgreSQL
PostgreSQL
SYNTAXExclusion Constraints — Solving booking overlaps forever
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS constraints_and_keys;
CREATE EXTENSION IF NOT EXISTS btree_gist;

-- start clean, safe to re-run
DROP TABLE IF EXISTS
  constraints_and_keys.doctor_schedules CASCADE;
CREATE TABLE constraints_and_keys.doctor_schedules (
  booking_id  SERIAL PRIMARY KEY,
  doctor_id   INT NOT NULL,
  slot_window TSRANGE NOT NULL,
  -- For any two rows, if doctor_id is EQUAL (=), slot_window CANNOT OVERLAP (&&):
  CONSTRAINT no_double_booking
    EXCLUDE USING gist (doctor_id WITH =, slot_window WITH &&)
);

-- Doctor 101 booked 10:00 to 11:00
INSERT INTO constraints_and_keys.doctor_schedules (doctor_id, slot_window)
VALUES (101, tsrange('2026-10-10 10:00', '2026-10-10 11:00'));

-- FAILS: 10:30 to 11:30 collides with 10:00 to 11:00!
INSERT INTO constraints_and_keys.doctor_schedules (doctor_id, slot_window)
VALUES (101, tsrange('2026-10-10 10:30', '2026-10-10 11:30'));
-- ERROR: conflicting key value violates exclusion constraint "no_double_booking"
-- DETAIL: Key (doctor_id, slot_window)=(101, ["2026-10-10 10:30:00","2026-10-10 11:30:00"))
-- conflicts with existing key (101, ["2026-10-10 10:00:00","2026-10-10 11:00:00")).
65%

Query Mode: Unlocked — Constraints

Time to lock down your tables! Enforce NOT NULL, DEFAULT, UNIQUE, composite rules, exclusion constraints, and zero-downtime ALTER TABLE migrations directly in PostgreSQL.

🧠Practice Time

Interview Studio: Constraints

Top real-world questions asked by staff engineers and senior database architects.

TOP INTERVIEW QUESTIONS
DATA INTEGRITY & CONSTRAINTS

“Does a CHECK (rating BETWEEN 1 AND 5) constraint reject NULL values?”

✓ MODEL ANSWER:
No, it does NOT reject NULL. In SQL 3-valued logic, evaluating NULL BETWEEN 1 AND 5 yields UNKNOWN. PostgreSQL CHECK constraints reject writes only when the condition evaluates to strictly FALSE — an UNKNOWN result is treated as passing! To guarantee that nulls cannot be inserted, you must explicitly pair the rule with NOT NULL.
🧠 WHY INTERVIEWERS ASK THIS:

Tests whether you understand SQL's three-valued logic (TRUE, FALSE, UNKNOWN) rather than just treating SQL like binary programming languages.

⚠️ COMMON TRAP TO AVOID:

Assuming CHECK acts as a filter on empty values. Never rely on CHECK alone when a field is mandatory.

Constraints Self-Check

Close your notes. Answer from memory. Click any card to reveal the verified answer.

1. What happens if you run SET NOT NULL on a column with NULLs?
2. What is the difference between NOT NULL and DEFAULT?
3. You INSERT an explicit NULL into a column that has a DEFAULT. What is stored?
4. Does a CHECK constraint block NULL? How do you also block NULL?
5. Can a column with a standard UNIQUE constraint hold multiple NULLs?
6. What is a Composite UNIQUE constraint?
7. How do you prevent overlapping bookings when UNIQUE cannot do it?
8. When would you declare a constraint DEFERRABLE INITIALLY DEFERRED?

The 5 Golden Rules Bento

The non-negotiable architectural principles for every constraint you write.

🛡️ RULE 01 • DEFENSE IN DEPTH

Never Trust Application Code Alone

Application validations are regularly bypassed by direct psql maintenance, ETL jobs, and race conditions. Declarative constraints are the engine's unbypasable source of truth.

⚡ Enforce in DB engine — validate in UI for UX feedback only.
⚖️ RULE 02 • 3-VALUED LOGIC

Always Pair CHECK with NOT NULL

In SQL, NULL > 0 evaluates to UNKNOWN, and CHECK only rejects FALSE. A column with bare CHECK will quietly accept NULL — always write NOT NULL CHECK (...).

⚠️ NULL slips past bare CHECK: UNKNOWN ≠ FALSE.
⚡ RULE 03 • AUTO-FILL

DEFAULT Fills Only What You Leave Out

DEFAULT runs only when the INSERT skips the column. An explicit NULL is stored as NULL — so pair DEFAULT with NOT NULL when the column must always have a value.

✅ status TEXT NOT NULL DEFAULT 'pending'
⏱️ RULE 04 • SCHEDULING DEFENSE

Use EXCLUDE for Interval Overlaps

When modeling appointments, hotel rooms, or reservations, standard UNIQUE fails. Use PostgreSQL Exclusion Constraints with GIST to physically guarantee zero overlapping time ranges.

🛡️ EXCLUDE USING gist (room_id WITH =, booked_range WITH &&)
🔑 RULE 05 • UNIQUENESS

UNIQUE Lets Many NULLs In

Plain UNIQUE blocks duplicate values but treats every NULL as different, so many NULL rows are allowed. Use NOT NULL, or PostgreSQL 15+ UNIQUE NULLS NOT DISTINCT, when that is not what you want.

🔒 Composite rule: UNIQUE (store_id, bin_code)

Constraints Quick Reference Studio

Every constraint from today, plus the ALTER syntax to add or drop one, ready to copy into your terminal.

Constraints Cheat Sheet
SQL REFERENCE
-- CORE & ADVANCED CONSTRAINTS (CREATE TABLE)
-- Column Constraints:
name     VARCHAR(100) NOT NULL
status   VARCHAR(20)  NOT NULL DEFAULT 'pending'
balance  NUMERIC(10,2) NOT NULL CHECK (balance >= 0)
tax_id   VARCHAR(20)  UNIQUE NULLS NOT DISTINCT

-- Composite & Conditional Table Constraints:
CONSTRAINT uq_org_user UNIQUE (org_id, username)
CONSTRAINT chk_dates   CHECK (end_date >= start_date)
CONSTRAINT chk_reason  CHECK (status != 'cancelled' OR reason IS NOT NULL)

-- Exclusion Constraint (Requires btree_gist extension):
CONSTRAINT no_overlap
  EXCLUDE USING gist (room_id WITH =, slot WITH &&)
-- ALTER TABLE: ADD, DROP & TIMING
-- In-Place Column Modifiers:
ALTER TABLE t ALTER COLUMN col SET NOT NULL;
ALTER TABLE t ALTER COLUMN col SET DEFAULT 'active';

-- Named Add & Drop:
ALTER TABLE t ADD CONSTRAINT uq_t_email UNIQUE (email);
ALTER TABLE t DROP CONSTRAINT uq_t_email;

-- Clean first, then lock the column:
UPDATE t SET col = 'unknown' WHERE col IS NULL;
ALTER TABLE t ALTER COLUMN col SET NOT NULL;

-- Deferrable Constraint Timing:
CONSTRAINT uq_rank UNIQUE (rank) DEFERRABLE INITIALLY DEFERRED;

Take-Home Challenge: The Bulletproof Enterprise Schema

Complete these hands-on constraint drills in your local practice database before the Keys class.

🎯 400 practice questions for this session · Easy to Crazy hard, run and checked on the pagePRACTICE →
🎯 THE 4-STEP INTEGRITY SPRINT:
Step 1 — Multi-Tenant Uniqueness

Create constraints_and_keys.departments with composite UNIQUE (hospital_id, department_code). Prove that code 'CAR' can exist in Hospital 1 and Hospital 2, but fails inside Hospital 1.

Step 2 — Conditional Business Rules

Add conditional check constraint ensuring that if is_emergency = true, the emergency_contact_phone column cannot be NULL.

Step 3 — Anti-Double-Booking Guardrail

Enable btree_gist and build an exclusion constraint on constraints_and_keys.operating_rooms preventing overlapping surgery times in the same room.

Step 4 — Clean, Then Lock

Make a table with a few NULL phone numbers. Try ALTER COLUMN phone SET NOT NULL and read the error. Fix the rows with an UPDATE ... WHERE phone IS NULL, then run the ALTER again.

Master Curriculum Journey Roadmap

TheShiraverse Curriculum Flow
🟢 ACTIVE WAYPOINT: MODULE 1 • TOPIC 05 (Constraints)
NEXT MISSION: TOPIC 06 (Keys) • NOT STARTED
01
TOPIC 01 Session 1 • Done
What is SQL & DBs?
Relational Model Port 5432 • Server 5 SQL Families Declarative Logic
✓ COMPLETED VERIFIED
02
TOPIC 02 Session 2 • Done
How SQL is Organised
Database Hierarchy Schemas as Folders Tables as Files Catalog Introspection
✓ COMPLETED VERIFIED
03
TOPIC 03 Session 3 • Done
Writing First Queries
SELECT Evaluation CREATE • ALTER • DROP INSERT • UPDATE • DELETE The Safe WHERE Rule
✓ COMPLETED VERIFIED
04
TOPIC 04 Session 4 • Done
Data Types & Casts
Ending All-TEXT NUMERIC vs FLOAT INT • DATE • TIMESTAMPTZ ALTER ... TYPE ... USING
✓ COMPLETED VERIFIED
05
TOPIC 05 Today • Live
Constraints
NOT NULL • DEFAULT CHECK • NULL Trap UNIQUE • Composite EXCLUDE gist
📍 YOU ARE HERE ACTIVE
06
TOPIC 06 Up Next
Keys
PRIMARY KEY FOREIGN KEY ON DELETE ALTER • NOT VALID
🔒 NOT STARTED UP NEXT
✦ THE FOCUSED ASCENT: ZERO CONCEPTS ➔ SCHEMAS & ARCHITECTURE ➔ FIRST QUERIES & MUTATIONS ➔ STRICT DATA TYPES ➔ CONSTRAINTS ➔ KEYS ✦

Next Session: Keys: PRIMARY KEY, FOREIGN KEY & ON DELETE

NEXT MISSION • LINKING TABLES
Keys: One Identity per Row, No Orphan Links
Today every column got its rules. Next class gives every row a permanent identity and links tables so that no order can point at a customer who does not exist.
• THE SHIRAVERSE
✨
1. PRIMARY KEY Identity
Candidate, natural and surrogate keys, GENERATED ALWAYS AS IDENTITY, and composite keys for junction tables.
🧭
2. FOREIGN KEY: No Orphans
Link orders to customers so a row for customer 999 that does not exist is rejected with Error 23503.
🔍
3. ON DELETE & ALTER TABLE
RESTRICT, CASCADE and SET NULL, the default NO ACTION, and adding rules to live tables with NOT VALID.
→ next   ← back   F fullscreen   H hide bars   N notes
1 / 28