DSA, Database System & Operating System β Data Models, Normalization, and SQL, NEC licence examination syllabus (Nepal Engineering Council).
Normal Forms (1NF, 2NF, 3NF, BCNF)
Removing redundancy step by step β each normal form eliminates one specific class of anomaly.
π Where this lives: normalization is the reason your bank's address appears in one place. Store a customer's address on every transaction row and you have three guaranteed problems: the same address occupies a thousand rows (waste), correcting it means updating a thousand rows (update anomaly), and if one update fails you now have two contradictory addresses with no way to tell which is right (inconsistency). That third one is the real killer β the database no longer knows the truth. Analytics systems deliberately denormalize for read speed, which is a considered trade of consistency for performance, not an excuse to skip this. Search "star schema denormalization OLAP vs OLTP".
The three anomalies normalization prevents
Consider an UNNORMALIZED table:
student_course(roll, sname, dept, dept_head,
course, ctitle, credits, grade)
roll sname dept dept_head course ctitle cr grade
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
101 Ram ACtE07 Sharma ACtE0703 Database 3 B
101 Ram ACtE07 Sharma AExE0101 Circuits 4 A
102 Sita ACtE07 Sharma ACtE0703 Database 3 A
103 Hari AExE01 Karki AExE0101 Circuits 4 C
1. INSERTION ANOMALY
You cannot record a new course that nobody has enrolled in
yet β there is no roll number to supply, and roll is part
of the key.
You cannot record a new department without a student.
2. UPDATE ANOMALY
Prof. Sharma is replaced as head of ACtE07. That value
appears in THREE rows. Update two and miss one, and the
database now says ACtE07 has two different heads. There is
no way to determine which is correct.
3. DELETION ANOMALY
Delete Hari's only enrolment (row 4) and you lose the fact
that AExE0101 is titled "Circuits" and carries 4 credits,
and that AExE01's head is Karki. Deleting an enrolment
destroyed unrelated information.
PLUS REDUNDANCY: "Database Systems" and "3" are stored twice;
"Sharma" three times. In a real table with 50,000 enrolments
across 200 courses, each course title is stored ~250 times.
THE ROOT CAUSE, in every case: attributes that depend on
something other than the whole primary key are stored in a
table keyed by that whole key. Normalization is the procedure
for detecting and fixing exactly that.
1NF β atomic values
A relation is in FIRST NORMAL FORM if:
Β· every attribute value is ATOMIC (indivisible)
Β· there are no repeating groups or multivalued attributes
Β· every row has the same number of columns
Β· each column holds values of one domain
VIOLATIONS and their fixes:
β MULTIVALUED CELL
roll name phones
101 Ram 9841000001, 9851000002
Fix: a separate table
student(roll, name)
student_phone(roll, phone) PK (roll, phone)
β REPEATING GROUP (columns numbered 1..n)
roll name phone1 phone2 phone3
101 Ram 9841000001 9851000002 NULL
Fix: the same separate table.
Why the repeating group is worse than it looks:
- what if someone has four phones?
- "find the student with phone X" needs
WHERE phone1=X OR phone2=X OR phone3=X
- most cells are NULL, wasting space
- you cannot index it usefully
β COMPOSITE / STRUCTURED CELL
address = "Kupondole, Lalitpur, Bagmati"
Fix: addr_street, addr_city, addr_province
WHAT COUNTS AS ATOMIC IS DOMAIN-DEPENDENT.
A full name is atomic if you never search by surname; it is
non-atomic if you do. "Atomic" means "not decomposed by any
query this database must answer" β not "short".
MODERN NUANCE: PostgreSQL's ARRAY and JSONB columns are
deliberate 1NF violations, and they are legitimate when the
value is genuinely opaque to the database (a settings blob).
They stop being legitimate the moment you need to query
inside them.
to_1nf.sql
-- ===== NOT in 1NF: repeating group =====
CREATE TABLE student_bad (
roll INTEGER PRIMARY KEY,
name VARCHAR(60),
phone1 VARCHAR(15),
phone2 VARCHAR(15),
phone3 VARCHAR(15) -- what about a 4th?
);
INSERT INTO student_bad VALUES
(101, 'Ram', '9841000001', '9851000002', NULL),
(102, 'Sita', '9841000003', NULL, NULL);
-- finding a student by phone is painful and unindexable
SELECT roll FROM student_bad
WHERE phone1 = '9851000002'
OR phone2 = '9851000002'
OR phone3 = '9851000002';
-- ===== IN 1NF: the repeating group becomes rows =====CREATE TABLE student (
roll INTEGER PRIMARY KEY,
name VARCHAR(60) NOT NULL
);
CREATE TABLE student_phone (
roll INTEGER NOT NULL REFERENCES student(roll)
ON DELETE CASCADE,
phone VARCHAR(15) NOT NULL,
PRIMARY KEY (roll, phone)
);
INSERT INTO student VALUES (101,'Ram'), (102,'Sita');
INSERT INTO student_phone VALUES
(101,'9841000001'), (101,'9851000002'), (102,'9841000003');
-- now the lookup is a single indexed predicate,
-- and any number of phones is supported
SELECT roll FROM student_phone WHERE phone = '9851000002';
-- roll
-- -----
-- 101
SELECT s.name, COUNT(p.phone) AS phones
FROM student s LEFT JOIN student_phone p ON p.roll = s.roll
GROUP BY s.roll, s.name ORDER BY s.roll;
-- name | phones
-- -----+--------
-- Ram | 2
-- Sita | 1
2NF β no partial dependency
A relation is in SECOND NORMAL FORM if:
Β· it is in 1NF, AND
Β· every NON-PRIME attribute is FULLY functionally dependent
on EVERY candidate key
(equivalently: no non-prime attribute depends on only
PART of a candidate key)
2NF only ever matters when a candidate key is COMPOSITE. If
every candidate key is a single attribute, 1NF β 2NF
automatically β there is no "part of the key" to depend on.
EXAMPLE VIOLATION
enrolment(roll, course, sname, ctitle, credits, grade)
candidate key: (roll, course)
FDs:
(roll, course) β grade FULL β fine
roll β sname PARTIAL β 2NF violation
course β ctitle PARTIAL β 2NF violation
course β credits PARTIAL β 2NF violation
Each partial dependency causes redundancy: sname repeats for
every course the student takes; ctitle and credits repeat
for every student in the course.
DECOMPOSITION β one relation per determinant:
student (roll, sname) key roll
course (course, ctitle, credits) key course
enrolment (roll, course, grade) key (roll,course)
VERIFY: is each result in 2NF?
student β single-attribute key, so trivially 2NF β
course β single-attribute key β
enrolment β grade depends on the full key, nothing else β
REDUNDANCY REMOVED, counted:
before: for 50,000 enrolments over 200 courses and 5,000
students, ctitle is stored 50,000 times
after : 200 times (once per course row)
β a 250Γ reduction for that column
3NF β no transitive dependency
A relation is in THIRD NORMAL FORM if:
Β· it is in 2NF, AND
Β· no non-prime attribute is TRANSITIVELY dependent on a
candidate key
EQUIVALENT AND MORE USEFUL DEFINITION:
for every non-trivial FD X β Y at least one holds:
(a) X is a superkey, OR
(b) every attribute of Y is PRIME (part of some
candidate key)
EXAMPLE VIOLATION
student(roll, sname, dept_code, dept_head)
candidate key: roll
FDs: roll β sname β (a)
roll β dept_code β (a)
dept_code β dept_head β VIOLATION
dept_code is not a superkey, and
dept_head is not prime
The transitive chain: roll β dept_code β dept_head
So dept_head depends on roll only INDIRECTLY.
CONSEQUENCE: dept_head is stored once per STUDENT rather
than once per DEPARTMENT. 5,000 students in 10 departments
means each head's name stored ~500 times.
DECOMPOSITION β split at the offending determinant:
student (roll, sname, dept_code) key roll
department(dept_code, dept_head) key dept_code
VERIFY:
student : roll β sname, dept_code. Both from the key β
department : dept_code β dept_head. dept_code IS the key β
WHY CLAUSE (b) EXISTS β a case that IS in 3NF but looks
wrong:
R(city, street, pincode)
FDs: (city, street) β pincode
pincode β city
candidate keys: (city, street) and (street, pincode)
prime attributes: city, street, pincode β ALL of them
pincode β city violates (a): pincode is not a superkey.
But city IS prime, so (b) is satisfied β R is in 3NF.
It is NOT in BCNF, which is exactly the gap between them.
BCNF β every determinant is a superkey
A relation is in BOYCEβCODD NORMAL FORM if:
for EVERY non-trivial FD X β Y, X is a SUPERKEY.
That is 3NF with clause (b) removed. No exception for prime
dependents.
BCNF β 3NF β 2NF β 1NF (each strictly stronger)
THE CLASSIC EXAMPLE β R(student, subject, teacher)
Business rules:
Β· a teacher teaches exactly ONE subject
Β· a student studies a subject with ONE teacher
FDs: (student, subject) β teacher
teacher β subject
candidate keys: (student, subject) and (student, teacher)
prime: student, subject, teacher β all of them
IN 3NF? teacher β subject: teacher is not a superkey, but
subject is PRIME β clause (b) satisfied β YES,
it is in 3NF.
IN BCNF? teacher β subject and teacher is NOT a superkey
β NO.
THE REDUNDANCY THIS PERMITS:
student subject teacher
βββββββββββββββββββββββββββββ
Ram Database Sharma
Sita Database Sharma
Hari Database Sharma
β "Sharma teaches Database" is stored three times, and
nothing prevents a fourth row saying Sharma teaches
Networks.
BCNF DECOMPOSITION β split on teacher β subject:
teaches (teacher, subject) key teacher
studies (student, teacher) key (student,
teacher)
Both are in BCNF. But watch what is LOST:
THE FD (student, subject) β teacher IS NO LONGER
ENFORCEABLE by any single table. To check it you must join.
So this decomposition is:
lossless-join β
dependency-preserving β
THE FUNDAMENTAL TRADE-OFF, and the standard exam answer:
3NF β always achievable with BOTH lossless join AND
dependency preservation
BCNF β always achievable lossless, but dependency
preservation is NOT guaranteed
That is why 3NF is the practical target and BCNF the ideal.
When they conflict, most designers stop at 3NF and enforce
the lost FD with a trigger or an application check.
The (student, subject) β teacher loss is the whole reason BCNF is not simply "better". After decomposition, two separate tables can each be internally valid while jointly violating a rule the original relation enforced automatically. You have traded a redundancy problem for an enforcement problem β and which is worse depends on the application.
Worked example β full normalization to BCNF
normalize.sql
-- ============ STAGE 0: unnormalized ============
-- One wide table with every problem at once.
-- student_course(roll, sname, phones, dept, dept_head,
-- course, ctitle, credits, grade)
-- phones is multivalued -> not even 1NF
-- roll -> sname, dept -> partial (2NF)
-- course -> ctitle, credits -> partial (2NF)
-- dept -> dept_head -> transitive (3NF)
-- ============ STAGE 1: reach 1NF ============
-- split the multivalued attribute out
CREATE TABLE sc_1nf (
roll INTEGER, sname VARCHAR(60),
dept CHAR(6), dept_head VARCHAR(60),
course CHAR(8), ctitle VARCHAR(60), credits SMALLINT,
grade CHAR(2),
PRIMARY KEY (roll, course)
);
CREATE TABLE student_phone (
roll INTEGER, phone VARCHAR(15),
PRIMARY KEY (roll, phone)
);
-- ============ STAGE 2: reach 2NF ============
-- remove the partial dependencies
CREATE TABLE student_2nf (
roll INTEGER PRIMARY KEY,
sname VARCHAR(60) NOT NULL,
dept CHAR(6) NOT NULL,
dept_head VARCHAR(60) NOT NULL -- still transitive!
);
CREATE TABLE course_2nf (
course CHAR(8) PRIMARY KEY,
ctitle VARCHAR(60) NOT NULL,
credits SMALLINT NOT NULL
);
CREATE TABLE enrolment (
roll INTEGER NOT NULL,
course CHAR(8) NOT NULL,
grade CHAR(2),
PRIMARY KEY (roll, course)
);
-- ============ STAGE 3: reach 3NF ============
-- remove the transitive dependency dept -> dept_headCREATE TABLE department (
dept CHAR(6) PRIMARY KEY,
dept_head VARCHAR(60) NOT NULL
);
CREATE TABLE student (
roll INTEGER PRIMARY KEY,
sname VARCHAR(60) NOT NULL,
dept CHAR(6) NOT NULL REFERENCES department(dept)
);
ALTER TABLE enrolment
ADD FOREIGN KEY (roll) REFERENCES student(roll),
ADD FOREIGN KEY (course) REFERENCES course_2nf(course);
ALTER TABLE student_phone
ADD FOREIGN KEY (roll) REFERENCES student(roll)
ON DELETE CASCADE;
-- ============ FINAL SCHEMA β all in BCNF ============
-- department(dept, dept_head) key: dept
-- student(roll, sname, dept) key: roll
-- course_2nf(course, ctitle, credits) key: course
-- enrolment(roll, course, grade) key: (roll,course)
-- student_phone(roll, phone) key: (roll,phone)
--
-- Every determinant in every table IS the key -> BCNF β
-- ============ THE ANOMALIES ARE GONE ============
INSERT INTO department VALUES ('ACtE07','Prof. Sharma'),
('AExE01','Prof. Karki');
INSERT INTO student VALUES (101,'Ram','ACtE07'),
(102,'Sita','ACtE07'),
(103,'Hari','AExE01');
INSERT INTO course_2nf VALUES ('ACtE0703','Database Systems',3),
('AExE0101','Circuit Theory',4);
INSERT INTO enrolment VALUES (101,'ACtE0703','B'),
(101,'AExE0101','A'),
(102,'ACtE0703','A');
-- 1. INSERTION now possible without related data:
INSERT INTO course_2nf VALUES ('ACtE0705','Operating Systems',3);
-- a course with zero enrolments β impossible before-- 2. UPDATE touches exactly ONE row:
UPDATE department SET dept_head = 'Prof. Adhikari'
WHERE dept = 'ACtE07';
-- one row, so partial-update inconsistency CANNOT occur-- 3. DELETION loses nothing unrelated:
DELETE FROM enrolment WHERE roll = 102;
SELECT * FROM course_2nf WHERE course = 'ACtE0703';
-- the course title and credits survive β
-- and the original wide view is recoverable by joining:
SELECT s.roll, s.sname, d.dept, d.dept_head,
c.course, c.ctitle, c.credits, e.grade
FROM student s
JOIN department d ON d.dept = s.dept
JOIN enrolment e ON e.roll = s.roll
JOIN course_2nf c ON c.course = e.course
ORDER BY s.roll, c.course;
π Go further: normalization does not stop at BCNF. 4NF removes multivalued dependencies (a lecturer's set of courses and set of skills are independent, so storing them in one table produces a cross-product of rows). 5NF removes join dependencies. Both are rare in practice because a good E-R design avoids them. Far more common is the deliberate opposite: denormalization for read performance, which is what a data warehouse star schema is β and the discipline there is that the denormalized copy is derived and rebuildable, never the source of truth. Search "4NF multivalued dependency example" and "Kimball star schema".
π‘ Exam angle: the guaranteed question is "normalize this table to 3NF/BCNF, showing each step". Work in order: check 1NF (atomic), then find all candidate keys, then look for partial dependencies (2NF), then transitive ones (3NF), then any non-superkey determinant (BCNF). State the definitions precisely β 3NF allows X β Y when Y is prime, BCNF does not. Name the three anomalies (insertion, update, deletion) and know the lossless-join test for a binary decomposition, plus the fact that 3NF guarantees both properties while BCNF may sacrifice dependency preservation.
Syllabus points
Normalization to 1NF, 2NF, 3NF, BCNF (numerical)
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.
Related topics in Data Models, Normalization, and SQL