📘 What is 2NF (Second Normal Form)?
A table is in 2NF if:
It is already in 1NF
Every non-prime attribute is fully functionally dependent on the entire primary key
🧠 What is a Partial Dependency?
A partial dependency occurs when a non-key attribute depends on part of a composite primary key, not the whole.
🧾 Example (Table in 1NF, but not in 2NF):
Let’s say we have this table:
🎓 Student_Course Table:
Student_ID | Course | Student_Name | Department |
|---|---|---|---|
101 | DBMS | Swati | CS |
101 | OS | Swati | CS |
102 | AI | Ravi | IT |
✅ Primary Key = (Student_ID, Course)
❌ Student_Name and Department only depend on Student_ID, not the full key (Student_ID, Course)
➡️ This is a partial dependency — hence not in 2NF.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Second Normal Form (2NF)</title>
<style>
.infographic-twonf {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--bad: #dc2626;
--bad-soft: #fee2e2;
--warn: #b45309;
--warn-soft: #fef3c7;
--display-font: Georgia, 'Iowan Old Style', serif;
--body-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono-font: 'SF Mono', Consolas, monospace;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 900px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-twonf[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4ade80;
--good-soft: #123420;
--bad: #f87171;
--bad-soft: #3a1414;
--warn: #fbbf24;
--warn-soft: #3a2f0e;
}
.infographic-twonf * { box-sizing: border-box; }
.nf2-head { text-align: center; margin-bottom: 0.25rem; }
.nf2-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.nf2-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 600px;
line-height: 1.55;
}
.nf2-head code { background: var(--paper-alt); border-radius: 4px; padding: 0.05rem 0.35rem; }
.nf2-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
margin-top: 1.75rem;
}
.nf2-frame-title { font-family: var(--display-font); font-size: 1.05rem; margin: 0 0 0.4rem; }
.nf2-frame-desc { font-size: 0.87rem; color: var(--ink-soft); line-height: 1.5; margin: 0 0 1rem; }
.nf2-key-note {
font-family: var(--mono-font);
font-size: 0.8rem;
background: var(--accent-soft);
color: var(--accent);
border-radius: 6px;
padding: 0.5rem 0.7rem;
margin-bottom: 1rem;
display: inline-block;
}
.nf2-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.nf2-table {
width: 100%;
min-width: 420px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.nf2-table th, table.nf2-table td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.nf2-table th {
background: var(--accent);
color: #fff;
font-weight: 700;
font-size: 0.75rem;
cursor: pointer;
transition: background 0.2s;
}
table.nf2-table th.is-key { background: var(--ink-soft); cursor: default; }
table.nf2-table th.is-flagged { background: var(--warn); }
table.nf2-table th .nf2-sub { display: block; font-weight: 400; font-size: 0.65rem; opacity: 0.85; margin-top: 0.15rem; }
table.nf2-table td.is-flagged-col { background: var(--warn-soft); color: var(--warn); font-weight: 600; }
.nf2-feedback {
margin-top: 0.9rem;
padding: 0.65rem 0.85rem;
border-radius: 8px;
font-size: 0.85rem;
line-height: 1.5;
display: none;
}
.nf2-feedback.is-visible { display: block; }
.nf2-feedback.fb-good { background: var(--good-soft); color: var(--good); }
.nf2-feedback.fb-bad { background: var(--bad-soft); color: var(--bad); }
.nf2-decompose-row { margin-top: 1rem; display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.nf2-decompose-btn {
font: inherit;
font-size: 0.88rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 7px;
padding: 0.6rem 1.2rem;
cursor: pointer;
}
.nf2-decompose-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.nf2-decompose-hint { font-size: 0.78rem; color: var(--ink-soft); }
.nf2-result {
display: none;
margin-top: 1.5rem;
}
.nf2-result.is-visible { display: block; animation: nf2-fade 0.3s ease; }
@keyframes nf2-fade { from { opacity: 0; } to { opacity: 1; } }
.nf2-arrow { text-align: center; font-size: 1.3rem; color: var(--ink-soft); margin-bottom: 1rem; }
.nf2-tables-row { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.nf2-table-block { flex: 1 1 220px; min-width: 0; }
.nf2-table-name { font-family: var(--display-font); font-size: 0.92rem; margin: 0 0 0.5rem; }
table.nf2-result-table {
width: 100%;
min-width: 200px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.85rem;
}
table.nf2-result-table th, table.nf2-result-table td {
border: 1px solid var(--line);
padding: 0.45rem 0.6rem;
text-align: left;
white-space: nowrap;
}
table.nf2-result-table th { background: var(--good); color: #fff; font-weight: 700; font-size: 0.7rem; }
.nf2-reset-btn {
font: inherit;
font-size: 0.82rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 7px;
padding: 0.45rem 0.9rem;
cursor: pointer;
color: var(--ink);
margin-top: 1.25rem;
}
.nf2-reset-btn:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-twonf *, .infographic-twonf *::before, .infographic-twonf *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-twonf" id="twonf">
<div class="nf2-head">
<p>A table is in 2NF when it's already in 1NF, and every non-key attribute depends on the <b>whole</b> primary key — not just part of it.</p>
</div>
<div class="nf2-frame">
<p class="nf2-frame-title">Spot the partial dependency</p>
<p class="nf2-frame-desc">This table's primary key is composite. Click the column(s) you think depend on only <i>part</i> of that key, not the whole thing.</p>
<div class="nf2-key-note">Primary Key = (Student_ID, Course)</div>
<div class="nf2-table-scroll">
<table class="nf2-table" id="nf2Table">
<thead>
<tr>
<th class="is-key" data-col="studentId">Student_ID<span class="nf2-sub">key</span></th>
<th class="is-key" data-col="course">Course<span class="nf2-sub">key</span></th>
<th data-col="name">Student_Name</th>
<th data-col="dept">Department</th>
</tr>
</thead>
<tbody>
<tr><td>101</td><td>DBMS</td><td>Swati</td><td>CS</td></tr>
<tr><td>101</td><td>OS</td><td>Swati</td><td>CS</td></tr>
<tr><td>102</td><td>AI</td><td>Ravi</td><td>IT</td></tr>
</tbody>
</table>
</div>
<div class="nf2-feedback" id="nf2Feedback"></div>
<div class="nf2-decompose-row">
<button class="nf2-decompose-btn" id="nf2DecomposeBtn" disabled type="button">Decompose into 2NF</button>
<span class="nf2-decompose-hint" id="nf2Hint">Find both partially-dependent columns to unlock this.</span>
</div>
<div class="nf2-result" id="nf2Result">
<div class="nf2-arrow">↓</div>
<div class="nf2-tables-row">
<div class="nf2-table-block">
<p class="nf2-table-name">Student</p>
<table class="nf2-result-table">
<thead><tr><th>Student_ID</th><th>Student_Name</th><th>Department</th></tr></thead>
<tbody>
<tr><td>101</td><td>Swati</td><td>CS</td></tr>
<tr><td>102</td><td>Ravi</td><td>IT</td></tr>
</tbody>
</table>
</div>
<div class="nf2-table-block">
<p class="nf2-table-name">Enrollment</p>
<table class="nf2-result-table">
<thead><tr><th>Student_ID</th><th>Course</th></tr></thead>
<tbody>
<tr><td>101</td><td>DBMS</td></tr>
<tr><td>101</td><td>OS</td></tr>
<tr><td>102</td><td>AI</td></tr>
</tbody>
</table>
</div>
</div>
<div class="nf2-feedback fb-good is-visible" style="margin-top:1rem;">✅ Student_Name and Department now live with just Student_ID — every non-key attribute depends on the whole key in both tables. This is 2NF.</div>
</div>
<button class="nf2-reset-btn" id="nf2Reset" type="button">Reset exercise</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('twonf');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const headers = document.querySelectorAll('#nf2Table th[data-col]');
const table = document.getElementById('nf2Table');
const feedback = document.getElementById('nf2Feedback');
const decomposeBtn = document.getElementById('nf2DecomposeBtn');
const hint = document.getElementById('nf2Hint');
const result = document.getElementById('nf2Result');
const correctCols = ['name', 'dept'];
let flagged = new Set();
const colIndex = { studentId: 0, course: 1, name: 2, dept: 3 };
function showFeedback(text, good) {
feedback.textContent = text;
feedback.className = 'nf2-feedback is-visible ' + (good ? 'fb-good' : 'fb-bad');
}
function updateColHighlight(col, on) {
const th = table.querySelector('th[data-col="' + col + '"]');
th.classList.toggle('is-flagged', on);
table.querySelectorAll('tbody tr').forEach(tr => {
tr.children[colIndex[col]].classList.toggle('is-flagged-col', on);
});
}
headers.forEach(th => {
th.addEventListener('click', () => {
const col = th.dataset.col;
if (col === 'studentId' || col === 'course') {
showFeedback('❌ Student_ID and Course together make up the primary key itself — they\u2019re not dependent attributes.', false);
return;
}
if (flagged.has(col)) {
flagged.delete(col);
updateColHighlight(col, false);
} else {
flagged.add(col);
updateColHighlight(col, true);
showFeedback('✔️ Correct — "' + th.textContent.replace('key', '').trim() + '" only depends on Student_ID, not the full (Student_ID, Course) key. That\u2019s a partial dependency.', true);
}
decomposeBtn.disabled = !(correctCols.every(c => flagged.has(c)));
hint.textContent = decomposeBtn.disabled
? (flagged.size + ' of 2 found — keep looking.')
: 'Both found! Ready to decompose.';
});
});
decomposeBtn.addEventListener('click', () => {
result.classList.add('is-visible');
result.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
document.getElementById('nf2Reset').addEventListener('click', () => {
flagged = new Set();
['name', 'dept'].forEach(c => updateColHighlight(c, false));
feedback.className = 'nf2-feedback';
decomposeBtn.disabled = true;
hint.textContent = 'Find both partially-dependent columns to unlock this.';
result.classList.remove('is-visible');
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});🔑 2NF Only Matters When:
The table has a composite primary key
There are partial dependencies
🧠 Key Concepts Recap:
Concept | In 2NF? |
|---|---|
Atomic values | ✅ Yes (inherited from 1NF) |
No repeating groups | ✅ Yes |
No partial dependency | ✅ Yes |
Full dependency on PK | ✅ Yes |
⚠️ Why Use 2NF?
Eliminates partial dependency
Reduces data duplication
Prevents update and insertion anomalies