The GROUP BY clause groups records that share common values so we can apply aggregate functions like:
COUNT(), SUM(), AVG(), MAX(), MIN() etc.
⚙️ Base Setup (ONE Code Block)
We will use THIS data for all examples below ⬇️
-- Students Table & Data
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
grade VARCHAR(2),
section VARCHAR(1),
marks INT
);
INSERT INTO students (name, grade, section, marks) VALUES
('Raj', 'B', 'X', 78),
('Tina', 'B', 'X', 82),
('Anya', 'A', 'Y', 90),
('Sara', 'A', 'Y', 88),
('John', 'A', 'X', 85),
('Neha', 'C', 'Z', 70);📌 We will use ONLY this table and data for all examples below
No repeated CREATE TABLE or INSERT again.
📌 COUNT() → Number of Students per Grade
SELECT grade, COUNT(*) AS total_students
FROM students
GROUP BY grade;📌 COUNT() with WHERE (filter before grouping)
SELECT grade, COUNT(*) AS total_students
FROM students
WHERE grade IN ('A','B')
GROUP BY grade;📌 GROUP BY Multiple Columns
SELECT grade, section, COUNT(*) AS student_count
FROM students
GROUP BY grade, section;📌 HAVING → Filter After Grouping
SELECT grade, COUNT(*) AS total_students
FROM students
GROUP BY grade
HAVING COUNT(*) > 1;📌 SUM() → Total Marks per Grade
SELECT grade, SUM(marks) AS total_marks
FROM students
GROUP BY grade;📌 AVG() → Average Marks per Grade
SELECT grade, AVG(marks) AS average_marks
FROM students
GROUP BY grade;📌 MAX() → Highest Marks per Grade
SELECT grade, MAX(marks) AS highest_mark
FROM students
GROUP BY grade;📌 MIN() → Lowest Marks per Grade
SELECT grade, MIN(marks) AS lowest_mark
FROM students
GROUP BY grade;<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GROUP BY & Aggregates — Try It Yourself</title>
<style>
.infographic-groupbyrunner {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--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: 780px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-groupbyrunner[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;
--on-accent: #101418;
}
.infographic-groupbyrunner * { box-sizing: border-box; }
.gb-topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; gap: 0.5rem; flex-wrap: wrap; }
.gb-progress { font-size: 0.78rem; color: var(--ink-soft); }
.gb-showfull-btn {
font: inherit;
font-size: 0.78rem;
background: none;
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.35rem 0.8rem;
cursor: pointer;
color: var(--ink-soft);
}
.gb-showfull-btn:hover { border-color: var(--accent); color: var(--accent); }
.gb-showfull-btn.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.gb-fullcode-panel { display: none; margin-bottom: 1.25rem; }
.gb-fullcode-panel.is-visible { display: block; }
.gb-fullcode-scroll { overflow-x: auto; }
.gb-fullcode {
background: var(--ink);
color: #d4e2ff;
border-radius: 8px;
padding: 1rem 1.2rem;
font-family: var(--mono-font);
font-size: 0.72rem;
line-height: 1.7;
white-space: pre;
opacity: 0.85;
display: inline-block;
min-width: 100%;
}
.infographic-groupbyrunner[data-theme="dark"] .gb-fullcode { background: #05070a; }
.gb-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
}
.gb-kw { color: #9cc5ff; }
.gb-str { color: #f8d980; }
.gb-num { color: #4ade80; }
.gb-cm { color: #7c8aa8; }
.gb-fn { color: #f472b6; }
.gb-step { display: none; margin-bottom: 1rem; }
.gb-step.is-revealed { display: block; animation: gb-step-in 0.4s ease; }
@keyframes gb-step-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.gb-step.is-done .gb-step-code-full { display: none; }
.gb-step.is-done .gb-step-done-pill { display: flex; }
.gb-step-done-pill {
display: none;
align-items: center;
gap: 0.5rem;
background: var(--good-soft);
color: var(--good);
border-radius: 7px;
padding: 0.55rem 0.85rem;
font-family: var(--mono-font);
font-size: 0.78rem;
cursor: pointer;
}
.gb-step-done-pill .chevron { margin-left: auto; font-size: 0.7rem; opacity: 0.7; }
.gb-step.is-expanded-done .gb-step-code-full { display: block; }
.gb-step.is-expanded-done .gb-step-done-pill { background: var(--accent-soft); color: var(--accent); }
.gb-step-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 0 0 0.5rem; }
.gb-step-note { font-size: 0.82rem; color: var(--ink-soft); margin: 0.5rem 0 0; line-height: 1.45; }
.gb-code-scroll { overflow-x: auto; }
.gb-code {
background: var(--ink);
color: #d4e2ff;
border-radius: 8px;
padding: 0.85rem 1.05rem;
font-family: var(--mono-font);
font-size: 0.8rem;
line-height: 1.6;
white-space: pre;
display: inline-block;
min-width: 100%;
}
.infographic-groupbyrunner[data-theme="dark"] .gb-code { background: #05070a; }
.gb-run-row { display: flex; justify-content: center; margin-top: 0.85rem; }
.gb-run-btn {
font: inherit;
font-size: 0.85rem;
background: var(--accent);
color: var(--on-accent);
border: none;
border-radius: 7px;
padding: 0.55rem 1.1rem;
cursor: pointer;
}
.gb-run-btn:hover { opacity: 0.9; }
.gb-run-btn:disabled { opacity: 0.55; cursor: wait; }
.gb-run-btn.is-secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.gb-output-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 1.1rem 0 0.6rem; }
.gb-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.gb-table {
width: 100%;
min-width: 260px;
border-collapse: collapse;
background: var(--paper);
border-radius: 8px;
font-size: 0.88rem;
}
table.gb-table th, table.gb-table td {
border: 1px solid var(--line);
padding: 0.5rem 0.7rem;
text-align: left;
white-space: nowrap;
}
table.gb-table th { background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.72rem; }
table.gb-table th.agg-col { text-decoration: underline; }
table.gb-table tr.is-new td { animation: gb-row-in 0.5s ease; }
@keyframes gb-row-in { from { background: var(--good-soft); } to { background: transparent; } }
table.gb-table td.is-aggcol { color: var(--accent); font-weight: 700; }
.gb-status {
text-align: center;
font-size: 0.82rem;
margin-top: 0.85rem;
padding: 0.5rem 0.8rem;
border-radius: 7px;
display: none;
background: var(--good-soft);
color: var(--good);
}
.gb-status.is-visible { display: block; animation: gb-step-in 0.3s ease; }
.gb-reset-row { display: flex; justify-content: center; margin-top: 1.25rem; }
@media (prefers-reduced-motion: reduce) {
.infographic-groupbyrunner *, .infographic-groupbyrunner *::before, .infographic-groupbyrunner *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-groupbyrunner" id="groupbyrunner">
<div class="gb-topbar">
<span class="gb-progress" id="gbProgress"></span>
<button class="gb-showfull-btn" id="gbShowFullBtn" type="button">👁️ Show full SQL script</button>
</div>
<div class="gb-fullcode-panel" id="gbFullCodePanel">
<div class="gb-fullcode-scroll">
<div class="gb-fullcode" id="gbFullCodeContent"></div>
</div>
</div>
<div class="gb-frame">
<div id="gbSteps"></div>
<p class="gb-status" id="gbStatus"></p>
<p class="gb-output-label" id="gbOutputLabel" style="display:none;">Query Result</p>
<div class="gb-table-scroll" id="gbTableScroll" style="display:none;">
<table class="gb-table" id="gbTable"></table>
</div>
<div class="gb-reset-row">
<button class="gb-run-btn is-secondary" id="gbReset" type="button">↺ Reset</button>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('groupbyrunner');
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 students = [
{ id: 1, name: 'Raj', grade: 'B', section: 'X', marks: 78 },
{ id: 2, name: 'Tina', grade: 'B', section: 'X', marks: 82 },
{ id: 3, name: 'Anya', grade: 'A', section: 'Y', marks: 90 },
{ id: 4, name: 'Sara', grade: 'A', section: 'Y', marks: 88 },
{ id: 5, name: 'John', grade: 'A', section: 'X', marks: 85 },
{ id: 6, name: 'Neha', grade: 'C', section: 'Z', marks: 70 }
];
function kw(s) { return '<span class="gb-kw">' + s + '</span>'; }
function str(s) { return '<span class="gb-str">' + s + '</span>'; }
function num(s) { return '<span class="gb-num">' + s + '</span>'; }
function cm(s) { return '<span class="gb-cm">' + s + '</span>'; }
function fn(s) { return '<span class="gb-fn">' + s + '</span>'; }
function groupBy(rows, keyFn) {
const map = new Map();
rows.forEach(r => {
const k = keyFn(r);
if (!map.has(k)) map.set(k, []);
map.get(k).push(r);
});
return map;
}
const setupCode =
cm('-- Students Table & Data') + '\n' +
kw('CREATE TABLE') + ' students (\n' +
' id ' + kw('INT AUTO_INCREMENT PRIMARY KEY') + ',\n' +
' name ' + kw('VARCHAR') + '(50),\n' +
' grade ' + kw('VARCHAR') + '(2),\n' +
' section ' + kw('VARCHAR') + '(1),\n' +
' marks ' + kw('INT') + '\n' +
');\n\n' +
kw('INSERT INTO') + ' students (name, grade, section, marks) ' + kw('VALUES') + '\n' +
'(' + str("'Raj'") + ', ' + str("'B'") + ', ' + str("'X'") + ', ' + num('78') + '),\n' +
'(' + str("'Tina'") + ', ' + str("'B'") + ', ' + str("'X'") + ', ' + num('82') + '),\n' +
'(' + str("'Anya'") + ', ' + str("'A'") + ', ' + str("'Y'") + ', ' + num('90') + '),\n' +
'(' + str("'Sara'") + ', ' + str("'A'") + ', ' + str("'Y'") + ', ' + num('88') + '),\n' +
"(" + str("'John'") + ', ' + str("'A'") + ', ' + str("'X'") + ', ' + num('85') + '),\n' +
'(' + str("'Neha'") + ', ' + str("'C'") + ', ' + str("'Z'") + ', ' + num('70') + ');';
const steps = [
{
key: 'setup', title: 'Setup', btnLabel: '▶ Run Setup', doneLabel: '✓ Setup ran',
code: setupCode, note: '',
cols: ['id', 'name', 'grade', 'section', 'marks'], aggCols: [],
getRows: () => students.map(s => [s.id, s.name, s.grade, s.section, s.marks])
},
{
key: 'count', title: 'COUNT() per grade', btnLabel: '▶ Run COUNT()', doneLabel: '✓ COUNT() ran',
code: cm('-- Number of students per grade') + '\n' + kw('SELECT') + ' grade, ' + fn('COUNT') + '(*) ' + kw('AS') + ' total_students\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade;',
note: 'Groups all 6 rows by grade and counts how many fall into each.',
cols: ['grade', 'total_students'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => [k, g.get(k).length]);
}
},
{
key: 'countWhere', title: 'COUNT() with WHERE', btnLabel: '▶ Run COUNT() + WHERE', doneLabel: '✓ COUNT() + WHERE ran',
code: cm("-- Filter rows before grouping") + '\n' + kw('SELECT') + ' grade, ' + fn('COUNT') + '(*) ' + kw('AS') + ' total_students\n' + kw('FROM') + ' students\n' + kw('WHERE') + ' grade ' + kw('IN') + ' (' + str("'A'") + ', ' + str("'B'") + ')\n' + kw('GROUP BY') + ' grade;',
note: 'Neha (grade C) is filtered out before grouping even happens, so C never appears.',
cols: ['grade', 'total_students'], aggCols: [1],
getRows: () => {
const filtered = students.filter(s => s.grade === 'A' || s.grade === 'B');
const g = groupBy(filtered, s => s.grade);
return [...g.keys()].sort().map(k => [k, g.get(k).length]);
}
},
{
key: 'multiGroup', title: 'GROUP BY multiple columns', btnLabel: '▶ Run GROUP BY grade, section', doneLabel: '✓ Multi-column GROUP BY ran',
code: cm('-- Group by more than one column') + '\n' + kw('SELECT') + ' grade, section, ' + fn('COUNT') + '(*) ' + kw('AS') + ' student_count\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade, section;',
note: 'Each unique (grade, section) pair becomes its own group.',
cols: ['grade', 'section', 'student_count'], aggCols: [2],
getRows: () => {
const g = groupBy(students, s => s.grade + '|' + s.section);
return [...g.keys()].sort().map(k => {
const [grade, section] = k.split('|');
return [grade, section, g.get(k).length];
});
}
},
{
key: 'having', title: 'HAVING COUNT(*) > 1', btnLabel: '▶ Run HAVING', doneLabel: '✓ HAVING ran',
code: cm('-- Filter groups after grouping') + '\n' + kw('SELECT') + ' grade, ' + fn('COUNT') + '(*) ' + kw('AS') + ' total_students\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade\n' + kw('HAVING') + ' ' + fn('COUNT') + '(*) > ' + num('1') + ';',
note: 'Grouping happens first, then HAVING drops any group with only 1 student — Neha\u2019s grade C disappears here.',
cols: ['grade', 'total_students'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => [k, g.get(k).length]).filter(r => r[1] > 1);
}
},
{
key: 'sum', title: 'SUM() per grade', btnLabel: '▶ Run SUM(marks)', doneLabel: '✓ SUM() ran',
code: cm('-- Total marks per grade') + '\n' + kw('SELECT') + ' grade, ' + fn('SUM') + '(marks) ' + kw('AS') + ' total_marks\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade;',
note: '',
cols: ['grade', 'total_marks'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => [k, g.get(k).reduce((a, s) => a + s.marks, 0)]);
}
},
{
key: 'avg', title: 'AVG() per grade', btnLabel: '▶ Run AVG(marks)', doneLabel: '✓ AVG() ran',
code: cm('-- Average marks per grade') + '\n' + kw('SELECT') + ' grade, ' + fn('AVG') + '(marks) ' + kw('AS') + ' average_marks\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade;',
note: '',
cols: ['grade', 'average_marks'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => {
const items = g.get(k);
const avg = items.reduce((a, s) => a + s.marks, 0) / items.length;
return [k, Math.round(avg * 100) / 100];
});
}
},
{
key: 'max', title: 'MAX() per grade', btnLabel: '▶ Run MAX(marks)', doneLabel: '✓ MAX() ran',
code: cm('-- Highest marks per grade') + '\n' + kw('SELECT') + ' grade, ' + fn('MAX') + '(marks) ' + kw('AS') + ' highest_mark\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade;',
note: '',
cols: ['grade', 'highest_mark'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => [k, Math.max(...g.get(k).map(s => s.marks))]);
}
},
{
key: 'min', title: 'MIN() per grade', btnLabel: '▶ Run MIN(marks)', doneLabel: '✓ MIN() ran',
code: cm('-- Lowest marks per grade') + '\n' + kw('SELECT') + ' grade, ' + fn('MIN') + '(marks) ' + kw('AS') + ' lowest_mark\n' + kw('FROM') + ' students\n' + kw('GROUP BY') + ' grade;',
note: '',
cols: ['grade', 'lowest_mark'], aggCols: [1],
getRows: () => {
const g = groupBy(students, s => s.grade);
return [...g.keys()].sort().map(k => [k, Math.min(...g.get(k).map(s => s.marks))]);
}
}
];
const stepsContainer = document.getElementById('gbSteps');
const progress = document.getElementById('gbProgress');
const outputLabel = document.getElementById('gbOutputLabel');
const tableScroll = document.getElementById('gbTableScroll');
const table = document.getElementById('gbTable');
const status = document.getElementById('gbStatus');
const fullCodeContent = document.getElementById('gbFullCodeContent');
fullCodeContent.innerHTML = steps.map(s => s.code).join('\n\n');
steps.forEach((s, i) => {
const stepEl = document.createElement('div');
stepEl.className = 'gb-step' + (i === 0 ? ' is-revealed' : '');
stepEl.id = 'step-' + s.key;
stepEl.innerHTML =
'<div class="gb-step-done-pill"><span>' + s.doneLabel + '</span><span class="chevron">\u25be view code</span></div>' +
'<div class="gb-step-code-full">' +
'<p class="gb-step-label">' + s.title + '</p>' +
'<div class="gb-code-scroll"><div class="gb-code">' + s.code + '</div></div>' +
(s.note ? '<p class="gb-step-note">' + s.note + '</p>' : '') +
'<div class="gb-run-row"><button class="gb-run-btn" id="btn-' + s.key + '" type="button">' + s.btnLabel + '</button></div>' +
'</div>';
stepsContainer.appendChild(stepEl);
});
function updateProgress(doneCount) {
progress.textContent = doneCount + ' / ' + steps.length + ' queries run';
}
updateProgress(0);
function renderResult(cols, rows, aggCols) {
let html = '<thead><tr>' + cols.map((c, i) => '<th' + (aggCols.includes(i) ? ' class="agg-col"' : '') + '>' + c + '</th>').join('') + '</tr></thead><tbody>';
rows.forEach(r => {
html += '<tr class="is-new">' + r.map((v, i) => '<td' + (aggCols.includes(i) ? ' class="is-aggcol"' : '') + '>' + v + '</td>').join('') + '</tr>';
});
html += '</tbody>';
table.innerHTML = html;
outputLabel.style.display = 'block';
tableScroll.style.display = 'block';
}
function showStatus(text) {
status.textContent = text;
status.classList.add('is-visible');
}
let doneCount = 0;
steps.forEach((s, i) => {
const btn = document.getElementById('btn-' + s.key);
btn.addEventListener('click', () => {
btn.disabled = true;
btn.textContent = 'Running...';
status.classList.remove('is-visible');
setTimeout(() => {
const rows = s.getRows();
renderResult(s.cols, rows, s.aggCols);
doneCount++;
updateProgress(doneCount);
showStatus(s.key === 'setup'
? '✔️ Table created, ' + rows.length + ' rows inserted.'
: '✔️ ' + rows.length + ' group' + (rows.length === 1 ? '' : 's') + ' returned.');
document.getElementById('step-' + s.key).classList.add('is-done');
const next = steps[i + 1];
if (next) {
const nextEl = document.getElementById('step-' + next.key);
nextEl.classList.add('is-revealed');
nextEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}, 350);
});
});
document.getElementById('gbShowFullBtn').addEventListener('click', function () {
const panel = document.getElementById('gbFullCodePanel');
const on = panel.classList.toggle('is-visible');
this.classList.toggle('is-on', on);
this.textContent = on ? '👁️ Hide full SQL script' : '👁️ Show full SQL script';
});
document.querySelectorAll('.gb-step-done-pill').forEach(pill => {
pill.addEventListener('click', () => {
const step = pill.closest('.gb-step');
step.classList.toggle('is-expanded-done');
pill.querySelector('.chevron').textContent = step.classList.contains('is-expanded-done') ? '\u25b4 hide code' : '\u25be view code';
});
});
document.getElementById('gbReset').addEventListener('click', () => {
doneCount = 0;
updateProgress(0);
status.classList.remove('is-visible');
outputLabel.style.display = 'none';
tableScroll.style.display = 'none';
table.innerHTML = '';
steps.forEach((s, i) => {
const el = document.getElementById('step-' + s.key);
el.classList.remove('is-done', 'is-expanded-done');
el.classList.toggle('is-revealed', i === 0);
const btn = document.getElementById('btn-' + s.key);
btn.disabled = false;
btn.textContent = s.btnLabel;
});
});
})();
</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!');
});📘 Summary
Clause | Works On |
|---|---|
GROUP BY | Groups rows |
COUNT() | Number of rows |
SUM() | Total value |
AVG() | Average value |
MAX() | Highest value |
MIN() | Lowest value |
WHERE | Filter BEFORE grouping |
HAVING | Filter AFTER grouping |