Module 1 › Keys

Keys: primary keys, foreign keys & ON DELETE

Module 1, Session 6. PRIMARY KEY and identity columns, FOREIGN KEY and orphan rows, 1:1, 1:N and M:N relationships, ON DELETE and ON UPDATE rules, self-referencing keys, and safe ALTER TABLE changes on live tables.

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

← All NukteSELECT * FROM TheShiraverse

Primary & Foreign Keys

Give every row one permanent identity, link tables so no row can point at something that does not exist, and decide what happens when a parent row is deleted.

27
Interactive Scenes
1
Practice Arenas
8
Hands-on Drills
6
Interview Scenarios

Today’s Agenda — Keys

Four pillars: give every row an identity, then link tables safely

Pillar 01
🔑 Identity
01 The Exam Hall Story: Results Day
02 Key Taxonomy: Candidate, Natural & Surrogate
03 PRIMARY KEY & Identity Columns
Pillar 02
🔗 Relationships
04 FOREIGN KEY & Relationship Types: 1:1, 1:N, M:N
05 ON DELETE: RESTRICT, CASCADE, SET NULL & the default NO ACTION
06 Self-Referencing Keys & ON UPDATE
Pillar 03
🛠️ Changing Live Tables
07 ALTER TABLE — Add & Drop Rules
08 Zero-Downtime with NOT VALID
09 RetailMart Constraints Audit
Pillar 04
🎯 Practice & Next
10 Keys Capstone Arena
11 Interview Questions & Self-Check
12 Next class: DISTINCT & Filtering
🔑
Constraints guard one table. Keys connect tables. A PRIMARY KEY gives every row one permanent identity; a FOREIGN KEY makes sure every link points at a row that really exists.

Where We Left Off

From rules on one column to links between tables

📦 SESSION 05 • COMPLETED

What Constraints Solved

-- Session 5: every column has a rule
CREATE TABLE orders (
amount  NUMERIC(10,2) NOT NULL CHECK (amount > 0),
status  TEXT NOT NULL DEFAULT 'pending',
coupon  TEXT UNIQUE
);
Last class, every column got a rule. A blank amount, a negative amount or a repeated coupon is now rejected by PostgreSQL itself, whatever app or script sends it.
✓ NOT NULL, DEFAULT, CHECK and UNIQUE guard every write
⚠️ SESSION 06 • THE MISSION

Why Column Rules Alone Are Not Enough

-- Every column rule passes, yet the row is wrong:
INSERT INTO orders (customer_id, amount)
VALUES (999, 1200.00);
⚠️ There is no customer 999! Nothing links orders to customers.
Constraints look at one row of one table. They cannot ask “does customer 999 exist?” or “which row is the customer?” — that is the job of keys.
🔑 Today: one identity per row, and links that cannot break
🛡️
The Core Distinction: Constraints decide what one row may contain. Keys decide which row is which, and which rows may point at each other.
🏫
The Exam Hall: Results Day ROWS WITH NO IDENTITY AND NO LINKS
SECOND UNIVERSITY EXAMPLE STORY • NAMES & NUMBERS MADE UP 🕘 RESULTS DAY
STEP 01 • THE SHORTCUT
university-registrar Results week
“We copied the column rules from the last exam. Linking tables can wait till next year.”
A second university copied only the NOT NULL and CHECK rules. Roll numbers were never made a key, and the hall column was never linked to the halls table.
STEP 02 • TOTAL SYSTEM COLLAPSE
💥 Roll 1047, Twice
No PRIMARY KEY
Two candidates share roll number 1047. One result is published under both names — nobody can say which marks belong to whom.
💥 Ghost Hall 99 & a Deleted Hall
No FOREIGN KEY
Results point at Hall 99, which does not exist. Then Hall 4 is deleted while 300 candidates still point at it.
STEP 03 • THE UNBYPASSABLE RULES
KEYS
🐘 Identity & Links
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.exam_candidates,
  keys.halls CASCADE;
