Normalization — 1NF, 2NF, 3NF
নরমালাইজেশন — 1NF, 2NF, 3NF
1. Why Normalize? — A Bad Table Tells the Truth
Normalization is the discipline of taking a messy, repetitive table and breaking it into smaller, cleaner tables that store every fact in exactly one place. It is not a religious ritual — it is a response to three concrete problems that bad schemas create: insertion anomalies, update anomalies, and deletion anomalies.
In this module we take a real e-commerce table — the kind you might first sketch on a napkin — and walk it through three normal forms until every anomaly is gone.
2. The Broken Table — Daraz-style Orders
Imagine an e-commerce site like Daraz. A junior engineer ships v1 of the schema with a single
orders table. Every order line lives in one row. It looks fine until customers actually
start placing orders.
orders টেবিল বানিয়ে ফেলল — যেখানে প্রতিটি অর্ডার লাইনের সব তথ্য একটি সারিতে থাকে। দেখতে ভালো লাগলেও বাস্তব ব্যবহারে এটি ভেঙে পড়ে।
-- The "everything in one place" anti-pattern
CREATE TABLE orders_v1 (
order_id INTEGER,
customer_name TEXT,
customer_phone TEXT,
customer_city TEXT,
products TEXT, -- "Phone, Charger, Cover" ← multiple values!
product_prices TEXT, -- "25000, 500, 300"
category TEXT, -- depends on product, not order
category_manager TEXT -- depends on category, not order
);
INSERT INTO orders_v1 VALUES
(1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka',
'Phone, Charger', '25000, 500', 'Electronics', 'Mr. Karim'),
(2, 'Sadia Akter', '018xxxxxxxx', 'Chittagong',
'Book', '450', 'Books', 'Mr. Rahim');
SELECT * FROM orders_v1;
| Anomaly | Concrete failure | বাংলায় |
|---|---|---|
| Insertion | You cannot add a new product (e.g., "Headphone") until at least one customer orders it. | কেউ অর্ডার না দেওয়া পর্যন্ত নতুন পণ্য যোগ করা যাচ্ছে না। |
| Update | If "Mr. Karim" gets replaced as Electronics manager, you must update every Electronics row. | একটি ম্যানেজার বদলালে শত শত সারি update করতে হবে। |
| Deletion | Deleting Sadia's order also deletes the only record that Books exists with manager Rahim. | একটি অর্ডার মুছলে Books category-র তথ্যও হারিয়ে যাচ্ছে। |
3. First Normal Form (1NF) — Atomicity
Rule: Every cell must contain a single, atomic value. No lists, no comma-separated strings, no JSON arrays squeezed into a TEXT column. Every row must be uniquely identifiable, and the order of rows and columns must not carry meaning.
-- Step 1: split multi-valued columns into separate rows.
CREATE TABLE orders_1nf (
order_id INTEGER,
customer_name TEXT,
customer_phone TEXT,
customer_city TEXT,
product TEXT,
price INTEGER,
category TEXT,
category_manager TEXT,
PRIMARY KEY (order_id, product)
);
INSERT INTO orders_1nf VALUES
(1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka', 'Phone', 25000, 'Electronics', 'Mr. Karim'),
(1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka', 'Charger', 500, 'Electronics', 'Mr. Karim'),
(2, 'Sadia Akter', '018xxxxxxxx', 'Chittagong', 'Book', 450, 'Books', 'Mr. Rahim');
SELECT * FROM orders_1nf;
split(), LIKE '%,%' or
JSON_EXTRACT just to query a column, the table is not in 1NF.
যদি একটি কলামের ভেতর থেকে split করে মান বের করতে হয়, ধরে নিন টেবিলটি 1NF-এ নেই।
4. Second Normal Form (2NF) — No Partial Dependencies
We have a composite primary key (order_id, product). Partial dependency
means: some non-key column depends on only part of that key, not the whole thing. In
orders_1nf, customer_name, customer_phone and
customer_city depend only on order_id — not on the product. Similarly,
category depends only on product.
order_id-র ওপর, আর category শুধু product-এর ওপর নির্ভর করছে — পুরো key (order_id, product) লাগছে না।
Rule: A table is in 2NF if it is in 1NF and every non-key column depends on the entire primary key. To fix it, we split the table.
-- Step 2: split into customers, products, order_items.
CREATE TABLE customers (
order_id INTEGER PRIMARY KEY,
customer_name TEXT,
customer_phone TEXT,
customer_city TEXT
);
CREATE TABLE products (
product TEXT PRIMARY KEY,
price INTEGER,
category TEXT,
category_manager TEXT
);
CREATE TABLE order_items (
order_id INTEGER,
product TEXT,
PRIMARY KEY (order_id, product),
FOREIGN KEY (order_id) REFERENCES customers(order_id),
FOREIGN KEY (product) REFERENCES products(product)
);
INSERT INTO customers VALUES
(1, 'Arif Hossain', '017xxxxxxxx', 'Dhaka'),
(2, 'Sadia Akter', '018xxxxxxxx', 'Chittagong');
INSERT INTO products VALUES
('Phone', 25000, 'Electronics', 'Mr. Karim'),
('Charger', 500, 'Electronics', 'Mr. Karim'),
('Book', 450, 'Books', 'Mr. Rahim');
INSERT INTO order_items VALUES
(1, 'Phone'), (1, 'Charger'), (2, 'Book');
SELECT c.customer_name, p.product, p.price
FROM order_items oi
JOIN customers c ON c.order_id = oi.order_id
JOIN products p ON p.product = oi.product;
Now adding a new product no longer requires an order, and updating a customer's phone number happens in exactly one place. But one anomaly remains — the manager.
5. Third Normal Form (3NF) — No Transitive Dependencies
In products, the primary key is product. We have:
product → category, and then category → category_manager. The manager
depends on the key only via the category. This indirect chain is a
transitive dependency and it must go.
product থেকে category বের হচ্ছে, আবার category থেকে category_manager বের হচ্ছে। অর্থাৎ manager-টি product-এর ওপর সরাসরি নির্ভর করছে না, বরং category-র মাধ্যমে indirect-ভাবে নির্ভর করছে — এটিই transitive dependency। 3NF-এ এই indirect chain ভাঙতে হয়।
Rule: A table is in 3NF if it is in 2NF and no non-key column transitively depends on the primary key. Codd's clean phrasing: "every non-key attribute depends on the key, the whole key, and nothing but the key — so help me Codd."
-- Step 3: extract the category → manager chain into its own table.
CREATE TABLE categories (
category TEXT PRIMARY KEY,
category_manager TEXT
);
CREATE TABLE products (
product TEXT PRIMARY KEY,
price INTEGER,
category TEXT,
FOREIGN KEY (category) REFERENCES categories(category)
);
INSERT INTO categories VALUES
('Electronics', 'Mr. Karim'),
('Books', 'Mr. Rahim');
INSERT INTO products VALUES
('Phone', 25000, 'Electronics'),
('Charger', 500, 'Electronics'),
('Book', 450, 'Books');
-- Now changing a manager touches exactly one row:
UPDATE categories SET category_manager = 'Ms. Tahmina' WHERE category = 'Electronics';
SELECT p.product, p.category, c.category_manager
FROM products p
JOIN categories c ON c.category = p.category;
6. When to Stop — 3NF Is Usually Enough
Higher normal forms (BCNF, 4NF, 5NF) exist and we cover them in the next module. But for almost every OLTP application — Daraz, Pathao, Foodpanda, a university registration system — 3NF is the practical sweet spot. It eliminates the three anomalies, keeps joins manageable, and does not over-fragment the schema.
✅ Stop normalizing when (কখন থামবেন)
- You are in 3NF and no anomalies remain.
- Further splits would force a 4-way join for every query.
- Read performance is a written, measured requirement.
- The team can reason about every join in their head.
⚠️ Keep going when (আরও কাটতে হবে)
- You see real update anomalies on a non-trivial column.
- An attribute belongs to multiple independent multi-valued facts.
- You are designing a financial or audit-critical system.
- Tests reveal hidden functional dependencies you missed.
"Key, পুরো key, এবং একমাত্র key" — প্রতিটি non-key attribute শুধু primary key-এর ওপর, পুরো primary key-এর ওপর, এবং কেবলমাত্র primary key-এর ওপর নির্ভর করবে।
7. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Functional dependency | If you know X, you know Y. Written X → Y. | X জানলে Y নিশ্চিতভাবে জানা যায়। |
| Atomic value | A single, indivisible value in a cell. | একটি cell-এ একটিমাত্র অবিভাজ্য মান। |
| Composite key | A primary key made of two or more columns. | একাধিক কলাম মিলে গঠিত primary key। |
| Partial dependency | A non-key column depending on only part of a composite key. | composite key-এর শুধু একটি অংশের ওপর নির্ভরশীলতা। |
| Transitive dependency | A → B and B → C, so A → C indirectly. | মাধ্যম ব্যবহার করে indirect নির্ভরশীলতা। |
| Anomaly | An update, insert or delete that produces inconsistent data. | data inconsistent করে ফেলা insert / update / delete। |
8. Practice Problems
Sixteen problems below. Most have a runnable SQLite answer — try them yourself first, then check.
-
Identify the 1NF violation in a column called
tagsthat stores'sale,featured,new'.যদি একটি কলামে'sale,featured,new'-এর মতো মান থাকে, সেটি 1NF লঙ্ঘন কেন?✨ Show Answer (উত্তর দেখুন)
Answer: The cell holds multiple values. To filter by tag you must use
LIKEor split — proof of non-atomicity. Fix by creating aproduct_tags(product_id, tag)child table.একই cell-এ একাধিক মান আছে। filter করতে হলে split বা LIKE লাগে — অর্থাৎ atomic নয়। সমাধান: একটি child table
product_tags(product_id, tag)। -
Convert the multi-valued
tagsfield into a 1NF table and run a query that finds all products tagged "sale".multi-valuedtags-কে 1NF টেবিলে রূপান্তর করুন এবং "sale" tag-যুক্ত পণ্য বের করুন।✨ Show Answer (উত্তর দেখুন)
ans2.sqlCREATE TABLE product_tags ( product_id INTEGER, tag TEXT, PRIMARY KEY (product_id, tag) ); INSERT INTO product_tags VALUES (1, 'sale'), (1, 'featured'), (2, 'new'), (3, 'sale'); SELECT p.name FROM products p JOIN product_tags t ON t.product_id = p.product_id WHERE t.tag = 'sale'; -
Given
enrolment(student_id, course_id, student_name, course_title)with PK(student_id, course_id), list every partial dependency.উপরের টেবিলে কোন কোন partial dependency আছে?✨ Show Answer (উত্তর দেখুন)
Answer:
student_id → student_name(depends on only part of the key) andcourse_id → course_title. Both are partial dependencies — the table is not in 2NF.student_id → student_nameএবংcourse_id → course_title— দুটিই partial dependency, তাই টেবিলটি 2NF-এ নেই। -
Decompose the
enrolmenttable above into 2NF and verify with a SQL join.উপরের টেবিলকে 2NF-এ ভাঙুন এবং join দিয়ে যাচাই করুন।✨ Show Answer (উত্তর দেখুন)
ans4.sqlCREATE TABLE students (student_id INTEGER PRIMARY KEY, student_name TEXT); CREATE TABLE courses (course_id INTEGER PRIMARY KEY, course_title TEXT); CREATE TABLE enrolment ( student_id INTEGER, course_id INTEGER, PRIMARY KEY (student_id, course_id) ); INSERT INTO students VALUES (1, 'Arif'), (2, 'Sadia'); INSERT INTO courses VALUES (101, 'DBMS'), (102, 'OS'); INSERT INTO enrolment VALUES (1, 101), (1, 102), (2, 101); SELECT s.student_name, c.course_title FROM enrolment e JOIN students s ON s.student_id = e.student_id JOIN courses c ON c.course_id = e.course_id; -
Find the transitive dependency in
employees(emp_id, dept_id, dept_name, dept_location)with PKemp_id.উপরের টেবিলে transitive dependency কোনটি?✨ Show Answer (উত্তর দেখুন)
Answer:
emp_id → dept_id → dept_nameandemp_id → dept_id → dept_location. Bothdept_nameanddept_locationdepend on the key transitively throughdept_id. Move them to adepartmentstable.dept_nameএবংdept_location— দুটিইdept_id-র মাধ্যমে indirect-ভাবে নির্ভরশীল। এদের একটি আলাদাdepartmentsটেবিলে সরাতে হবে। -
Convert the employees table to 3NF and write a query that lists every employee with their department location.employees টেবিলকে 3NF-এ আনুন এবং প্রতিটি কর্মীর department location দেখান।
✨ Show Answer (উত্তর দেখুন)
ans6.sqlCREATE TABLE departments ( dept_id INTEGER PRIMARY KEY, dept_name TEXT, dept_location TEXT ); CREATE TABLE employees ( emp_id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER REFERENCES departments(dept_id) ); INSERT INTO departments VALUES (10, 'Engineering', 'Dhaka'), (20, 'Sales', 'Sylhet'); INSERT INTO employees VALUES (1, 'Arif', 10), (2, 'Sadia', 20); SELECT e.name, d.dept_name, d.dept_location FROM employees e JOIN departments d ON d.dept_id = e.dept_id; -
A library has
book(isbn, title, author_name, author_country)with PKisbn. Is it in 3NF? Justify.উপরের library টেবিল কি 3NF-এ আছে? ব্যাখ্যা করুন।✨ Show Answer (উত্তর দেখুন)
Answer: No.
author_countrydepends onauthor_name, not onisbn. Transitive dependency. Split intoauthors(author_id, name, country)and reference it frombook.না।
author_countryনির্ভর করেauthor_name-এর ওপর,isbn-এর ওপর নয়। তাই এটি 3NF-এ নেই — author-কে আলাদা টেবিলে আনতে হবে। -
Show an insertion anomaly that exists in 1NF but disappears in 2NF (use the enrolment example).উপরের enrolment উদাহরণে এমন একটি insertion anomaly দেখান যা 1NF-এ আছে কিন্তু 2NF-এ নেই।
✨ Show Answer (উত্তর দেখুন)
Answer: In 1NF you cannot insert a new course (e.g. "Networks") until at least one student enrols, because
(student_id, course_id)is the primary key — a row needs both. In 2NF you simplyINSERT INTO courseswith no enrolments, no anomaly.1NF-এ কোনো ছাত্র enrol না করা পর্যন্ত নতুন course যোগ করা যায় না, কারণ key-এ student_id লাগবেই। 2NF-এ courses টেবিলে সরাসরি যোগ করা যায়।
-
Write SQL that demonstrates an update anomaly on the un-normalized
orders_v1table: change the manager and observe the duplication.un-normalized টেবিলে manager update করার সময় anomaly প্রদর্শন করুন।✨ Show Answer (উত্তর দেখুন)
ans9.sqlCREATE TABLE orders_v1 ( order_id INTEGER, product TEXT, category TEXT, category_manager TEXT ); INSERT INTO orders_v1 VALUES (1, 'Phone', 'Electronics', 'Mr. Karim'), (2, 'Charger', 'Electronics', 'Mr. Karim'), (3, 'TV', 'Electronics', 'Mr. Karim'); -- Forget even one row and the data is now inconsistent: UPDATE orders_v1 SET category_manager = 'Ms. Tahmina' WHERE order_id < 3; SELECT * FROM orders_v1;Notice the contradiction: order 1 and 2 say "Ms. Tahmina", order 3 still says "Mr. Karim".
-
Given
book_loan(loan_id, member_id, member_name, isbn, title, due_date)with PKloan_id, list every functional dependency.উপরের লাইব্রেরি টেবিলে কোন কোন functional dependency আছে?✨ Show Answer (উত্তর দেখুন)
Answer:
loan_id → member_id, isbn, due_date;member_id → member_name;isbn → title. The last two are transitive — table is in 2NF but not 3NF. -
Decompose the
book_loantable into 3NF.উপরেরbook_loan-কে 3NF-এ আনুন।✨ Show Answer (উত্তর দেখুন)
ans11.sqlCREATE TABLE members (member_id INTEGER PRIMARY KEY, member_name TEXT); CREATE TABLE books (isbn TEXT PRIMARY KEY, title TEXT); CREATE TABLE loans ( loan_id INTEGER PRIMARY KEY, member_id INTEGER REFERENCES members(member_id), isbn TEXT REFERENCES books(isbn), due_date TEXT ); INSERT INTO members VALUES (1, 'Arif'); INSERT INTO books VALUES ('978-1', 'C Programming'); INSERT INTO loans VALUES (100, 1, '978-1', '2026-06-01'); SELECT * FROM loans; -
Why is "phone number" sometimes a 1NF violation even though it is a single string?phone number কখন 1NF লঙ্ঘন হতে পারে যদিও সেটি একটি single string?
✨ Show Answer (উত্তর দেখুন)
Answer: If a customer can have multiple phone numbers and you store them as
"017xxx,018xxx", that single string is multi-valued — a 1NF violation. Either store one phone per customer column (limited) or, better, create acustomer_phones(customer_id, phone)child table. -
Is
invoice(invoice_id, total_amount, line_count)in 3NF ifline_countis computed from invoice lines?উপরের invoice টেবিলেline_countঅন্য টেবিল থেকে গণনা করা — এটি কি 3NF-এ আছে?✨ Show Answer (উত্তর দেখুন)
Answer: Strictly speaking, yes — there is no transitive dependency among the three columns. But it stores derivable data, which is a different code-smell (denormalization for performance). Pure normalization theory has no quarrel here; in module 28 we discuss when you might keep such a column on purpose.
-
Insert two rows that violate 3NF on the
productstable from §4 and explain.§4-এরproductsটেবিলে এমন দুটি সারি দিন যা 3NF লঙ্ঘন করবে।✨ Show Answer (উত্তর দেখুন)
Answer: Insert
('Laptop', 60000, 'Electronics', 'Mr. Karim')and('Tablet', 18000, 'Electronics', 'Ms. Different'). Now two products in the same Electronics category disagree on the manager — a direct contradiction caused by the transitive dependencycategory → category_managernot being isolated. -
Take the bKash transaction sheet
tx(tx_id, sender_phone, sender_name, receiver_phone, receiver_name, amount)and bring it to 3NF.উপরের bKash সারণিকে 3NF-এ আনুন।✨ Show Answer (উত্তর দেখুন)
ans15.sqlCREATE TABLE users ( phone TEXT PRIMARY KEY, name TEXT ); CREATE TABLE tx ( tx_id INTEGER PRIMARY KEY, sender_phone TEXT REFERENCES users(phone), receiver_phone TEXT REFERENCES users(phone), amount INTEGER ); INSERT INTO users VALUES ('017', 'Arif'), ('018', 'Sadia'); INSERT INTO tx VALUES (1, '017', '018', 1500); SELECT t.tx_id, s.name AS sender, r.name AS receiver, t.amount FROM tx t JOIN users s ON s.phone = t.sender_phone JOIN users r ON r.phone = t.receiver_phone; -
When is keeping a "computed" or "duplicated" column actually correct? Give one good and one bad reason.কখন duplicate column রাখা ঠিক হতে পারে — একটি ভালো এবং একটি খারাপ কারণ দিন।
✨ Show Answer (উত্তর দেখুন)
Good reason: a frozen historical value — e.g.
order_lines.unit_price_at_purchase. The product price will change tomorrow but the invoice must keep the price at the moment of sale. Storing it is correct, not denormalization.Bad reason: "It is faster to read." Without measuring, this is premature optimization that introduces every anomaly we just spent an hour eliminating.
Summary — Module 26
We took a single bloated orders table through three normal forms.
1NF made every cell atomic. 2NF removed partial dependencies on a
composite key. 3NF killed transitive dependencies. The result: every fact lives in
exactly one place, and the three classic anomalies — insertion, update, deletion — disappear. For
most production OLTP systems, 3NF is exactly where you should stop.
orders টেবিলকে আমরা তিনটি normal form-এর মধ্য দিয়ে নিয়ে গেছি। 1NF প্রতিটি cell-কে atomic করেছে, 2NF partial dependency দূর করেছে, এবং 3NF transitive dependency বাদ দিয়েছে। ফলাফল — প্রতিটি তথ্য ঠিক একটি জায়গায় থাকে এবং insert/update/delete-এর তিনটি anomaly আর থাকে না। অধিকাংশ production OLTP system-এর জন্য 3NF-ই সঠিক বিন্দু।