When data is not properly normalized, redundant data (duplicate or repetitive information) can lead to serious data anomalies. These are inconsistencies or errors during basic operations like insert, update, or delete.
🚨 Types of Anomalies Caused by Redundant Data
📝 Update Anomaly
Occurs when updating redundant data in one place but not everywhere it appears.
Example:
A student's course instructor name is stored in every row. If the instructor changes, you must update it in every row.
If you miss some, data becomes inconsistent.
StudentID | Name | Course | Instructor |
|---|---|---|---|
101 | Alice | Math | Dr. Sharma |
102 | Bob | Math | Dr. Sharma |
If Dr. Sharma changes to Dr. Mehta, and you forget to update in one row — Update Anomaly.
🚫 Insert Anomaly
Occurs when you cannot insert data into the table without other unrelated data.
Example:
You want to add a new course, but you don’t yet have a student enrolled in it.
StudentID | Name | Course | Instructor |
|---|---|---|---|
(null) | (null) | Statistics | Dr. Rao |
Inserting nulls just to record a course? That's an Insert Anomaly.
❌ Delete Anomaly
Occurs when deleting one piece of data also deletes important related data unintentionally.
Example:
If a student is the only one enrolled in a course and you delete their record, the course and instructor info is lost too.
StudentID | Name | Course | Instructor |
|---|---|---|---|
105 | Zara | Music | Dr. Bose |
If Zara drops out and her row is deleted, info about Music course and Dr. Bose is gone — Delete Anomaly.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anomalies Due to Redundant Data</title>
<style>
.infographic-anomalies {
--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);
padding: 2.5rem 1.5rem 2rem;
max-width: 980px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-anomalies[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-anomalies * { box-sizing: border-box; }
.an-head { text-align: center; margin-bottom: 0.25rem; }
.an-head h2 {
font-family: var(--display-font);
font-size: 1.9rem;
margin: 0 0 0.5rem;
}
.an-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 620px;
line-height: 1.55;
}
.an-tabs {
display: flex;
justify-content: center;
gap: 0.5rem;
margin: 1.75rem 0 0;
flex-wrap: wrap;
}
.an-tab {
font: inherit;
font-size: 0.88rem;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.5rem 1.1rem;
cursor: pointer;
color: var(--ink-soft);
}
.an-tab:hover { border-color: var(--accent); }
.an-tab.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.an-panel { display: none; margin-top: 1.5rem; }
.an-panel.is-active { display: block; animation: an-fade 0.2s ease; }
@keyframes an-fade { from { opacity: 0; } to { opacity: 1; } }
.an-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
}
.an-frame-desc { font-size: 0.88rem; color: var(--ink-soft); line-height: 1.55; margin: 0 0 1rem; }
.an-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.an-table {
width: 100%;
min-width: 460px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.an-table th, table.an-table td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.an-table th { background: var(--accent); color: #fff; font-weight: 700; font-size: 0.75rem; }
table.an-table td.is-null { color: var(--ink-soft); font-style: italic; }
table.an-table td.an-flag { background: var(--bad-soft); color: var(--bad); font-weight: 600; }
table.an-table tr.is-removing { opacity: 0.35; text-decoration: line-through; }
table.an-table tr.is-new td { animation: an-row-in 0.5s ease; }
@keyframes an-row-in { from { background: var(--good-soft); } to { background: transparent; } }
.an-controls { display: flex; gap: 0.6rem; margin-top: 1rem; flex-wrap: wrap; align-items: center; }
.an-btn {
font: inherit;
font-size: 0.85rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 7px;
padding: 0.55rem 1rem;
cursor: pointer;
}
.an-btn:hover { opacity: 0.9; }
.an-btn.is-secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.an-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.an-mini-form { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
.an-mini-form input {
flex: 1 1 130px;
min-width: 0;
font: inherit;
font-size: 0.85rem;
padding: 0.5rem 0.6rem;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--paper);
color: var(--ink);
}
.an-msg {
margin-top: 1rem;
padding: 0.7rem 0.9rem;
border-radius: 8px;
font-size: 0.85rem;
line-height: 1.55;
display: none;
}
.an-msg.is-visible { display: block; }
.an-msg.msg-bad { background: var(--bad-soft); color: var(--bad); }
.an-msg.msg-good { background: var(--good-soft); color: var(--good); }
.an-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 1.75rem; }
.an-chip {
font-size: 0.75rem;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.4rem 0.85rem;
color: var(--ink-soft);
transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.an-chip.is-done { background: var(--bad-soft); color: var(--bad); border-color: var(--bad); }
.an-solution {
margin-top: 1.75rem;
background: var(--good-soft);
color: var(--good);
border-radius: 8px;
padding: 0.85rem 1rem;
font-size: 0.88rem;
line-height: 1.5;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.infographic-anomalies *, .infographic-anomalies *::before, .infographic-anomalies *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-anomalies" id="anomalies">
<div class="an-head">
<h2>Anomalies Due to Redundant Data</h2>
<p>One flat, unnormalized table causes three kinds of trouble. Trigger each one yourself and watch it happen.</p>
</div>
<div class="an-tabs">
<button class="an-tab" data-tab="update" type="button">📝 Update Anomaly</button>
<button class="an-tab" data-tab="insert" type="button">🚫 Insert Anomaly</button>
<button class="an-tab" data-tab="delete" type="button">❌ Delete Anomaly</button>
</div>
<!-- UPDATE -->
<div class="an-panel" data-panel="update">
<div class="an-frame">
<p class="an-frame-desc">Dr. Sharma teaches Math, and his name is repeated in every row for that course. Try updating just one row — a very easy mistake to make.</p>
<div class="an-table-scroll">
<table class="an-table" id="anUpdateTable"></table>
</div>
<div class="an-controls">
<button class="an-btn" id="anUpdateBtn" type="button">Update row 101 → "Dr. Mehta" only</button>
<button class="an-btn is-secondary" id="anUpdateReset" type="button">Reset</button>
</div>
<div class="an-msg" id="anUpdateMsg"></div>
</div>
</div>
<!-- INSERT -->
<div class="an-panel" data-panel="insert">
<div class="an-frame">
<p class="an-frame-desc">You want to record a new course before anyone has enrolled. But this table only knows about courses through student rows.</p>
<div class="an-table-scroll">
<table class="an-table" id="anInsertTable"></table>
</div>
<div class="an-mini-form">
<input id="anInsertCourse" type="text" placeholder="New course, e.g. Statistics">
<input id="anInsertInstructor" type="text" placeholder="Instructor, e.g. Dr. Rao">
</div>
<div class="an-controls">
<button class="an-btn" id="anInsertBtn" type="button">Add course (no student yet)</button>
<button class="an-btn is-secondary" id="anInsertReset" type="button">Reset</button>
</div>
<div class="an-msg" id="anInsertMsg"></div>
</div>
</div>
<!-- DELETE -->
<div class="an-panel" data-panel="delete">
<div class="an-frame">
<p class="an-frame-desc">Zara is the only student enrolled in Music. Her row is the only place "Music" and "Dr. Bose" are recorded.</p>
<div class="an-table-scroll">
<table class="an-table" id="anDeleteTable"></table>
</div>
<div class="an-controls">
<button class="an-btn" id="anDeleteBtn" type="button">Delete Zara's row</button>
<button class="an-btn is-secondary" id="anDeleteReset" type="button">Reset</button>
</div>
<div class="an-msg" id="anDeleteMsg"></div>
</div>
</div>
<div class="an-chips" id="anChips">
<span class="an-chip" data-chip="update">Update Anomaly triggered</span>
<span class="an-chip" data-chip="insert">Insert Anomaly triggered</span>
<span class="an-chip" data-chip="delete">Delete Anomaly triggered</span>
</div>
<div class="an-solution">✅ Solution: Normalization — splitting this into separate Student, Course, and Instructor tables removes the duplication that causes all three anomalies.</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('anomalies');
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 tabs = document.querySelectorAll('.an-tab');
const panels = document.querySelectorAll('.an-panel');
function showTab(n) {
tabs.forEach(t => t.classList.toggle('is-active', t.dataset.tab === n));
panels.forEach(p => p.classList.toggle('is-active', p.dataset.panel === n));
}
tabs.forEach(t => t.addEventListener('click', () => showTab(t.dataset.tab)));
showTab('update');
function markChip(name) {
const chip = document.querySelector('.an-chip[data-chip="' + name + '"]');
if (chip) chip.classList.add('is-done');
}
function showMsg(el, text, type) {
el.textContent = text;
el.className = 'an-msg is-visible msg-' + type;
}
// ---------- UPDATE ANOMALY ----------
(function () {
let rows = [
{ id: '101', name: 'Alice', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '102', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '103', name: 'Chloe', course: 'Physics', instructor: 'Dr. Lee' }
];
const table = document.getElementById('anUpdateTable');
const msg = document.getElementById('anUpdateMsg');
const btn = document.getElementById('anUpdateBtn');
function render(flagMismatch) {
let html = '<thead><tr><th>StudentID</th><th>Name</th><th>Course</th><th>Instructor</th></tr></thead><tbody>';
rows.forEach(r => {
const mismatch = flagMismatch && r.course === 'Math';
html += '<tr><td>' + r.id + '</td><td>' + r.name + '</td><td>' + r.course + '</td>' +
(mismatch ? '<td class="an-flag">' + r.instructor + '</td>' : '<td>' + r.instructor + '</td>') + '</tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
btn.addEventListener('click', () => {
rows[0].instructor = 'Dr. Mehta';
render(true);
showMsg(msg, '❌ Update Anomaly! Row 101 now says "Dr. Mehta" but row 102 still says "Dr. Sharma" — same course, two different instructors. One update, missed everywhere else it was duplicated.', 'bad');
markChip('update');
btn.disabled = true;
});
document.getElementById('anUpdateReset').addEventListener('click', () => {
rows = [
{ id: '101', name: 'Alice', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '102', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '103', name: 'Chloe', course: 'Physics', instructor: 'Dr. Lee' }
];
render(false);
msg.className = 'an-msg';
btn.disabled = false;
});
render(false);
})();
// ---------- INSERT ANOMALY ----------
(function () {
let rows = [
{ id: '101', name: 'Alice', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '102', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' }
];
const table = document.getElementById('anInsertTable');
const msg = document.getElementById('anInsertMsg');
let added = false;
function render() {
let html = '<thead><tr><th>StudentID</th><th>Name</th><th>Course</th><th>Instructor</th></tr></thead><tbody>';
rows.forEach((r, i) => {
const isNullRow = r.id === '';
html += '<tr' + (isNullRow ? ' class="is-new"' : '') + '>' +
(isNullRow ? '<td class="is-null">NULL</td><td class="is-null">NULL</td>' : '<td>' + r.id + '</td><td>' + r.name + '</td>') +
'<td>' + r.course + '</td><td>' + r.instructor + '</td></tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
document.getElementById('anInsertBtn').addEventListener('click', () => {
const course = document.getElementById('anInsertCourse').value.trim() || 'Statistics';
const instructor = document.getElementById('anInsertInstructor').value.trim() || 'Dr. Rao';
if (added) return;
rows.push({ id: '', name: '', course, instructor });
added = true;
render();
showMsg(msg, '🚫 Insert Anomaly! To record "' + course + '" at all, StudentID and Name had to be inserted as NULL — this table can\u2019t describe a course without also describing a student.', 'bad');
markChip('insert');
});
document.getElementById('anInsertReset').addEventListener('click', () => {
rows = [
{ id: '101', name: 'Alice', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '102', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' }
];
added = false;
render();
msg.className = 'an-msg';
document.getElementById('anInsertCourse').value = '';
document.getElementById('anInsertInstructor').value = '';
});
render();
})();
// ---------- DELETE ANOMALY ----------
(function () {
let rows = [
{ id: '104', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '105', name: 'Zara', course: 'Music', instructor: 'Dr. Bose' }
];
const table = document.getElementById('anDeleteTable');
const msg = document.getElementById('anDeleteMsg');
const btn = document.getElementById('anDeleteBtn');
let deleted = false;
function render() {
let html = '<thead><tr><th>StudentID</th><th>Name</th><th>Course</th><th>Instructor</th></tr></thead><tbody>';
rows.forEach(r => {
html += '<tr' + (deleted && r.id === '105' ? ' class="is-removing"' : '') + '><td>' + r.id + '</td><td>' + r.name + '</td><td>' + r.course + '</td><td>' + r.instructor + '</td></tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
btn.addEventListener('click', () => {
if (deleted) return;
deleted = true;
render();
setTimeout(() => {
rows = rows.filter(r => r.id !== '105');
render();
showMsg(msg, '❌ Delete Anomaly! Zara\u2019s row is gone — and with it, every trace that "Music" or "Dr. Bose" ever existed. That information lived nowhere else.', 'bad');
markChip('delete');
}, 500);
btn.disabled = true;
});
document.getElementById('anDeleteReset').addEventListener('click', () => {
rows = [
{ id: '104', name: 'Bob', course: 'Math', instructor: 'Dr. Sharma' },
{ id: '105', name: 'Zara', course: 'Music', instructor: 'Dr. Bose' }
];
deleted = false;
render();
msg.className = 'an-msg';
btn.disabled = false;
});
render();
})();
})();
</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!');
});🔁 Why Does Redundancy Cause These?
Redundancy means storing the same data repeatedly, often in the same table. This:
Increases dependency of unrelated data on each other
Makes data inconsistent across rows
Creates integrity problems
✅ Solution: Normalization
Breaking large redundant tables into smaller related ones removes duplication, and avoids these anomalies.
🧾 Summary Table
🧨 Anomaly | ❓ What Happens | 😱 Due to... |
|---|---|---|
Update | Change made in one place, not everywhere | Repeated data in many rows |
Insert | Can’t add data without other data | Unnecessary dependency |
Delete | Deleting data removes valuable info | Data stored together unnecessarily |