π What is 1NF (First Normal Form)?
A table is in First Normal Form (1NF) if:
All attributes (columns) contain only atomic (indivisible) values.
There are no repeating groups or arrays in a row.
Each column contains only one type of data.
π Why is 1NF Important?
It removes multi-valued and grouped data.
Prepares the data for further normalization (2NF, 3NF, etc.).
Prevents basic data redundancy and inconsistency.
π§Ύ Example: Table Not in 1NF
Suppose we have a table like this:
Student_ID | Name | Courses |
|---|---|---|
101 | Swati | DBMS, OS |
102 | Ravi | AI |
103 | Neha | OS, AI, DBMS |
β Problem:
The
Coursescolumn contains multiple values, which violates 1NF.It is not atomic.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First Normal Form (1NF)</title>
<style>
.infographic-onenf {
--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;
--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-onenf[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;
}
.infographic-onenf * { box-sizing: border-box; }
.nf1-head { text-align: center; margin-bottom: 0.25rem; }
.nf1-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.nf1-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 580px;
line-height: 1.55;
}
.nf1-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
margin-top: 1.75rem;
}
.nf1-frame-title { font-family: var(--display-font); font-size: 1.05rem; margin: 0 0 0.4rem; }
.nf1-frame-desc { font-size: 0.87rem; color: var(--ink-soft); line-height: 1.5; margin: 0 0 1rem; }
.nf1-progress {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 1rem;
}
.nf1-progress-bar {
flex: 1;
height: 8px;
border-radius: 999px;
background: var(--paper);
border: 1px solid var(--line);
overflow: hidden;
}
.nf1-progress-fill {
height: 100%;
width: 0%;
background: var(--accent);
transition: width 0.4s ease;
}
.nf1-progress-label { font-size: 0.78rem; color: var(--ink-soft); white-space: nowrap; }
.nf1-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.nf1-table {
width: 100%;
min-width: 400px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.nf1-table th, table.nf1-table td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.nf1-table th { background: var(--accent); color: #fff; font-weight: 700; font-size: 0.75rem; }
table.nf1-table td.nf1-multi { color: var(--bad); font-weight: 600; }
table.nf1-table tr.is-resolved td { opacity: 0.4; text-decoration: line-through; }
.nf1-split-btn {
font: inherit;
font-size: 0.76rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 6px;
padding: 0.3rem 0.7rem;
cursor: pointer;
}
.nf1-split-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.nf1-arrow { text-align: center; font-size: 1.3rem; color: var(--ink-soft); margin: 1rem 0; }
.nf1-result-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.nf1-result {
width: 100%;
min-width: 340px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.nf1-result th, table.nf1-result td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.nf1-result th { background: var(--good); color: #fff; font-weight: 700; font-size: 0.75rem; }
table.nf1-result tr.is-new td { animation: nf1-row-in 0.5s ease; }
@keyframes nf1-row-in { from { background: var(--good-soft); } to { background: transparent; } }
.nf1-empty-note { font-size: 0.8rem; color: var(--ink-soft); font-style: italic; padding: 0.5rem 0.2rem; }
.nf1-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: 1rem;
}
.nf1-reset-btn:hover { border-color: var(--accent); }
.nf1-success {
margin-top: 1rem;
background: var(--good-soft);
color: var(--good);
border-radius: 8px;
padding: 0.7rem 0.9rem;
font-size: 0.85rem;
display: none;
}
.nf1-success.is-visible { display: block; }
@media (prefers-reduced-motion: reduce) {
.infographic-onenf *, .infographic-onenf *::before, .infographic-onenf *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-onenf" id="onenf">
<div class="nf1-head">
<p>A table is in 1NF when every value is atomic β one value per cell, no lists hiding inside a column.</p>
</div>
<!-- Fix the table exercise -->
<div class="nf1-frame">
<p class="nf1-frame-title">Fix this table, one row at a time</p>
<p class="nf1-frame-desc">The <code>Courses</code> column below holds multiple values per cell. Click "Split" on each row to break it into atomic rows.</p>
<div class="nf1-progress">
<div class="nf1-progress-bar"><div class="nf1-progress-fill" id="nf1ProgressFill"></div></div>
<span class="nf1-progress-label" id="nf1ProgressLabel">0 of 3 rows fixed</span>
</div>
<div class="nf1-table-scroll">
<table class="nf1-table" id="nf1SourceTable"></table>
</div>
<div class="nf1-arrow">β</div>
<div class="nf1-result-scroll">
<table class="nf1-result" id="nf1ResultTable"></table>
</div>
<div class="nf1-success" id="nf1Success">β
Every value is now atomic, and there are no repeating groups β this table is in 1NF!</div>
<button class="nf1-reset-btn" id="nf1Reset" type="button">Reset exercise</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('onenf');
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 sourceRows = [
{ id: '101', name: 'Swati', courses: ['DBMS', 'OS'] },
{ id: '102', name: 'Ravi', courses: ['AI'] },
{ id: '103', name: 'Neha', courses: ['OS', 'AI', 'DBMS'] }
];
let resolved = {};
let resultRows = [];
const sourceTable = document.getElementById('nf1SourceTable');
const resultTable = document.getElementById('nf1ResultTable');
const progressFill = document.getElementById('nf1ProgressFill');
const progressLabel = document.getElementById('nf1ProgressLabel');
const success = document.getElementById('nf1Success');
function renderSource() {
let html = '<thead><tr><th>Student_ID</th><th>Name</th><th>Courses</th><th></th></tr></thead><tbody>';
sourceRows.forEach(r => {
const done = resolved[r.id];
html += '<tr' + (done ? ' class="is-resolved"' : '') + '>' +
'<td>' + r.id + '</td><td>' + r.name + '</td>' +
'<td class="nf1-multi">' + r.courses.join(', ') + '</td>' +
'<td><button class="nf1-split-btn" data-split="' + r.id + '"' + (done ? ' disabled' : '') + '>' + (done ? 'Split β' : 'Split β') + '</button></td></tr>';
});
html += '</tbody>';
sourceTable.innerHTML = html;
sourceTable.querySelectorAll('[data-split]').forEach(btn => {
btn.addEventListener('click', () => splitRow(btn.dataset.split));
});
}
function renderResult() {
if (resultRows.length === 0) {
resultTable.innerHTML = '<tbody><tr><td class="nf1-empty-note">No rows yet β click Split above to begin.</td></tr></tbody>';
return;
}
let html = '<thead><tr><th>Student_ID</th><th>Name</th><th>Course</th></tr></thead><tbody>';
resultRows.forEach(r => {
html += '<tr' + (r.isNew ? ' class="is-new"' : '') + '><td>' + r.id + '</td><td>' + r.name + '</td><td>' + r.course + '</td></tr>';
});
html += '</tbody>';
resultTable.innerHTML = html;
}
function splitRow(id) {
if (resolved[id]) return;
resolved[id] = true;
const row = sourceRows.find(r => r.id === id);
resultRows.forEach(r => r.isNew = false);
row.courses.forEach(c => resultRows.push({ id: row.id, name: row.name, course: c, isNew: true }));
renderSource();
renderResult();
updateProgress();
}
function updateProgress() {
const done = Object.keys(resolved).length;
const total = sourceRows.length;
progressFill.style.width = (done / total * 100) + '%';
progressLabel.textContent = done + ' of ' + total + ' rows fixed';
success.classList.toggle('is-visible', done === total);
}
document.getElementById('nf1Reset').addEventListener('click', () => {
resolved = {};
resultRows = [];
renderSource();
renderResult();
updateProgress();
});
renderSource();
renderResult();
updateProgress();
})();
</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!');
});π§ Key Points of 1NF:
Feature | Required in 1NF |
|---|---|
Atomic values only | β Yes |
No multivalued columns | β Yes |
Same type per column | β Yes |
Unique column names | β Yes |
Unordered rows | β Yes |
π― Real-Life Analogy:
Imagine a form that asks:
"List your hobbies"
And someone writes:
Reading, Singing, Painting
Thatβs not in 1NF. To make it 1NF, you'd enter one hobby per line.