Module 1 › Constraints

Constraints: enforcing database integrity & business rules

Module 1, Session 5. NOT NULL, DEFAULT, UNIQUE, composite rules, conditional CHECK, exclusion constraints, and zero-downtime ALTER TABLE migrations.

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

← All NukteSELECT * FROM TheShiraverse

Constraints & Keys

Enforcing physical database integrity, declarative domain rules, unique identity, referential laser bridges, and zero-downtime production migrations.

41
Interactive Scenes
2
Practice Arenas
20
Hands-on Drills
5
Interview Scenarios

Today’s Agenda — Constraints & Keys

Four architectural pillars to guarantee database correctness and eliminate dirty data

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 EXCLUDE USING gist Interval Overlaps
Pillar 03
🔑 Identity & Keys
07 Single & Composite UNIQUE
08 Postgres 15+: UNIQUE NULLS NOT DISTINCT
09 Key Taxonomy: Candidate, Natural & Surrogate
Pillar 04
🔗 Bridges & Migrations
10 PRIMARY KEY & FOREIGN KEY Laser Shield
11 ON DELETE: RESTRICT, CASCADE & SET NULL
12 Zero-Downtime Migrations with NOT VALID
🛡️
The Enterprise Reality: In high-throughput databases (Flipkart, Amazon, Swiggy), application-level validation always fails under race conditions. Database constraints are your last line of defense — physical, atomic, and enforced directly inside the engine kernel.

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 🕘 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
CREATE TABLE 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 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) Referential laser bridge — child row cannot reference non-existent parent hall; eliminates orphan rows (Error 23503).
🔑 Student Identity & Primary Anchor PRIMARY KEY (roll_no) 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
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.users (
  user_id    SERIAL PRIMARY KEY,
  username   VARCHAR(50) NOT NULL,
  email      VARCHAR(100) NOT NULL
);

-- Valid write: all required columns provided:
INSERT INTO practice.users (username, email) VALUES ('aarav', '[email protected]'); -- OK!
SELECT * FROM practice.users LIMIT 10;

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

-- In-Place Schema Evolution on Existing Tables:
-- Step 1: Pre-clean dirty rows (otherwise SET NOT NULL aborts!)
UPDATE practice.users SET username = 'anonymous' WHERE username IS NULL;
-- Step 2: Lock the column against future NULL writes:
ALTER TABLE practice.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
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.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 practice.web_orders (customer_name) VALUES ('Priya');
-- order_status='pending', created_at=now() auto-populated!

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

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

-- See what the defaults filled in:
SELECT order_id, customer_name, order_status, created_at FROM practice.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
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.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 practice.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 practice.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 practice.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: "[email protected]" }
Target table: users • Column: email
🌳 ROOT NODE [A — Z]
🖍️ BRANCH [M — S]
pankaj@...
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
CREATE SCHEMA IF NOT EXISTS practice;
-- 1. Single-column UNIQUE (email must be unique across all users):
CREATE TABLE practice.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 practice.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 practice.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 practice.tenant_workspaces (tenant_id, slug) VALUES (1, 'sales'), (2, 'sales');
SELECT tenant_id, slug FROM practice.tenant_workspaces LIMIT 10;
-- Now try (FAILS with Error 23505): the same slug twice in tenant 1
-- INSERT INTO practice.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
CREATE SCHEMA IF NOT EXISTS practice;
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE practice.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 practice.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 practice.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

Relational Keys Taxonomy — The Hierarchy of Identity

From minimal Candidate Keys to physical Surrogate anchors: how relational database engines establish immutable entity identity without ambiguity.