CREATE TABLE keys.halls (
  hall_id   INT PRIMARY KEY,
  hall_name TEXT NOT NULL
);
CREATE TABLE keys.exam_candidates (
  roll_no   INT PRIMARY KEY,
  name      TEXT NOT NULL,
  hall_id   INT REFERENCES keys.halls(hall_id)
            ON DELETE RESTRICT
);
-- One roll number = one candidate. No ghost halls.
✓ 2 Keys + 1 Delete Rule Zero Mix-ups
STEP 04 • CORE PRINCIPLE
The Moral: Rules on columns keep each row clean; keys keep rows apart and tables connected. A PRIMARY KEY says which row is which; a FOREIGN KEY makes every link point at a row that really exists.

Translating Results Day to PostgreSQL

Every Mix-up Has a Key
ROSETTA STONE • 6 KEY RULES

Each results-day mistake maps to one key or delete rule that PostgreSQL enforces on every write:

Results-Day Mistake Key or Delete Rule Production Defense Guarantee
👥 Roll 1047 Given to Two Candidates PRIMARY KEY (roll_no) The second INSERT with roll 1047 fails (Error 23505): one key value = one row, forever.
📝 Candidate With No Roll Number PRIMARY KEY ⇒ NOT NULL A primary key column can never be NULL (Error 23502), so every row can be found by its key.
🏢 Result Points at Hall 99 FOREIGN KEY (hall_id) hall_id must exist in halls; Hall 99 is rejected (Error 23503) — no orphan rows.
🚫 Hall 4 Deleted With 300 Candidates ON DELETE RESTRICT The DELETE is blocked while candidates still point at Hall 4. With no rule written, the default NO ACTION blocks it too.
🗑️ Exam Cancelled: Remove Its Seat List ON DELETE CASCADE Deleting the exam deletes its seat allocations with it — for child rows that mean nothing alone.
👤 Invigilator Leaves the University ON DELETE SET NULL The halls stay; their invigilator_id becomes NULL until someone new is assigned.
⚖️
Parent First, Child Second: a FOREIGN KEY means the hall must exist before a candidate can be seated in it. Insert parents first; delete children first, or let ON DELETE decide.

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
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.taxpayers CASCADE;
CREATE TABLE keys.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 keys.taxpayers (pan_number, email, full_name)
VALUES ('ABCDE1234F', '[email protected]', 'Priya Sharma');
SELECT taxpayer_id, pan_number, email, full_name FROM keys.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
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- 1. Modern single-column surrogate primary key:
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.shop_order_items,
  keys.shop_orders CASCADE;
CREATE TABLE keys.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 keys.shop_order_items (
  order_id   BIGINT REFERENCES keys.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 keys.shop_orders (total) VALUES (2499.00);                -- order_id 1 is generated
INSERT INTO keys.shop_order_items VALUES (1, 1, 2), (1, 2, 1);        -- same order, different items: OK
SELECT order_id, item_id, quantity FROM keys.shop_order_items LIMIT 10;
-- Now try (FAILS with Error 23505): the pair (1, 1) already exists
-- INSERT INTO keys.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
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.orders,
  keys.customers CASCADE;
CREATE TABLE keys.customers (
  cust_id    INT PRIMARY KEY,
  full_name  VARCHAR(100) NOT NULL
);

-- Step 2: Child table enforces referential relationship
CREATE TABLE keys.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 keys.customers(cust_id)
);

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

-- Blocked write: customer 999 does not exist (Orphan Shield fires)
INSERT INTO keys.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".

Relationship Types: 1:1, 1:N & M:N

Where you put the foreign key decides how two tables relate. Three shapes cover every database you will meet, and RetailMart has all three.

