DSA, Database System & Operating System β Data Models, Normalization, and SQL, NEC licence examination syllabus (Nepal Engineering Council).
The schema is the design; the instance is the data currently in it β a distinction exactly like class versus object.
makemigrations, Rails' ActiveRecord::Migration, Flyway, Liquibase β exists to version the schema while leaving the instance alone. That is why a deploy can add a column to a table holding ten million rows without deleting any of them. It is also why "schema migration" is a scary phrase in production: the schema change is instantaneous in the catalog but may rewrite every row on disk, locking the table meanwhile. Search "zero downtime schema migration" β the techniques all come down to making schema changes that no existing instance violates.-- ======== THE SCHEMA β written once ========
CREATE TABLE department (
code CHAR(6) PRIMARY KEY,
name VARCHAR(60) NOT NULL UNIQUE,
budget NUMERIC(12,2) CHECK (budget >= 0)
);
CREATE TABLE student (
roll INTEGER PRIMARY KEY,
name VARCHAR(60) NOT NULL,
dept_code CHAR(6) NOT NULL REFERENCES department(code),
marks NUMERIC(5,2) CHECK (marks BETWEEN 0 AND 100),
admitted_on DATE NOT NULL DEFAULT CURRENT_DATE
);
-- ======== INSTANCE at time T1 ========
INSERT INTO department VALUES
('ACtE07', 'Computer Engineering', 5000000),
('AExE01', 'Electrical Engineering', 4200000);
INSERT INTO student (roll, name, dept_code, marks) VALUES
(101, 'Ram Bahadur', 'ACtE07', 87.5),
(102, 'Sita Devi', 'ACtE07', 91.0),
(103, 'Hari Prasad', 'AExE01', 76.5);
-- instance: 2 departments, 3 students
-- ======== INSTANCE at time T2 β same schema ========
INSERT INTO student (roll, name, dept_code, marks) VALUES
(104, 'Gita Kumari', 'ACtE07', 68.0);
UPDATE student SET marks = 89.0 WHERE roll = 101;
DELETE FROM student WHERE roll = 103;
-- instance: 2 departments, 3 students (different ones)
-- THE SCHEMA DID NOT CHANGE. Only the data did.
-- ======== OPERATIONS THE SCHEMA REJECTS ========
-- Each of these would create an INVALID instance.
INSERT INTO student (roll, name, dept_code, marks)
VALUES (101, 'Duplicate', 'ACtE07', 50);
-- ERROR: duplicate key value violates unique constraint
-- "student_pkey"
-- DETAIL: Key (roll)=(101) already exists.
INSERT INTO student (roll, name, dept_code, marks)
VALUES (105, 'Bad Marks', 'ACtE07', 150);
-- ERROR: new row violates check constraint
-- "student_marks_check"
INSERT INTO student (roll, name, dept_code, marks)
VALUES (106, 'Ghost Dept', 'XXXXXX', 70);
-- ERROR: insert or update on table "student" violates
-- foreign key constraint "student_dept_code_fkey"
INSERT INTO student (roll, name, dept_code, marks)
VALUES (107, NULL, 'ACtE07', 70);
-- ERROR: null value in column "name" violates
-- not-null constraint
DELETE FROM department WHERE code = 'ACtE07';
-- ERROR: update or delete on table "department" violates
-- foreign key constraint on table "student"
-- (students still reference it)
VALUES (101, 'Duplicate', 'ACtE07', 50, NULL) β supplying NULL for admitted_on rather than letting the DEFAULT apply β produces a not-null violation on admitted_on, not the duplicate-key error, because row-level checks run before the index is consulted. When a statement violates several constraints, which error you see depends on the engine's evaluation order. That is why "it failed with error X" is weaker evidence than "it failed" β always fix the constraint the message names, then re-run.-- The hard part of schema change is that an INSTANCE already
-- exists and must remain valid after the change.
-- ===== SAFE: adding a nullable column =====
-- Existing 10,000,000 rows get NULL. No rewrite needed in
-- PostgreSQL 11+, so this is instant.
ALTER TABLE student ADD COLUMN email VARCHAR(120);
-- ===== DANGEROUS: adding a NOT NULL column with no default
-- Every existing row would violate it immediately. =====
ALTER TABLE student ADD COLUMN phone VARCHAR(15) NOT NULL;
-- ERROR: column "phone" of relation "student" contains
-- null values
-- ===== THE THREE-STEP SAFE PATTERN =====
-- 1. add it nullable
ALTER TABLE student ADD COLUMN phone VARCHAR(15);
-- 2. backfill the existing instance
UPDATE student SET phone = 'unknown' WHERE phone IS NULL;
-- 3. NOW the constraint can be added, because no row
-- violates it
ALTER TABLE student ALTER COLUMN phone SET NOT NULL;
-- ===== TIGHTENING A CONSTRAINT =====
-- Suppose marks were CHECK(0..100) and we now want 0..100
-- with at most 2 decimals AND a pass floor of 32 recorded.
-- Existing rows must be inspected FIRST.
SELECT roll, marks FROM student WHERE marks < 32;
-- decide what to do with those rows, THEN:
ALTER TABLE student
ADD CONSTRAINT chk_marks_pass CHECK (marks >= 32);
-- fails if ANY existing row breaks it β the DBMS validates
-- the whole instance against the new schema.
-- ===== WIDENING vs NARROWING =====
ALTER TABLE student ALTER COLUMN name TYPE VARCHAR(120);
-- SAFE: every existing 60-char value fits in 120
ALTER TABLE student ALTER COLUMN name TYPE VARCHAR(20);
-- ERROR if any name exceeds 20 characters.
-- Narrowing must be validated against the instance.
-- ===== RENAMING: schema change, instance untouched =====
ALTER TABLE student RENAME COLUMN marks TO total_marks;
-- The DATA does not move at all β only the catalog entry
-- changes. But every view, query and application naming
-- "marks" now breaks. Cheap on disk, expensive on
-- dependencies.
$jsonSchema validators). The interesting middle ground is schema-on-read, where files are stored raw and a schema is applied at query time β how Parquet, Avro and data lakes work, with schema evolution rules that formalise exactly the safe/unsafe distinctions above. Search "schema on read vs schema on write" and "Avro schema evolution compatibility".INFORMATION_SCHEMA. Know the term valid instance (one satisfying every schema constraint) and be ready to give constraint violations as examples of rejected instances. Mention that the three architecture levels each have their own schema.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