MINIMAL SUPERKEYS
Candidate Keys
Any column or minimal set of columns that uniquely identifies a row. A table can have multiple Candidate Keys (e.g., id, email, pan_number).
THE CHOSEN ANCHOR
Primary vs Alternate Keys
The architect picks exactly ONE Candidate Key as the Primary Key. All other candidate keys become Alternate Keys (enforced with UNIQUE NOT NULL).
ARCHITECTURAL CHOICE
Surrogate vs Natural Keys
Natural Key: Real-world attribute (PAN, Aadhaar, ISBN). Surrogate Key: Synthetic sequential integer (BIGINT IDENTITY, UUID) with no business meaning — guarantees 8-byte fast joins!
The Relational Key Hierarchy
▶ Run Query
PostgreSQL
SCHEMA BLUEPRINTtaxpayers — Candidate, Primary, Alternate, and Natural Keys in Action
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.taxpayers (
  -- 1. SURROGATE PRIMARY KEY: the chosen candidate key (synthetic 8-byte anchor)
  taxpayer_id  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

  -- 2. NATURAL ALTERNATE KEY: real-world business identifier (immutable)
  pan_number   VARCHAR(10) UNIQUE NOT NULL,

  -- 3. SECONDARY ALTERNATE KEY: candidate key enforced via UNIQUE NOT NULL
  email        VARCHAR(150) UNIQUE NOT NULL,

  -- 4. NON-KEY ATTRIBUTE: descriptive domain payload
  full_name    TEXT NOT NULL
);

INSERT INTO practice.taxpayers (pan_number, email, full_name)
VALUES ('ABCDE1234F', '[email protected]', 'Priya Sharma');
SELECT taxpayer_id, pan_number, email, full_name FROM practice.taxpayers LIMIT 10;

-- 💡 The Golden Rule: use surrogate keys for joins and primary keys;
-- enforce natural keys using alternate keys (UNIQUE NOT NULL)!

PRIMARY KEY: The Entity Identity Forge Studio

In relational database theory, every physical table requires an unbypasable identity anchor. PostgreSQL enforces PRIMARY KEY by combining NOT NULL + UNIQUE and building a unique B-Tree index.

🔑 POSTGRES IDENTITY FORGE • DUAL-SHIELD PK INDEX
KEY TAXONOMY HIERARCHY
• Super Key: Any unique attribute combo.
• Candidate Key: Minimal super key (no extras).
• Primary Key: The 1 chosen table anchor.
• Alternate Key: Other candidates (UNIQUE NOT NULL).
• Surrogate vs Natural: Synthetic ID vs Real PAN/GSTIN.
🛡️ NOT NULL CLAMP
PK: id = 1001
🔒 UNIQUE B-TREE CLAMP
STORAGE & INDEX ANCHOR
✓ 8-BYTE SURROGATE KEY ANCHOR
Sequential append to rightmost B-Tree leaf
Zero page splits • Blazing-fast 8-byte joins
🔑
The Architecture Verdict: In production databases, always prefer a Surrogate Key (BIGINT GENERATED ALWAYS AS IDENTITY) as the Primary Key for fast joins and storage stability. Protect real-world natural keys (PAN, GSTIN, Email) using UNIQUE NOT NULL.

PRIMARY KEY — Identity Anchor & Physical Storage

The ultimate entity anchor in relational tables. Combines NOT NULL + UNIQUE with an automatic unique B-Tree index, for fast lookups and joins.

MODERN ANSI STANDARD
GENERATED ALWAYS AS IDENTITY
The modern SQL standard identity mechanism. Supersedes legacy SERIAL by preventing accidental manual sequence desynchronization.
JUNCTION TABLES
Composite Primary Keys
Used in bridge and line-item tables (PRIMARY KEY (order_id, item_id)) ensuring no duplicate combinations can exist in junction records.
EXPLICIT METADATA
Named Primary Keys
Always name constraints explicitly: CONSTRAINT pk_orders PRIMARY KEY (order_id) so production logs output actionable names.
Primary Key Definitions
▶ Run Query
PostgreSQL
SYNTAXSingle-column surrogate PK and composite bridge table PK
CREATE SCHEMA IF NOT EXISTS practice;
-- 1. Modern single-column surrogate primary key:
CREATE TABLE practice.shop_orders (
  order_id   BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_date DATE NOT NULL DEFAULT CURRENT_DATE,
  total      NUMERIC(10,2) NOT NULL CHECK (total >= 0)
);

-- 2. Composite primary key on a junction / line-items table:
CREATE TABLE practice.shop_order_items (
  order_id   BIGINT REFERENCES practice.shop_orders(order_id),
  item_id    INT,
  quantity   INT NOT NULL CHECK (quantity > 0),
  CONSTRAINT pk_shop_order_items PRIMARY KEY (order_id, item_id)
);