One-to-Many · 1:N
One customer, many orders
customers1 —< Norders
The foreign key sits on the “many” side: orders.cust_id → customers. RetailMart: 50,000 customers and 1,50,000 orders. The most common link there is.
One-to-One · 1:1
A foreign key that is also unique
customers1 —— 1loyalty.members
Make the child’s foreign key its PRIMARY KEY (or UNIQUE): each parent gets at most one row. RetailMart: loyalty.members(customer_id), 30,000 members, one per customer.
Many-to-Many · M:N
A bridge table with two keys
products—<inventory>—stores
No single foreign key can hold it. A third table carries both keys and a composite PRIMARY KEY. RetailMart: products.inventory, 1,14,153 rows linking 6,036 products and 200 stores.
Rule to Remember
Where the foreign key goes
1:N → on the many side. 1:1 → on one side, made UNIQUE. M:N → on neither side: both keys go in the bridge table.
Many-to-Many with a Bridge Table
▶ Run Query
PostgreSQL
SYNTAXMany-to-Many with a Bridge Table
-- Many-to-many: students and courses, linked by a bridge table
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.enrollments,
  keys.courses, keys.students CASCADE;
CREATE TABLE keys.students (
  student_id  INT PRIMARY KEY,
  full_name   VARCHAR(100) NOT NULL
);
CREATE TABLE keys.courses (
  course_id   INT PRIMARY KEY,
  title       VARCHAR(100) NOT NULL
);
-- The bridge: one row per (student, course) pair, two foreign keys
CREATE TABLE keys.enrollments (
  student_id   INT REFERENCES keys.students(student_id),
  course_id    INT REFERENCES keys.courses(course_id),
  enrolled_on  DATE NOT NULL DEFAULT CURRENT_DATE,
  PRIMARY KEY (student_id, course_id)
);

INSERT INTO keys.students VALUES (1, 'Aarav'), (2, 'Diya');
INSERT INTO keys.courses  VALUES (10, 'SQL'), (20, 'Python');
-- Aarav takes 2 courses, SQL has 2 students: many-to-many
INSERT INTO keys.enrollments (student_id, course_id) VALUES (1, 10), (1, 20), (2, 10);

-- The same pair twice is blocked by the composite PRIMARY KEY
INSERT INTO keys.enrollments (student_id, course_id) VALUES (1, 10);
-- 💥 ERROR: duplicate key value violates unique constraint "enrollments_pkey"
-- DETAIL: Key (student_id, course_id)=(1, 10) already exists.

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
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.staff,
  keys.comments, keys.departments,
  keys.managers, keys.blog_posts CASCADE;
CREATE TABLE keys.blog_posts  (post_id INT PRIMARY KEY, title VARCHAR(100) NOT NULL);
CREATE TABLE keys.managers    (manager_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL);
CREATE TABLE keys.departments (dept_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL);
-- Children: one ON DELETE rule each
CREATE TABLE keys.comments (comment_id SERIAL PRIMARY KEY,
  post_id INT REFERENCES keys.blog_posts(post_id) ON DELETE CASCADE);   -- disposable: delete them too
CREATE TABLE keys.staff (staff_id SERIAL PRIMARY KEY,
  manager_id INT REFERENCES keys.managers(manager_id) ON DELETE SET NULL, -- reassignable: unlink (must allow NULL)
  dept_id INT NOT NULL REFERENCES keys.departments(dept_id) ON DELETE RESTRICT); -- critical: block it
INSERT INTO keys.blog_posts VALUES (1, 'Intro to SQL');
INSERT INTO keys.comments (post_id) VALUES (1), (1);
INSERT INTO keys.managers VALUES (7, 'Priya');
INSERT INTO keys.departments VALUES (10, 'Tech');
INSERT INTO keys.staff (manager_id, dept_id) VALUES (7, 10);
DELETE FROM keys.blog_posts WHERE post_id = 1;    -- CASCADE: both comments are deleted too
DELETE FROM keys.managers WHERE manager_id = 7;   -- SET NULL: staff row stays, manager_id becomes NULL
SELECT staff_id, manager_id, dept_id FROM keys.staff LIMIT 10;
-- Now try RESTRICT: remove the two dashes below and run again. It is blocked with Error 23503.
-- DELETE FROM keys.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.

