π What is a Primary Index?
A Primary Index is an index created on a primary key column of a table.
π§ Features:
Automatically created when you define a primary key.
It is unique β no two rows have the same primary key.
Index entries are in sorted order of the primary key.
Often dense index (i.e., every search key has a pointer).
π§ Example:
CREATE TABLE Students (
RollNo INT PRIMARY KEY,
Name VARCHAR(50)
);This creates a primary index on RollNo.
π What is a Clustering Index?
A Clustering Index determines the physical order of data rows in the table. That means the table is stored on disk in the order of this index.
π§ Features:
Only one clustering index per table.
Not necessarily on a primary key.
Often used for columns that are frequently searched or range-queried.
Can be either unique or non-unique.
Data rows themselves are stored in sorted order of the clustering key.
π§ Example:
CREATE CLUSTERED INDEX idx_name ON Students(Name);Here, the table will store its rows sorted by Name physically.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Primary Index vs Clustering Index</title>
<style>
.infographic-indextypes {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--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: 940px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-indextypes[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;
}
.infographic-indextypes * { box-sizing: border-box; }
.it-head { text-align: center; margin-bottom: 0.25rem; }
.it-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.it-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 600px;
line-height: 1.55;
}
.it-tabs { display: flex; justify-content: center; gap: 0.5rem; margin: 1.75rem 0 0; flex-wrap: wrap; }
.it-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);
}
.it-tab:hover { border-color: var(--accent); }
.it-tab.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.it-panel { display: none; margin-top: 1.5rem; }
.it-panel.is-active { display: block; animation: it-fade 0.2s ease; }
@keyframes it-fade { from { opacity: 0; } to { opacity: 1; } }
.it-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
}
.it-frame-desc { font-size: 0.87rem; color: var(--ink-soft); line-height: 1.55; margin: 0 0 1.1rem; }
.it-cols { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.it-col { flex: 1 1 220px; min-width: 0; }
.it-col-title { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 0 0 0.5rem; }
.it-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.it-table {
width: 100%;
min-width: 200px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.86rem;
}
table.it-table th, table.it-table td {
border: 1px solid var(--line);
padding: 0.45rem 0.65rem;
text-align: left;
white-space: nowrap;
}
table.it-table th { background: var(--accent); color: #fff; font-weight: 700; font-size: 0.72rem; }
table.it-table td.it-pos { color: var(--ink-soft); font-family: var(--mono-font); font-size: 0.72rem; }
table.it-table tr.it-row-anim td { animation: it-row-flash 0.6s ease; }
@keyframes it-row-flash { from { background: var(--good-soft); } to { background: transparent; } }
.it-index-list { display: flex; flex-direction: column; gap: 0.35rem; }
.it-index-entry {
font-family: var(--mono-font);
font-size: 0.82rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 6px;
padding: 0.4rem 0.6rem;
opacity: 0;
transform: translateY(-4px);
animation: it-entry-in 0.3s ease forwards;
}
@keyframes it-entry-in { to { opacity: 1; transform: translateY(0); } }
.it-index-placeholder { font-size: 0.82rem; color: var(--ink-soft); font-style: italic; }
.it-btn-row { display: flex; gap: 0.6rem; margin-top: 1.1rem; flex-wrap: wrap; }
.it-btn {
font: inherit;
font-size: 0.85rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 7px;
padding: 0.6rem 1.1rem;
cursor: pointer;
}
.it-btn:hover { opacity: 0.9; }
.it-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.it-btn.is-secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.it-msg {
margin-top: 1rem;
padding: 0.7rem 0.9rem;
border-radius: 8px;
font-size: 0.85rem;
line-height: 1.55;
display: none;
background: var(--good-soft);
color: var(--good);
}
.it-msg.is-visible { display: block; }
.it-note {
font-size: 0.78rem;
color: var(--ink-soft);
background: var(--paper);
border: 1px solid var(--line);
border-radius: 7px;
padding: 0.5rem 0.75rem;
margin-top: 0.9rem;
}
@media (prefers-reduced-motion: reduce) {
.infographic-indextypes *, .infographic-indextypes *::before, .infographic-indextypes *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-indextypes" id="indextypes">
<div class="it-head">
<h2>Primary Index vs Clustering Index</h2>
<p>Both speed up lookups, but they do it in very different ways. Watch what actually happens to the table's physical rows in each case.</p>
</div>
<div class="it-tabs">
<button class="it-tab" data-tab="primary" type="button">π Primary Index</button>
<button class="it-tab" data-tab="clustering" type="button">π Clustering Index</button>
</div>
<!-- PRIMARY INDEX -->
<div class="it-panel" data-panel="primary">
<div class="it-frame">
<p class="it-frame-desc">This table isn't stored in RollNo order β rows just sit wherever they were inserted. Build a primary index and see whether the rows themselves move.</p>
<div class="it-cols">
<div class="it-col">
<p class="it-col-title">Students table (physical order)</p>
<div class="it-table-scroll">
<table class="it-table" id="itPrimaryTable"></table>
</div>
</div>
<div class="it-col">
<p class="it-col-title">Primary Index on RollNo</p>
<div class="it-index-list" id="itPrimaryIndex">
<span class="it-index-placeholder">Not built yet β click below.</span>
</div>
</div>
</div>
<div class="it-btn-row">
<button class="it-btn" id="itBuildPrimary" type="button">Build Primary Index on RollNo</button>
<button class="it-btn is-secondary" id="itResetPrimary" type="button">Reset</button>
</div>
<div class="it-msg" id="itPrimaryMsg"></div>
</div>
</div>
<!-- CLUSTERING INDEX -->
<div class="it-panel" data-panel="clustering">
<div class="it-frame">
<p class="it-frame-desc">Same starting table. This time, build a clustering index on Name and watch what happens to the rows themselves.</p>
<div class="it-cols">
<div class="it-col" style="flex: 1 1 100%;">
<p class="it-col-title">Students table (physical order)</p>
<div class="it-table-scroll">
<table class="it-table" id="itClusterTable"></table>
</div>
</div>
</div>
<div class="it-btn-row">
<button class="it-btn" id="itBuildCluster" type="button">Build Clustering Index on Name</button>
<button class="it-btn is-secondary" id="itResetCluster" type="button">Reset</button>
</div>
<div class="it-msg" id="itClusterMsg"></div>
<div class="it-note">Note: RollNo order and Name order don't match here β that's the point. A clustering index doesn't have to be on the primary key, and a table can only have one, since there's only one physical order at a time.</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('indextypes');
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('.it-tab');
const panels = document.querySelectorAll('.it-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('primary');
const baseRows = [
{ rollNo: 103, name: 'Zara' },
{ rollNo: 101, name: 'Mike' },
{ rollNo: 105, name: 'Alice' },
{ rollNo: 102, name: 'Queen' },
{ rollNo: 104, name: 'Bob' }
];
// ---------- Primary Index tab ----------
(function () {
const table = document.getElementById('itPrimaryTable');
const indexList = document.getElementById('itPrimaryIndex');
const msg = document.getElementById('itPrimaryMsg');
const buildBtn = document.getElementById('itBuildPrimary');
function renderTable() {
let html = '<thead><tr><th>Pos</th><th>RollNo</th><th>Name</th></tr></thead><tbody>';
baseRows.forEach((r, i) => {
html += '<tr><td class="it-pos">' + (i + 1) + '</td><td>' + r.rollNo + '</td><td>' + r.name + '</td></tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
buildBtn.addEventListener('click', () => {
const sorted = baseRows
.map((r, i) => ({ ...r, pos: i + 1 }))
.sort((a, b) => a.rollNo - b.rollNo);
indexList.innerHTML = '';
sorted.forEach((r, i) => {
const el = document.createElement('div');
el.className = 'it-index-entry';
el.style.animationDelay = (i * 0.12) + 's';
el.textContent = '[' + r.rollNo + '] β Pos ' + r.pos + ' (' + r.name + ')';
indexList.appendChild(el);
});
msg.textContent = 'βοΈ The index is sorted by RollNo β but look at the table: the rows never moved. Pos 1 is still Zara (RollNo 103). The index just holds pointers to wherever the data already lives.';
msg.classList.add('is-visible');
buildBtn.disabled = true;
});
document.getElementById('itResetPrimary').addEventListener('click', () => {
renderTable();
indexList.innerHTML = '<span class="it-index-placeholder">Not built yet β click below.</span>';
msg.classList.remove('is-visible');
buildBtn.disabled = false;
});
renderTable();
})();
// ---------- Clustering Index tab ----------
(function () {
const table = document.getElementById('itClusterTable');
const msg = document.getElementById('itClusterMsg');
const buildBtn = document.getElementById('itBuildCluster');
let rows = baseRows.slice();
function renderTable(animate) {
let html = '<thead><tr><th>Pos</th><th>RollNo</th><th>Name</th></tr></thead><tbody>';
rows.forEach((r, i) => {
html += '<tr' + (animate ? ' class="it-row-anim"' : '') + ' style="' + (animate ? 'animation-delay:' + (i * 0.1) + 's' : '') + '"><td class="it-pos">' + (i + 1) + '</td><td>' + r.rollNo + '</td><td>' + r.name + '</td></tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
buildBtn.addEventListener('click', () => {
rows = rows.slice().sort((a, b) => a.name.localeCompare(b.name));
renderTable(true);
msg.textContent = 'βοΈ This time the table itself got physically rewritten in Name order β Alice is now Pos 1, even though her RollNo (105) is the highest. There\u2019s no separate pointer list here: the data rows ARE the index.';
msg.classList.add('is-visible');
buildBtn.disabled = true;
});
document.getElementById('itResetCluster').addEventListener('click', () => {
rows = baseRows.slice();
renderTable(false);
msg.classList.remove('is-visible');
buildBtn.disabled = false;
});
renderTable(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 Table: Primary Index vs Clustering Index
Feature | Primary Index | Clustering Index |
|---|---|---|
Based on | Primary key column | Any column (not necessarily primary key) |
Order of rows | May or may not determine row order | Determines actual physical row order |
Number per table | Only one (on primary key) | Only one allowed |
Uniqueness | Always unique | Can be unique or non-unique |
Automatic/Manual | Created automatically with primary key | Created manually by user |
Index structure | Usually dense | Usually sparse |
Use case | Uniquely identify rows | Improve performance for range queries |
π Visual Summary:
Imagine a book:
Primary Index: Like a table of contents pointing to chapters (but chapters are in any order).
Clustering Index: Like pages sorted by topic (data physically follows index order).
π When to Use Which?
Use Primary Index for fast key-based access (e.g., lookup by Roll Number, ID, etc.).
Use Clustering Index if:
You do lots of range queries (e.g.,
WHERE salary BETWEEN 10000 AND 50000)You often sort or group by a specific column.