INSERT INTO practice.shop_orders (total) VALUES (2499.00);                -- order_id 1 is generated
INSERT INTO practice.shop_order_items VALUES (1, 1, 2), (1, 2, 1);        -- same order, different items: OK
SELECT order_id, item_id, quantity FROM practice.shop_order_items LIMIT 10;
-- Now try (FAILS with Error 23505): the pair (1, 1) already exists
-- INSERT INTO practice.shop_order_items VALUES (1, 1, 5);
⚡Topic Drill

Master surrogate IDENTITY generation and composite junction keys

FOREIGN KEY: The Relational Bridge & The Orphan Shield

A Foreign Key connects a column in a child table to the primary key of a parent table. It physically prevents orphan records from entering your database by validating relational existence.

🔗 POSTGRES REFERENTIAL BRIDGE • ORPHAN DEFENSE SYSTEM
CHILD TABLE: orders
INCOMING ORDER
{ order_id: 501, cust_id: 101, total: ₹3,400 }
REFERENCES customers(id)
🔗 LINK ACTIVE
VERIFYING PK
PARENT TABLE: customers
✓ id: 101 • Priya Sharma
• id: 102 • Rahul Verma
• id: 103 • Ananya Iyer
🔗
The Orphan Defense: Without foreign keys, deleting a customer leaves orphan orders in your billing database that can never be invoiced or reconciled. Foreign keys turn isolated spreadsheet tables into a coherent, self-protecting relational graph.

FOREIGN KEY (REFERENCES)

A child table cannot reference a parent record that does not exist. The database engine validates referential existence before writing, permanently eliminating corrupt orphan rows.

Parent Target
Target Must Be Unique
A Foreign Key must point to a column guaranteed to be unique on the parent table — typically a PRIMARY KEY or UNIQUE column.
Orphan Shield
Blocks Ghost References
Inserting or updating a child row with a non-existent parent ID is rejected immediately at write time by PostgreSQL.
Engine Error
Code 23503
Fires foreign_key_violation: Key (cust_id)=(999) is not present in table customers. Pinpoints the bad payload instantly.
Table-Level Syntax
Explicit Naming
Always name your constraints: CONSTRAINT fk_orders_customer FOREIGN KEY (cust_id) REFERENCES customers(cust_id).
Preventing Orphan Records
▶ Run Query
PostgreSQL
SYNTAXPreventing Orphan Records
-- Step 1: Parent table must exist with a unique identifier
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.customers (
  cust_id    INT PRIMARY KEY,
  full_name  VARCHAR(100) NOT NULL
);

-- Step 2: Child table enforces referential relationship
CREATE TABLE practice.orders (
  order_id   SERIAL PRIMARY KEY,
  cust_id    INT NOT NULL,
  amount     NUMERIC(10,2) NOT NULL CHECK (amount >= 0),
  CONSTRAINT fk_orders_customer
    FOREIGN KEY (cust_id) REFERENCES practice.customers(cust_id)
);

-- Valid write: customer 101 exists in parent table
INSERT INTO practice.customers (cust_id, full_name) VALUES (101, 'Aarav Sharma');
INSERT INTO practice.orders (cust_id, amount) VALUES (101, 2499.00); -- OK!

-- Blocked write: customer 999 does not exist (Orphan Shield fires)
INSERT INTO practice.orders (cust_id, amount) VALUES (999, 499.00);
-- 💥 ERROR: insert or update on table "orders" violates foreign key constraint "fk_orders_customer"
-- DETAIL: Key (cust_id)=(999) is not present in table "customers".

ON DELETE: The Referential Action Engine

When a parent row is deleted, foreign keys enforce referential actions: RESTRICT blocks the delete with a shield, CASCADE sweeps child rows away, and SET NULL severs the link safely.