Self-Referencing Keys & ON UPDATE

A foreign key can point at its own table: every employee’s manager is another employee. ON UPDATE decides what happens to the children when a parent’s key value changes.

Self-Reference
Parent and child, same table
employees↻ manager_id → emp_id
manager_id REFERENCES employees(emp_id). The classic interview schema: employees and managers, or categories and sub-categories.
NULL Is Allowed
The top of the tree
Meeramanager_id = NULL
The CEO has no manager, so manager_id is NULL. A NULL foreign key points at nothing and is not checked. Insert the boss first, then the team.
ON UPDATE CASCADE
A key changes, children follow
Rohan 2 → 20⇒Arjun.manager_id 2 → 20
Rohan’s id changes from 2 to 20 and Arjun’s manager_id follows by itself. With no ON UPDATE rule (NO ACTION), that UPDATE is blocked.
Design Rule
Good keys never change
Needing ON UPDATE CASCADE means the key carries meaning. A surrogate key (IDENTITY) never changes, so ON UPDATE never fires. One more reason surrogates win.
Self-Referencing Foreign Key
▶ Run Query
PostgreSQL
SYNTAXSelf-Referencing Foreign Key
-- A foreign key that points at its own table
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.employees CASCADE;
CREATE TABLE keys.employees (
  emp_id      INT PRIMARY KEY,
  full_name   VARCHAR(100) NOT NULL,
  manager_id  INT REFERENCES keys.employees(emp_id)
                ON DELETE SET NULL
                ON UPDATE CASCADE
);
-- Boss first (no manager), then the team
INSERT INTO keys.employees VALUES
  (1, 'Meera', NULL),
  (2, 'Rohan', 1), (3, 'Kavita', 1),
  (4, 'Arjun', 2);

-- ON UPDATE CASCADE: Rohan's id changes from 2 to 20, Arjun follows
UPDATE keys.employees SET emp_id = 20 WHERE emp_id = 2;
SELECT emp_id, full_name, manager_id FROM keys.employees ORDER BY emp_id;
-- Arjun: manager_id 2 -> 20

-- ON DELETE SET NULL: Rohan leaves, Arjun stays with no manager
DELETE FROM keys.employees WHERE emp_id = 20;
SELECT emp_id, full_name, manager_id FROM keys.employees ORDER BY emp_id;
-- Arjun: manager_id 20 -> NULL

-- A manager who does not exist is still an orphan
INSERT INTO keys.employees VALUES (5, 'Sana', 99);
-- 💥 ERROR: ... violates foreign key constraint "employees_manager_id_fkey"
-- DETAIL: Key (manager_id)=(99) is not present in table "employees".
⚡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
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.orders,
  keys.customers CASCADE;
CREATE TABLE keys.customers (
  customer_id SERIAL PRIMARY KEY, phone_number VARCHAR(15));
CREATE TABLE keys.orders (
  order_id SERIAL PRIMARY KEY, order_status VARCHAR(20));
INSERT INTO keys.customers (phone_number)
VALUES ('9876543210'), (NULL);
-- ⚠️ GOTCHA: SET NOT NULL fails on NULLs; fix them first:
UPDATE keys.customers SET phone_number = '0000000000'
WHERE phone_number IS NULL;
-- Make the column mandatory, then remove the rule:
ALTER TABLE keys.customers
  ALTER COLUMN phone_number SET NOT NULL;
ALTER TABLE keys.customers
  ALTER COLUMN phone_number DROP NOT NULL;
-- Auto-fill default for future inserts, then remove it:
ALTER TABLE keys.orders
  ALTER COLUMN order_status SET DEFAULT 'pending';
ALTER TABLE keys.orders
  ALTER COLUMN order_status DROP DEFAULT;
