π What is Secondary Index?
A Secondary Index is an additional index created on non-primary key columns to speed up data retrieval based on non-unique or non-key attributes.
π Why Use a Secondary Index?
In many cases, you query data not based on the primary key.
For example:
SELECT * FROM Employees WHERE Department = 'Sales';Here, Department is not a primary key β so searching would require a full table scan unless a secondary index is available.
π§ Key Features:
Feature | Description |
|---|---|
π¦ Based on | Non-primary or non-clustering columns |
π Key Type | Can be non-unique |
π Purpose | Speeds up queries using non-key attributes |
ποΈ Structure | Typically a dense index (every record is indexed) |
π Does NOT define | The physical order of the table (unlike clustered index) |
π Multiple Allowed | You can create many secondary indexes per table |
π§ Example:
Assume this table:
EmpID (PK) | Name | Dept |
|---|---|---|
101 | Alice | HR |
102 | Bob | Sales |
103 | Charlie | HR |
If you run:
SELECT * FROM Employees WHERE Dept = 'HR';A secondary index on Dept might look like:
HR β [101, 103]
Sales β [102]This allows the DBMS to directly fetch EmpIDs with 'HR' without scanning all rows.
π§± Dense Secondary Index Example:
[Dept: HR] β Row 1
[Dept: Sales] β Row 2
[Dept: HR] β Row 3All matching records are individually indexed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secondary Index β Try It Yourself</title>
<style>
.infographic-secondaryindex {
--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);
max-width: 980px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-secondaryindex[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-secondaryindex * { box-sizing: border-box; }
.si-head { text-align: center; margin-bottom: 0.25rem; }
.si-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.si-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 620px;
line-height: 1.55;
}
.si-head code { background: var(--paper-alt); border-radius: 4px; padding: 0.05rem 0.35rem; }
.si-query {
margin-top: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
flex-wrap: wrap;
}
.si-query-text {
font-family: var(--mono-font);
font-size: 0.88rem;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.6rem 0.9rem;
}
.si-query select {
font: inherit;
font-family: var(--mono-font);
font-size: 0.9rem;
padding: 0.5rem 0.6rem;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--paper);
color: var(--accent);
font-weight: 700;
}
.si-body { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-top: 1.75rem; min-width: 0; }
@media (max-width: 720px) { .si-body { grid-template-columns: 1fr; } }
.si-panel { background: var(--paper-alt); border: 1px solid var(--line); border-radius: 10px; padding: 1.1rem; min-width: 0; }
.si-panel-title { font-family: var(--display-font); font-size: 1rem; margin: 0 0 0.3rem; }
.si-panel-sub { font-size: 0.78rem; color: var(--ink-soft); margin: 0 0 0.75rem; }
.si-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.si-table {
width: 100%;
min-width: 220px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.85rem;
}
table.si-table th, table.si-table td {
border: 1px solid var(--line);
padding: 0.45rem 0.65rem;
text-align: left;
white-space: nowrap;
transition: background 0.2s, color 0.2s;
}
table.si-table th { background: var(--accent); color: #fff; font-weight: 700; font-size: 0.72rem; }
table.si-table tr.is-checking td { background: var(--accent-soft); }
table.si-table tr.is-match td { background: var(--good-soft); color: var(--good); font-weight: 700; }
.si-index-list { display: flex; flex-direction: column; gap: 0.4rem; }
.si-index-entry {
font-family: var(--mono-font);
font-size: 0.82rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 6px;
padding: 0.45rem 0.65rem;
transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.si-index-entry.is-checking { background: var(--accent-soft); border-color: var(--accent); }
.si-index-entry.is-match { background: var(--good-soft); color: var(--good); border-color: var(--good); font-weight: 700; }
.si-btn-row { display: flex; gap: 0.6rem; margin-top: 1.5rem; justify-content: center; flex-wrap: wrap; }
.si-btn {
font: inherit;
font-size: 0.85rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 7px;
padding: 0.6rem 1.1rem;
cursor: pointer;
}
.si-btn:hover { opacity: 0.9; }
.si-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.si-btn.is-secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.si-log-scroll { overflow-x: auto; }
.si-log {
margin-top: 1.1rem;
background: var(--ink);
color: #d4e2ff;
border-radius: 8px;
padding: 0.75rem 0.9rem;
font-family: var(--mono-font);
font-size: 0.78rem;
line-height: 1.65;
min-height: 2rem;
}
.infographic-secondaryindex[data-theme="dark"] .si-log { background: #05070a; }
.si-log-line { opacity: 0; animation: si-line-in 0.25s ease forwards; }
@keyframes si-line-in { to { opacity: 1; } }
.si-log-placeholder { color: #7c8aa8; font-style: italic; }
.si-scoreboard { display: flex; gap: 1rem; justify-content: center; margin-top: 1.5rem; flex-wrap: wrap; }
.si-score { background: var(--paper-alt); border: 1px solid var(--line); border-radius: 8px; padding: 0.7rem 1.2rem; text-align: center; min-width: 190px; }
.si-score-label { font-size: 0.75rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.04em; }
.si-score-value { font-family: var(--mono-font); font-size: 1.15rem; margin-top: 0.2rem; color: var(--accent); }
.si-score.is-winner { border-color: var(--good); }
.si-score.is-winner .si-score-value { color: var(--good); }
.si-note {
font-size: 0.8rem;
color: var(--ink-soft);
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.65rem 0.9rem;
margin-top: 1.5rem;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.infographic-secondaryindex *, .infographic-secondaryindex *::before, .infographic-secondaryindex *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-secondaryindex" id="secondaryindex">
<div class="si-head">
<h2>Secondary Index β Try It Yourself</h2>
<p>Dept isn't the primary key, and several employees can share the same value. Run the same search two ways.</p>
</div>
<div class="si-query">
<span class="si-query-text">SELECT * FROM Employees WHERE Dept =</span>
<select id="siDeptSelect"></select>
</div>
<div class="si-body">
<div class="si-panel">
<p class="si-panel-title">π§\u200dπΌ Employees table</p>
<p class="si-panel-sub">Stored in insertion order β untouched by any index.</p>
<div class="si-table-scroll">
<table class="si-table" id="siTable"></table>
</div>
</div>
<div class="si-panel">
<p class="si-panel-title">π² Secondary Index on Dept</p>
<p class="si-panel-sub">Non-unique key β each entry holds a bucket of EmpIDs.</p>
<div class="si-index-list" id="siIndexList"></div>
</div>
</div>
<div class="si-btn-row">
<button class="si-btn" id="siRunScan" type="button">Run without index (table scan)</button>
<button class="si-btn" id="siRunIndex" type="button">Run with Secondary Index</button>
<button class="si-btn is-secondary" id="siReset" type="button">Reset</button>
</div>
<div class="si-log-scroll">
<div class="si-log" id="siLog"><span class="si-log-placeholder">Run a search above to see it happen step by step...</span></div>
</div>
<div class="si-scoreboard">
<div class="si-score" id="siScoreScan">
<div class="si-score-label">Table scan</div>
<div class="si-score-value">β</div>
</div>
<div class="si-score" id="siScoreIndex">
<div class="si-score-label">Secondary index</div>
<div class="si-score-value">β</div>
</div>
</div>
<div class="si-note">Notice the Employees table never reorders, no matter which method runs β a secondary index never changes physical row order, unlike a clustering index.</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('secondaryindex');
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 employees = [
{ id: 101, name: 'Alice', dept: 'HR' },
{ id: 102, name: 'Bob', dept: 'Sales' },
{ id: 104, name: 'David', dept: 'IT' },
{ id: 103, name: 'Charlie', dept: 'HR' },
{ id: 106, name: 'Frank', dept: 'Sales' },
{ id: 105, name: 'Emma', dept: 'HR' }
];
const buckets = {};
employees.forEach(e => {
if (!buckets[e.dept]) buckets[e.dept] = [];
buckets[e.dept].push(e.id);
});
const deptOrder = Object.keys(buckets).sort();
const table = document.getElementById('siTable');
const indexList = document.getElementById('siIndexList');
const select = document.getElementById('siDeptSelect');
const log = document.getElementById('siLog');
const scoreScan = document.querySelector('#siScoreScan .si-score-value');
const scoreIndex = document.querySelector('#siScoreIndex .si-score-value');
const scoreScanCard = document.getElementById('siScoreScan');
const scoreIndexCard = document.getElementById('siScoreIndex');
const runScanBtn = document.getElementById('siRunScan');
const runIndexBtn = document.getElementById('siRunIndex');
function renderTable() {
let html = '<thead><tr><th>EmpID</th><th>Name</th><th>Dept</th></tr></thead><tbody>';
employees.forEach(e => {
html += '<tr data-row="' + e.id + '"><td>' + e.id + '</td><td>' + e.name + '</td><td>' + e.dept + '</td></tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
function renderIndex() {
indexList.innerHTML = deptOrder.map(d =>
'<div class="si-index-entry" data-dept="' + d + '">[' + d + '] β [' + buckets[d].join(', ') + ']</div>'
).join('');
}
function fillSelect() {
select.innerHTML = deptOrder.map(d => '<option value="' + d + '">' + d + '</option>').join('');
select.value = 'HR';
}
function clearLog() { log.innerHTML = ''; }
function appendLog(text) {
if (log.querySelector('.si-log-placeholder')) log.innerHTML = '';
const line = document.createElement('div');
line.className = 'si-log-line';
line.textContent = text;
log.appendChild(line);
}
function clearHighlights() {
table.querySelectorAll('tr').forEach(tr => tr.classList.remove('is-checking', 'is-match'));
indexList.querySelectorAll('.si-index-entry').forEach(el => el.classList.remove('is-checking', 'is-match'));
}
function setButtons(disabled) {
runScanBtn.disabled = disabled;
runIndexBtn.disabled = disabled;
}
function markWinner(scanCount, indexCount) {
scoreScanCard.classList.toggle('is-winner', scanCount < indexCount);
scoreIndexCard.classList.toggle('is-winner', indexCount < scanCount);
}
runScanBtn.addEventListener('click', () => {
clearHighlights();
clearLog();
setButtons(true);
const target = select.value;
let i = 0;
let checked = 0;
const matches = [];
function step() {
if (i >= employees.length) {
scoreScan.textContent = checked + ' rows checked, ' + matches.length + ' matched';
appendLog('Scan complete β every row had to be checked, since matches could be anywhere.');
markWinner(checked, parseInt(scoreIndex.textContent) || 999);
setButtons(false);
return;
}
const emp = employees[i];
const tr = table.querySelector('tr[data-row="' + emp.id + '"]');
table.querySelectorAll('tr').forEach(t => t.classList.remove('is-checking'));
tr.classList.add('is-checking');
checked++;
const isMatch = emp.dept === target;
appendLog('Row EmpID ' + emp.id + ': Dept = ' + emp.dept + (isMatch ? ' β match' : ' β ' + target));
if (isMatch) {
tr.classList.remove('is-checking');
tr.classList.add('is-match');
matches.push(emp.id);
}
i++;
setTimeout(step, 340);
}
step();
});
runIndexBtn.addEventListener('click', () => {
clearHighlights();
clearLog();
setButtons(true);
const target = select.value;
const el = indexList.querySelector('.si-index-entry[data-dept="' + target + '"]');
el.classList.add('is-checking');
appendLog('Secondary index: look up bucket for [' + target + ']...');
setTimeout(() => {
el.classList.remove('is-checking');
el.classList.add('is-match');
const ids = buckets[target];
appendLog('β Bucket found: [' + ids.join(', ') + ']. Following ' + ids.length + ' pointer(s) directly β no other rows touched.');
let j = 0;
function jump() {
if (j >= ids.length) {
scoreIndex.textContent = '1 lookup + ' + ids.length + ' direct jump' + (ids.length === 1 ? '' : 's');
markWinner(parseInt(scoreScan.textContent) || 999, 1 + ids.length);
setButtons(false);
return;
}
const tr = table.querySelector('tr[data-row="' + ids[j] + '"]');
tr.classList.add('is-match');
appendLog('Jump to EmpID ' + ids[j] + ' directly.');
j++;
setTimeout(jump, 300);
}
jump();
}, 400);
});
document.getElementById('siReset').addEventListener('click', () => {
clearHighlights();
clearLog();
log.innerHTML = '<span class="si-log-placeholder">Run a search above to see it happen step by step...</span>';
scoreScan.textContent = 'β';
scoreIndex.textContent = 'β';
scoreScanCard.classList.remove('is-winner');
scoreIndexCard.classList.remove('is-winner');
setButtons(false);
});
renderTable();
renderIndex();
fillSelect();
})();
</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!');
});π Primary vs Secondary Index
Feature | Primary Index | Secondary Index |
|---|---|---|
Based on | Primary key / clustering key | Any other column |
Uniqueness | Always unique | Can be non-unique |
Physical row order | Affects row order (if clustered) | Does not affect row order |
Number per table | Only one (clustered) | Can have multiple |
Index type | Usually sparse or dense | Usually dense |
β οΈ Limitations:
Requires more storage.
Must be maintained during updates/inserts/deletes.
Can slow down write operations.
β When to Use Secondary Index:
Use when:
You often search/filter using non-primary columns.
You do JOINs,
WHERE,ORDER BY, orGROUP BYon those fields.You want to avoid full table scans.
π§ Summary:
Aspect | Secondary Index |
|---|---|
Column Type | Non-primary |
Index Type | Usually Dense |
Row Order | Unchanged |
Purpose | Faster searches on non-key fields |
Storage Use | Moderate to High |