⚡ POSTGRES ENGINE • ON DELETE REFERENTIAL ACTIONS
READY • Click a delete action button above to simulate PostgreSQL engine behavior. STANDBY
PARENT TABLE: departments ✓ 1 ROW
PRIMARY KEY: dept_id = 10
{ dept_id: 10, name: 'Tech' }
Referenced by child table
🔗 LINK ACTIVE
ON DELETE (Select action)
CHILD TABLE: staff ✓ 1 CHILD
FOREIGN KEY: dept_id → 10
{ staff_id: 1, name: 'Kavita', dept_id: 10 }
🛡
Referential Action Rule: Child rows can never point to non-existent parents. When deleting a parent, PostgreSQL executes the defined policy: RESTRICT aborts with Error 23503, CASCADE deletes child rows in lockstep, and SET NULL severs the relationship while preserving the child record.

ON DELETE — What Happens to the Children?

When you delete a parent row that still has children, the foreign key’s ON DELETE clause decides the outcome. Pick it per relationship.

Critical Data
RESTRICT — Block It
Block the delete until the children are handled (Error 23503). The safest choice: use it for financial and critical data, like a department that still has staff.
Disposable Children
CASCADE — Delete Them Too
Delete the children along with the parent. Only for dependents that mean nothing alone, like comments on a deleted blog post.
Reassignable Links
SET NULL — Unlink Them
Keep the children and set their foreign key to NULL. For links you can reassign later, like an employee whose manager leaves. The column must allow NULL.
The Three ON DELETE Rules, Side by Side
▶ Run Query
PostgreSQL
SYNTAXRESTRICT, CASCADE and SET NULL
-- Parents
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.blog_posts  (post_id INT PRIMARY KEY, title VARCHAR(100) NOT NULL);
CREATE TABLE practice.managers    (manager_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL);
CREATE TABLE practice.departments (dept_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL);
-- Children: one ON DELETE rule each
CREATE TABLE practice.comments (comment_id SERIAL PRIMARY KEY,
  post_id INT REFERENCES practice.blog_posts(post_id) ON DELETE CASCADE);   -- disposable: delete them too
CREATE TABLE practice.staff (staff_id SERIAL PRIMARY KEY,
  manager_id INT REFERENCES practice.managers(manager_id) ON DELETE SET NULL, -- reassignable: unlink (must allow NULL)
  dept_id INT NOT NULL REFERENCES practice.departments(dept_id) ON DELETE RESTRICT); -- critical: block it
INSERT INTO practice.blog_posts VALUES (1, 'Intro to SQL');
INSERT INTO practice.comments (post_id) VALUES (1), (1);
INSERT INTO practice.managers VALUES (7, 'Priya');
INSERT INTO practice.departments VALUES (10, 'Tech');
INSERT INTO practice.staff (manager_id, dept_id) VALUES (7, 10);
DELETE FROM practice.blog_posts WHERE post_id = 1;    -- CASCADE: both comments are deleted too
DELETE FROM practice.managers WHERE manager_id = 7;   -- SET NULL: staff row stays, manager_id becomes NULL
SELECT staff_id, manager_id, dept_id FROM practice.staff LIMIT 10;
-- Now try RESTRICT: remove the two dashes below and run again. It is blocked with Error 23503.
-- DELETE FROM practice.departments WHERE dept_id = 10;
💡
Pro tip: if you write no ON DELETE at all, PostgreSQL uses NO ACTION, which blocks the delete just like RESTRICT (it only differs for deferred constraints). For money tables, keep the block and use a soft delete (an is_active flag) instead of CASCADE.
⚡Topic Drill

Deflect orphan records and control cascading delete ripple policies

ALTER TABLE — In-Place Column Evolution & Zero-Downtime Migrations

Modifying column rules and locking down multi-crore row tables without table locking or live outages

In-Place Column Evolution & Zero-Downtime Pattern
▶ Run Block 1
PostgreSQL
▶ Run Block 2
PostgreSQL
COLUMN MODIFIERS 1. Adding & Dropping NOT NULL / DEFAULT
-- Setup: 2 practice tables, one phone number missing
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.customers (
  customer_id SERIAL PRIMARY KEY, phone_number VARCHAR(15));
CREATE TABLE practice.orders (
  order_id SERIAL PRIMARY KEY, order_status VARCHAR(20));
INSERT INTO practice.customers (phone_number)
VALUES ('9876543210'), (NULL);
-- ⚠️ GOTCHA: SET NOT NULL fails on NULLs; fix them first:
UPDATE practice.customers SET phone_number = '0000000000'
WHERE phone_number IS NULL;
-- Make the column mandatory, then remove the rule:
ALTER TABLE practice.customers
  ALTER COLUMN phone_number SET NOT NULL;