-- 💡 Affects only FUTURE inserts; old rows stay untouched.
SELECT customer_id, phone_number
FROM keys.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).
-- 1,50,000 practice orders (as many as RetailMart has),
-- made with generate_series, so it runs anywhere:
-- in the practice database (\c practice)
CREATE SCHEMA IF NOT EXISTS keys;
-- start clean, safe to re-run
DROP TABLE IF EXISTS keys.big_orders CASCADE;
CREATE TABLE keys.big_orders AS
  SELECT g AS order_id, ROUND((g % 5000) * 1.5 + 99, 2) AS net_total
  FROM generate_series(1, 150000) AS g;
-- ✅ STEP 1: NOT VALID (a very short lock)
-- enforces the rule for all FUTURE writes:
ALTER TABLE keys.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 keys.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 keys.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 Keys Capstone Arena! Build schemas that combine primary keys, foreign keys, ON DELETE rules and the constraints from last class.

🏆Grand Capstone

Interview Studio: Keys

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

TOP INTERVIEW QUESTIONS
KEYS & RELATIONSHIPS

“What is the difference between UNIQUE and PRIMARY KEY?”

✓ MODEL ANSWER:
A table can have many UNIQUE constraints but only one PRIMARY KEY. UNIQUE allows NULLs; a PRIMARY KEY is UNIQUE + NOT NULL. The PRIMARY KEY is the row’s official identity — the column other tables point at with a FOREIGN KEY — while UNIQUE protects business values like an email address.
🧠 WHY INTERVIEWERS ASK THIS:

Checks that you can tell identity apart from uniqueness, the base of every relational design.

⚠️ COMMON TRAP TO AVOID:

Saying they are the same thing. UNIQUE still lets NULLs in, and a table can have only one PRIMARY KEY.

Keys Self-Check

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

1. What is the fundamental difference between PRIMARY KEY and UNIQUE NOT NULL?
2. What is a PRIMARY KEY equal to, in terms of other constraints?
3. What does a FOREIGN KEY guarantee?
4. Parent or child first when inserting? Why?
5. What is the difference between ON DELETE RESTRICT, CASCADE and SET NULL?
6. Why use the blocking rule + soft-delete for financial data?
7. How do you add a CHECK constraint without taking down a 2-crore row table?
8. What is the difference between a natural key and a surrogate key?

The 5 Golden Rules Bento

The non-negotiable architectural principles for every key you design.

🔑 RULE 01 • IDENTITY

Every Table Gets a PRIMARY KEY

A PRIMARY KEY is UNIQUE + NOT NULL, one per table. Prefer a surrogate (GENERATED ALWAYS AS IDENTITY) and keep natural ids like email as UNIQUE.

✅ customer_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
🔗 RULE 02 • NO ORPHANS

A FOREIGN KEY Means No Orphan Rows

The child column may only hold values that exist in the parent’s key. An order for customer 999 that does not exist is rejected with Error 23503.

⚠️ customer_id INT REFERENCES customers(customer_id)
📝 RULE 03 • ORDER OF WORK

Parent First, Child Second

Insert the parent row before the child that points at it; delete children before the parent. Give the FK column the same type as the parent key (INT → INT).

➡️ INSERT customer → INSERT order
🚀 RULE 04 • ENTERPRISE SCALE

Zero-Downtime with NOT VALID

Never run a bare ADD CONSTRAINT on large tables. Add constraints with NOT VALID first (a very short lock), then run VALIDATE CONSTRAINT.

⏱️ A very short lock — no long table freeze.
🔗 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.

Keys Quick Reference Studio

Primary keys, foreign keys, ON DELETE rules and safe ALTER syntax, ready to copy into your terminal.

Keys Cheat Sheet
SQL REFERENCE
-- PRIMARY KEY, FOREIGN KEY & ON DELETE
-- PRIMARY KEY: one per table (UNIQUE + NOT NULL):
customer_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
order_id    SERIAL PRIMARY KEY
email       VARCHAR(150) UNIQUE NOT NULL  -- natural id

-- Composite PRIMARY KEY (junction table):
PRIMARY KEY (student_id, course_id)

