Both are used to maintain data consistency when deleting rows from a parent table (with a primary key) that are referenced in a child table (with a foreign key).
🔗 Problem: Referential Integrity Violation
Imagine you have:
📦 students table → parent (with student_id as PRIMARY KEY)
📘 enrollments table → child (with student_id as FOREIGN KEY)
❌ If you delete a student who is still referenced in enrollments, it causes a referential integrity error — unless you handle it using ON DELETE actions.
✅ Solution 1: ON DELETE CASCADE
Automatically deletes the matching child rows when a parent row is deleted.
📌 Example:
CREATE TABLE enrollments (
student_id INT,
course_id INT,
FOREIGN KEY (student_id) REFERENCES students(student_id)
ON DELETE CASCADE
);🗑️ If a student is deleted from students, all their enrollments are automatically deleted too.
✔️ Ensures no orphan rows
✔️ Maintains clean data automatically
✅ Solution 2: ON DELETE SET NULL
Sets the foreign key to NULL in the child table when the parent row is deleted.
📌 Example:
CREATE TABLE enrollments (
student_id INT,
course_id INT,
FOREIGN KEY (student_id) REFERENCES students(student_id)
ON DELETE SET NULL
);🗑️ If a student is deleted from students, their student_id in enrollments becomes NULL.
✔️ Keeps the row in child table
✔️ Use this only when student_id allows NULL
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ON DELETE CASCADE vs SET NULL — Try It Yourself</title>
<style>
.infographic-cascadesetnull {
--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;
--on-accent: #ffffff;
--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: 960px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-cascadesetnull[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;
--on-accent: #101418;
}
.infographic-cascadesetnull * { box-sizing: border-box; }
.cn-head { text-align: center; margin-bottom: 0.25rem; }
.cn-head h2 {
font-family: var(--display-font);
font-size: 1.85rem;
margin: 0 0 0.5rem;
}
.cn-head p {
color: var(--ink-soft);
font-size: 0.98rem;
margin: 0 auto;
max-width: 620px;
line-height: 1.55;
}
.cn-head code { background: var(--paper-alt); border-radius: 4px; padding: 0.05rem 0.35rem; }
.cn-trigger-row { display: flex; justify-content: center; margin: 1.5rem 0; }
.cn-trigger-btn {
font: inherit;
font-size: 0.92rem;
background: var(--bad);
color: #fff;
border: none;
border-radius: 8px;
padding: 0.7rem 1.4rem;
cursor: pointer;
}
.cn-trigger-btn:hover { opacity: 0.9; }
.cn-trigger-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.cn-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
@media (max-width: 700px) { .cn-panels { grid-template-columns: 1fr; } }
.cn-panel { background: var(--paper-alt); border: 1px solid var(--line); border-radius: 10px; padding: 1.15rem; min-width: 0; }
.cn-panel-title { font-family: var(--display-font); font-size: 1rem; margin: 0 0 0.3rem; text-align: center; }
.cn-panel-desc { font-size: 0.8rem; color: var(--ink-soft); margin: 0 0 0.9rem; text-align: center; line-height: 1.4; }
.cn-code-scroll { overflow-x: auto; margin-bottom: 0.9rem; }
.cn-code {
background: var(--ink); color: #d4e2ff; border-radius: 8px; padding: 0.7rem 0.85rem;
font-family: var(--mono-font); font-size: 0.7rem; line-height: 1.55; white-space: pre;
display: inline-block;
min-width: 100%;
}
.infographic-cascadesetnull[data-theme="dark"] .cn-code { background: #05070a; }
.cn-kw { color: #9cc5ff; }
.cn-table-label { font-size: 0.72rem; color: var(--ink-soft); margin: 0 0 0.3rem; text-transform: uppercase; letter-spacing: 0.04em; }
.cn-table-scroll { overflow-x: auto; margin-bottom: 0.8rem; }
table.cn-table {
width: 100%; min-width: 180px; border-collapse: collapse; background: var(--paper);
border-radius: 7px; font-size: 0.8rem;
}
table.cn-table th, table.cn-table td {
border: 1px solid var(--line); padding: 0.4rem 0.55rem; text-align: left; white-space: nowrap;
transition: background 0.4s, color 0.4s, opacity 0.4s;
}
table.cn-table th { background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.68rem; }
table.cn-table tr.is-removing td { opacity: 0.3; text-decoration: line-through; background: var(--bad-soft); }
table.cn-table td.is-nulled { color: var(--bad); font-weight: 700; font-style: italic; }
.cn-msg {
margin-top: 0.7rem; padding: 0.6rem 0.75rem; border-radius: 7px; font-size: 0.8rem; line-height: 1.5; display: none;
}
.cn-msg.is-visible { display: block; }
.cn-msg.st-good { background: var(--good-soft); color: var(--good); }
.cn-msg.st-bad { background: var(--bad-soft); color: var(--bad); }
.cn-reset-row { display: flex; justify-content: center; margin-top: 1.25rem; }
.cn-reset-btn {
font: inherit; font-size: 0.82rem; background: var(--paper); border: 1px solid var(--line);
border-radius: 7px; padding: 0.5rem 1rem; cursor: pointer; color: var(--ink);
}
.cn-reset-btn:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-cascadesetnull *, .infographic-cascadesetnull *::before, .infographic-cascadesetnull *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-cascadesetnull" id="cascadesetnull">
<div class="cn-head">
<h2>ON DELETE CASCADE vs SET NULL</h2>
<p>Same action — delete Alice from <code>students</code> — set up two different ways. Watch what happens to her <code>enrollments</code> row in each.</p>
</div>
<div class="cn-trigger-row">
<button class="cn-trigger-btn" id="cnDeleteBtn" type="button">🗑️ DELETE Alice (id = 1) FROM students</button>
</div>
<div class="cn-panels">
<div class="cn-panel">
<p class="cn-panel-title">✅ ON DELETE CASCADE</p>
<p class="cn-panel-desc">Child rows are automatically deleted along with the parent.</p>
<div class="cn-code-scroll">
<div class="cn-code"><span class="cn-kw">FOREIGN KEY</span> (student_id) <span class="cn-kw">REFERENCES</span> students(student_id)
<span class="cn-kw">ON DELETE CASCADE</span></div>
</div>
<p class="cn-table-label">students</p>
<div class="cn-table-scroll">
<table class="cn-table" id="cascStudents">
<thead><tr><th>id</th><th>name</th></tr></thead>
<tbody>
<tr data-id="1"><td>1</td><td>Alice</td></tr>
<tr data-id="2"><td>2</td><td>Bob</td></tr>
</tbody>
</table>
</div>
<p class="cn-table-label">enrollments</p>
<div class="cn-table-scroll">
<table class="cn-table" id="cascEnroll">
<thead><tr><th>student_id</th><th>course_id</th></tr></thead>
<tbody>
<tr data-student="1"><td>1</td><td>C101</td></tr>
<tr data-student="2"><td>2</td><td>C102</td></tr>
</tbody>
</table>
</div>
<p class="cn-msg" id="cascMsg"></p>
</div>
<div class="cn-panel">
<p class="cn-panel-title">✅ ON DELETE SET NULL</p>
<p class="cn-panel-desc">The child row stays, but its foreign key becomes NULL.</p>
<div class="cn-code-scroll">
<div class="cn-code"><span class="cn-kw">FOREIGN KEY</span> (student_id) <span class="cn-kw">REFERENCES</span> students(student_id)
<span class="cn-kw">ON DELETE SET NULL</span></div>
</div>
<p class="cn-table-label">students</p>
<div class="cn-table-scroll">
<table class="cn-table" id="nullStudents">
<thead><tr><th>id</th><th>name</th></tr></thead>
<tbody>
<tr data-id="1"><td>1</td><td>Alice</td></tr>
<tr data-id="2"><td>2</td><td>Bob</td></tr>
</tbody>
</table>
</div>
<p class="cn-table-label">enrollments</p>
<div class="cn-table-scroll">
<table class="cn-table" id="nullEnroll">
<thead><tr><th>student_id</th><th>course_id</th></tr></thead>
<tbody>
<tr data-student="1"><td>1</td><td>C101</td></tr>
<tr data-student="2"><td>2</td><td>C102</td></tr>
</tbody>
</table>
</div>
<p class="cn-msg" id="nullMsg"></p>
</div>
</div>
<div class="cn-reset-row">
<button class="cn-reset-btn" id="cnReset" type="button">↺ Reset both</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('cascadesetnull');
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 deleteBtn = document.getElementById('cnDeleteBtn');
const resetBtn = document.getElementById('cnReset');
const cascStudentRow = () => document.querySelector('#cascStudents tr[data-id="1"]');
const cascEnrollRow = () => document.querySelector('#cascEnroll tr[data-student="1"]');
const nullStudentRow = () => document.querySelector('#nullStudents tr[data-id="1"]');
const nullEnrollRow = () => document.querySelector('#nullEnroll tr[data-student="1"]');
const cascMsg = document.getElementById('cascMsg');
const nullMsg = document.getElementById('nullMsg');
function showMsg(el, text, kind) {
el.textContent = text;
el.className = 'cn-msg is-visible st-' + kind;
}
deleteBtn.addEventListener('click', () => {
deleteBtn.disabled = true;
// CASCADE: student row and enrollment row both go
cascStudentRow().classList.add('is-removing');
cascEnrollRow().classList.add('is-removing');
setTimeout(() => {
cascStudentRow().remove();
cascEnrollRow().remove();
showMsg(cascMsg, '✔️ CASCADE: Alice\u2019s row in students is gone, and her enrollment row was automatically deleted with it. No orphan row left behind.', 'good');
}, 550);
// SET NULL: student row goes, enrollment row stays but student_id becomes NULL
nullStudentRow().classList.add('is-removing');
setTimeout(() => {
nullStudentRow().remove();
const enrollRow = nullEnrollRow();
const cell = enrollRow.children[0];
cell.textContent = 'NULL';
cell.classList.add('is-nulled');
showMsg(nullMsg, '✔️ SET NULL: Alice\u2019s row in students is gone, but her enrollment row survives — student_id just became NULL instead.', 'good');
}, 550);
});
resetBtn.addEventListener('click', () => {
document.getElementById('cascStudents').querySelector('tbody').innerHTML =
'<tr data-id="1"><td>1</td><td>Alice</td></tr><tr data-id="2"><td>2</td><td>Bob</td></tr>';
document.getElementById('cascEnroll').querySelector('tbody').innerHTML =
'<tr data-student="1"><td>1</td><td>C101</td></tr><tr data-student="2"><td>2</td><td>C102</td></tr>';
document.getElementById('nullStudents').querySelector('tbody').innerHTML =
'<tr data-id="1"><td>1</td><td>Alice</td></tr><tr data-id="2"><td>2</td><td>Bob</td></tr>';
document.getElementById('nullEnroll').querySelector('tbody').innerHTML =
'<tr data-student="1"><td>1</td><td>C101</td></tr><tr data-student="2"><td>2</td><td>C102</td></tr>';
cascMsg.classList.remove('is-visible');
nullMsg.classList.remove('is-visible');
deleteBtn.disabled = false;
});
})();
</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!');
});⚠️ Comparison
Option | What It Does | Use When... |
|---|---|---|
🔗 | Deletes child rows automatically | You want to delete all related records |
🕳️ | Sets FK to NULL in child rows | You want to keep child rows but break link |
❌ Default (No action) | Error if parent is deleted while child exists | You want to prevent accidental data loss |
🧠 Summary
These are referential integrity options added in the
FOREIGN KEYclauseThey prevent errors and help cleanly manage dependent data