ALTER TABLE practice.customers
  ALTER COLUMN phone_number DROP NOT NULL;
-- Auto-fill default for future inserts, then remove it:
ALTER TABLE practice.orders
  ALTER COLUMN order_status SET DEFAULT 'pending';
ALTER TABLE practice.orders
  ALTER COLUMN order_status DROP DEFAULT;
-- 💡 Affects only FUTURE inserts; old rows stay untouched.
SELECT customer_id, phone_number
FROM practice.customers LIMIT 10;
ZERO-DOWNTIME PATTERN 2. Safe Enterprise Migrations on Large Tables
-- 💥 OUTAGE HAZARD on 2 crore rows: bare ADD CONSTRAINT
-- takes an EXCLUSIVE lock and scans every row (timeouts).
-- Practice copy of RetailMart's 1.5 lakh orders
-- (RetailMart itself stays read-only):
CREATE SCHEMA IF NOT EXISTS practice;
CREATE TABLE practice.big_orders AS
  SELECT order_id, net_total FROM sales.orders;
-- ✅ STEP 1: NOT VALID (instant <1ms lock!)
-- enforces the rule for all FUTURE writes:
ALTER TABLE practice.big_orders
  ADD CONSTRAINT chk_orders_positive_total
  CHECK (net_total >= 0) NOT VALID;
-- ✅ STEP 2: validate old rows (SHARE UPDATE EXCLUSIVE
-- lock): the app keeps reading and WRITING meanwhile!
ALTER TABLE practice.big_orders
  VALIDATE CONSTRAINT chk_orders_positive_total;
-- Proof: convalidated = true
SELECT conname, convalidated FROM pg_constraint
WHERE conname = 'chk_orders_positive_total' LIMIT 10;
-- Drop any constraint by name (try it next):
-- ALTER TABLE practice.big_orders
--   DROP CONSTRAINT chk_orders_positive_total;
🏆Enterprise DBA Best Practice: 1) Always sanitize existing NULLs with UPDATE before running SET NOT NULL. 2) In high-throughput databases (Flipkart, Amazon, Swiggy), never run bare ADD CONSTRAINT; always pair NOT VALID with background VALIDATE CONSTRAINT.

RetailMart Constraints Audit

How RetailMart protects ~24 lakh rows across 55 tables. These are its real CHECK rules: run the query below to see them yourself.

sales.orders
Money Guardrail
CHECK (discount_amount >= 0) stops negative discounts; DEFAULT 'Processing' fills in the status of every new order.
sales.order_items
Line-Item Sanity
CHECK (quantity > 0) blocks empty order lines; CHECK (unit_price > 0) blocks free-item checkout exploits.
customers.reviews
Rating Range
CHECK (rating BETWEEN 1 AND 5) keeps every review between 1 and 5 stars; products.products adds CHECK (price > 0).
SYSTEM CATALOG INTROSPECTION ▶ Run Query
How to Inspect Active Constraints in Any PostgreSQL Database:
-- Query all constraints on any table via information_schema:
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'sales' AND table_name = 'orders' LIMIT 10;

-- Inspect the exact CHECK expressions defined on your tables:
SELECT constraint_name, check_clause
FROM information_schema.check_constraints
WHERE constraint_schema = 'sales' LIMIT 10;
65%

Query Mode: Unlocked — Relational Bridges & Keys

The Ultimate Capstone Arena! Architect production schemas combining primary keys, foreign key laser bridges, domain checks, unique natural keys, and zero-downtime evolution.

🏆Grand Capstone

Interview Studio: Constraints & Integrity

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 & Keys 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. Can a column with a standard UNIQUE constraint hold multiple NULLs?
3. How do you prevent overlapping bookings when UNIQUE cannot do it?
4. How do you add a CHECK constraint without taking down a 2-crore row table?
5. When would you declare a constraint DEFERRABLE INITIALLY DEFERRED?
6. What is a Composite UNIQUE constraint?
7. What is the difference between ON DELETE RESTRICT, CASCADE and SET NULL?
8. What is the fundamental difference between PRIMARY KEY and UNIQUE NOT NULL?