-- FOREIGN KEY: child points at an existing parent:
customer_id INT REFERENCES customers(customer_id)
CONSTRAINT fk_orders_cust FOREIGN KEY (customer_id)
  REFERENCES customers(customer_id)

-- ON DELETE: what happens to the children:
... REFERENCES p(id)                      -- NO ACTION: block (default)
... REFERENCES p(id) ON DELETE RESTRICT  -- block
... REFERENCES p(id) ON DELETE CASCADE   -- delete children
... REFERENCES p(id) ON DELETE SET NULL  -- keep, unlink
-- ALTER TABLE & ZERO-DOWNTIME MIGRATIONS
-- Add & drop keys on an existing table:
ALTER TABLE t ADD PRIMARY KEY (id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_cust
  FOREIGN KEY (customer_id) REFERENCES customers(customer_id);
ALTER TABLE orders DROP CONSTRAINT fk_orders_cust;

-- 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;

-- See every rule on a table (p = PK, f = FK, u, c):
SELECT conname, contype
FROM   pg_constraint
WHERE  conrelid = 'keys.customers'::regclass;

Take-Home Challenge: Keys That Hold

Complete these hands-on key drills in your local practice database before moving on to DISTINCT & Filtering.

🎯 400 practice questions for this session · Easy to Crazy hard, run and checked on the pagePRACTICE →
🎯 THE 4-STEP KEYS SPRINT:
Step 1 — Give Every Table an Identity

In practice, create customers and orders, each with a GENERATED ALWAYS AS IDENTITY PRIMARY KEY. Add a FOREIGN KEY from orders.customer_id to customers.

Step 2 — Prove Every Rule Fires

Write 4 INSERTs that each fail for a different reason: NOT NULL (23502), UNIQUE (23505), CHECK (23514) and FOREIGN KEY (23503). Save each error message.

Step 3 — Cascade the Line Items

Add order_items with ON DELETE CASCADE to orders. Delete one order and confirm with a SELECT that its items are gone too.

Step 4 — Block, Then Soft-Delete

Try to delete a customer who still has orders and read the 23503 error. Then add is_active BOOLEAN NOT NULL DEFAULT true and “delete” the customer with an UPDATE instead.

Master Curriculum Journey Roadmap

TheShiraverse Curriculum Flow
🟢 ACTIVE WAYPOINT: MODULE 1 • TOPIC 06 (Keys)
NEXT MISSION: TOPIC 07 (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
02
TOPIC 02 Session 2 • Done
How SQL is Organised
Database Hierarchy Schemas as Folders Tables as Files Catalog Introspection
✓ COMPLETED
03
TOPIC 03 Session 3 • Done
Writing First Queries
SELECT Evaluation CREATE • ALTER • DROP INSERT • UPDATE • DELETE The Safe WHERE Rule
✓ COMPLETED
04
TOPIC 04 Session 4 • Done
Data Types & Casts
Ending All-TEXT NUMERIC vs FLOAT INT • DATE • TIMESTAMPTZ ALTER ... TYPE ... USING
✓ COMPLETED
05
TOPIC 05 Session 5 • Done
Constraints
NOT NULL • DEFAULT CHECK • NULL Trap UNIQUE • Composite EXCLUDE gist
✓ COMPLETED
06
TOPIC 06 Today • Live
Keys
PRIMARY KEY FOREIGN KEY ON DELETE ALTER • NOT VALID
📍 YOU ARE HERE
07
TOPIC 07 Up Next
DISTINCT & Filtering
DISTINCT Logic WHERE Predicates AND / OR / NOT LIKE • ILIKE • IN
🔒 NOT STARTED
✦ THE FOCUSED ASCENT: SCHEMAS ➔ FIRST QUERIES ➔ DATA TYPES ➔ CONSTRAINTS ➔ KEYS ➔ DISTINCT & FILTERING ✦

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

NEXT MISSION • MODULE 1 • FOUNDATIONS
DISTINCT & Filtering: Surgical Data Extraction
Tables now have rules and keys. Next we stop building and start 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 / 27