The 5 Golden Rules Bento

The non-negotiable architectural principles governing relational database constraints and schema integrity.

🛡️ 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 • ENTERPRISE SCALE

Zero-Downtime with NOT VALID

Never run a bare ADD CONSTRAINT on large tables. Add constraints with NOT VALID first for instant sub-second locking, then run background VALIDATE CONSTRAINT.

⏱️ Sub-second catalog lock — zero production table freeze.
⏱️ 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 • REFERENTIAL INTEGRITY

Block by Default, CASCADE with Caution

Never place ON DELETE CASCADE across top-level financial entities. Rely on the default (NO ACTION, which blocks like RESTRICT) to shield master customer history and transaction ledgers from accidental cascading deletion.

🔒 The default block prevents accidental wipeout of revenue history.

Constraints Quick Reference Studio

Core PostgreSQL constraint definitions, advanced patterns, and ALTER syntax ready to copy directly into your terminal.

Constraints & Keys 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 &&)

-- Foreign Key Referential Rules:
CONSTRAINT fk_orders_cust FOREIGN KEY (cust_id)
  REFERENCES customers(cust_id) ON DELETE RESTRICT
CONSTRAINT fk_items_order FOREIGN KEY (order_id)
  REFERENCES orders(order_id) ON DELETE CASCADE
CONSTRAINT fk_staff_manager FOREIGN KEY (manager_id)
  REFERENCES managers(manager_id) ON DELETE SET NULL
-- ALTER TABLE & ZERO-DOWNTIME MIGRATIONS
-- 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;

-- Zero-Downtime 2-Step Pattern for Large Tables:
ALTER TABLE t ADD CONSTRAINT chk_val CHECK (val > 0) NOT VALID;
ALTER TABLE t VALIDATE CONSTRAINT chk_val;

-- 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 moving on to DISTINCT & Filtering.

🎯 THE 4-STEP INTEGRITY SPRINT:
Step 1 — Multi-Tenant Uniqueness

Create hospital.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 hospital.operating_rooms preventing overlapping surgery times in the same room.

Step 4 — Zero-Downtime Migration

Take an existing table, simulate dirty rows, sanitize them, and apply a named CHECK rule using NOT VALID followed by VALIDATE CONSTRAINT.

Master Curriculum Journey Roadmap

TheShiraverse Curriculum Flow
🟢 ACTIVE WAYPOINT: MODULE 1 • TOPIC 05 (Constraints & Keys)
NEXT MISSION: TOPIC 06 (DISTINCT & Filtering) • 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 & Keys
NOT NULL • DEFAULT UNIQUE • Composite CHECK • EXCLUDE gist PRIMARY & FOREIGN KEY
📍 YOU ARE HERE ACTIVE
06
TOPIC 06 Up Next
DISTINCT & Filtering
DISTINCT Logic WHERE Predicates AND / OR / NOT LIKE • ILIKE • IN
🔒 NOT STARTED UP NEXT
✦ THE FOCUSED ASCENT: ZERO CONCEPTS ➔ SCHEMAS & ARCHITECTURE ➔ FIRST QUERIES & MUTATIONS ➔ STRICT DATA TYPES ➔ CONSTRAINTS & KEYS ➔ DISTINCT & FILTERING ✦

Next Session: Mastering DISTINCT & Filtering (WHERE, AND, OR, NOT)

NEXT MISSION • QUERYING & AGGREGATION
DISTINCT & Filtering: Surgical Data Extraction
Moving from defining schemas to asking precise business questions — eliminating duplicate rows, chaining Boolean logic, pattern matching, and indexing for filter performance.
• THE SHIRAVERSE
✨
1. DISTINCT Deduplication
Deduplicate query results cleanly, count unique active customers, and recognize when DISTINCT masks underlying schema design flaws.
🧭
2. Boolean WHERE Predicates
Master operator precedence (AND binds before OR!), parentheses guardrails, and safe evaluation pipelines.
🔍
3. Pattern Matching & IN Ranges
Extract insights with LIKE, case-insensitive ILIKE, discrete membership with IN (...), and interval slicing.
→ next   ← back   F fullscreen   H hide bars   N notes
1 / 41