Try it. The query joins four tables â Users, Orders, Products, Reviews â connected in a chain. Click them in the order you'd join them. The demo estimates the intermediate rows at each step and the total cost.
<p class="hint">Join four tables (a chain: UsersâOrdersâProductsâReviews). Click them in the order you'd join. The demo estimates intermediate rows per step and the total <b>cost</b>. Smart orders stay tiny; bad ones blow up.</p>
<div id="tables" class="tables"></div>
<div class="track-wrap"><span class="lab">Join order:</span><div id="track" class="track"></div></div>
<div id="steps" class="steps"></div>
<div class="meter">Total cost: <b id="cost" class="cost">â</b><span id="cmp" class="cmp"></span></div>
<div class="btns">
<button id="best" type="button">Best plan</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.tables { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .9rem; }
.tbl { border: 2px solid #457b9d; border-radius: 8px; padding: .45rem .7rem; cursor: pointer; font: 700 13px system-ui; color: #1d3557; background: #fff; text-align: center; }
.tbl small { display: block; font-weight: 600; color: #888; font-size: .82em; }
.tbl.used { opacity: .35; cursor: default; }
.track-wrap { display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
.lab { font: 600 13px system-ui; color: #555; }
.track { display: flex; gap: .3rem; flex-wrap: wrap; min-height: 2rem; }
.chip { font: 800 13px ui-monospace, monospace; background: #2a9d8f; color: #fff; border-radius: 6px; padding: .35rem .55rem; cursor: pointer; }
.steps { font: 600 12.5px ui-monospace, monospace; color: #444; line-height: 1.7; margin-bottom: .6rem; min-height: 1.5em; }
.meter { font-size: 1.05rem; margin-bottom: .6rem; }
.cost { color: #1d3557; font-family: ui-monospace, monospace; }
.cmp { margin-left: .6rem; font: 700 .9rem system-ui; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .5rem 1rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// SQL join-order optimization (NP-hard). Cost = sum of intermediate row counts
// of a left-deep plan. Join order can swing the cost by ~1000x; Best plan
// brute-forces the cheapest of the 4! orders.
const T = { Users: 1000, Orders: 5000, Products: 200, Reviews: 8000 };
const NAMES = ['Users', 'Orders', 'Products', 'Reviews'];
const EDGES = { 'Orders,Users': 0.001, 'Orders,Products': 0.005, 'Products,Reviews': 0.002 };
function sel(t, set) { let s = 1; for (const u of set) { const k = [t, u].sort().join(','); if (EDGES[k] !== undefined) s *= EDGES[k]; } return s; }
function plan(order) { let cur = T[order[0]], set = [order[0]], total = 0, steps = []; for (let i = 1; i < order.length; i++) { const t = order[i]; const sl = sel(t, set); cur = Math.max(1, Math.round(cur * T[t] * sl)); total += cur; steps.push({ join: order.slice(0, i + 1), rows: cur, cartesian: sl === 1 }); set.push(t); } return { total, steps }; }
function perms(a) { if (a.length <= 1) return [a]; const r = []; a.forEach((v, i) => perms(a.slice(0, i).concat(a.slice(i + 1))).forEach(p => r.push([v, ...p]))); return r; }
let order = [];
let BEST = Infinity, WORST = 0, BESTORDER = null;
for (const p of perms(NAMES)) { const c = plan(p).total; if (c < BEST) { BEST = c; BESTORDER = p; } if (c > WORST) WORST = c; }
const fmt = n => n.toLocaleString('en-US');
function render() {
document.getElementById('tables').innerHTML = NAMES.map(n =>
i18nFmt('<div class="tbl%v%" data-n="%n%">%n%<small>%fmt% rows</small></div>', { v: order.includes(n) ? ' used' : '', n: n, fmt: fmt(T[n]) })).join('');
document.querySelectorAll('.tbl').forEach(el => el.addEventListener('click', () => { const n = el.dataset.n; if (!order.includes(n)) { order.push(n); render(); } }));
document.getElementById('track').innerHTML = order.map((n, i) => '<span class="chip" data-i="' + i + '">' + n + '</span>').join('') || '<span style="color:#aaa;font-size:.85rem">pick tablesâŠ</span>';
document.querySelectorAll('.chip').forEach(el => el.addEventListener('click', () => { order.splice(+el.dataset.i); render(); }));
const stepsEl = document.getElementById('steps');
if (order.length >= 2) {
const pl = plan(order);
stepsEl.innerHTML = pl.steps.map(s => i18nFmt('â %s% â <b>%fmt%</b> rows%v%', { s: s.join.join(' â '), fmt: fmt(s.rows), v: s.cartesian ? ' <span style="color:#c0392b">â cartesian!</span>' : '' })).join('<br>');
document.getElementById('cost').textContent = fmt(pl.total);
const cmp = document.getElementById('cmp');
if (order.length === NAMES.length) {
if (pl.total <= BEST) { cmp.style.color = '#0a7d33'; cmp.textContent = i18nFmt('â optimal! (worst order â %fmt%)', { fmt: fmt(WORST) }); }
else { cmp.style.color = '#b9770e'; cmp.textContent = i18nFmt('%round%Ă the best plan (%fmt%)', { round: Math.round(pl.total / BEST), fmt: fmt(BEST) }); }
} else cmp.textContent = '';
} else { stepsEl.innerHTML = ''; document.getElementById('cost').textContent = 'â'; document.getElementById('cmp').textContent = ''; }
}
document.getElementById('best').addEventListener('click', () => { order = BESTORDER.slice(); render(); });
document.getElementById('reset').addEventListener('click', () => { order = []; render(); });
render();
<p class="hint">Join four tables (a chain: UsersâOrdersâProductsâReviews). Click them in the order you'd join. The demo estimates intermediate rows per step and the total <b>cost</b>. Smart orders stay tiny; bad ones blow up.</p>
<div id="tables" class="tables"></div>
<div class="track-wrap"><span class="lab">Join order:</span><div id="track" class="track"></div></div>
<div id="steps" class="steps"></div>
<div class="meter">Total cost: <b id="cost" class="cost">â</b><span id="cmp" class="cmp"></span></div>
<div class="btns">
<button id="best" type="button">Best plan</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.tables { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .9rem; }
.tbl { border: 2px solid #457b9d; border-radius: 8px; padding: .45rem .7rem; cursor: pointer; font: 700 13px system-ui; color: #1d3557; background: #fff; text-align: center; }
.tbl small { display: block; font-weight: 600; color: #888; font-size: .82em; }
.tbl.used { opacity: .35; cursor: default; }
.track-wrap { display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
.lab { font: 600 13px system-ui; color: #555; }
.track { display: flex; gap: .3rem; flex-wrap: wrap; min-height: 2rem; }
.chip { font: 800 13px ui-monospace, monospace; background: #2a9d8f; color: #fff; border-radius: 6px; padding: .35rem .55rem; cursor: pointer; }
.steps { font: 600 12.5px ui-monospace, monospace; color: #444; line-height: 1.7; margin-bottom: .6rem; min-height: 1.5em; }
.meter { font-size: 1.05rem; margin-bottom: .6rem; }
.cost { color: #1d3557; font-family: ui-monospace, monospace; }
.cmp { margin-left: .6rem; font: 700 .9rem system-ui; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .5rem 1rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// SQL join-order optimization (NP-hard). Cost = sum of intermediate row counts
// of a left-deep plan. Join order can swing the cost by ~1000x; Best plan
// brute-forces the cheapest of the 4! orders.
const T = { Users: 1000, Orders: 5000, Products: 200, Reviews: 8000 };
const NAMES = ['Users', 'Orders', 'Products', 'Reviews'];
const EDGES = { 'Orders,Users': 0.001, 'Orders,Products': 0.005, 'Products,Reviews': 0.002 };
function sel(t, set) { let s = 1; for (const u of set) { const k = [t, u].sort().join(','); if (EDGES[k] !== undefined) s *= EDGES[k]; } return s; }
function plan(order) { let cur = T[order[0]], set = [order[0]], total = 0, steps = []; for (let i = 1; i < order.length; i++) { const t = order[i]; const sl = sel(t, set); cur = Math.max(1, Math.round(cur * T[t] * sl)); total += cur; steps.push({ join: order.slice(0, i + 1), rows: cur, cartesian: sl === 1 }); set.push(t); } return { total, steps }; }
function perms(a) { if (a.length <= 1) return [a]; const r = []; a.forEach((v, i) => perms(a.slice(0, i).concat(a.slice(i + 1))).forEach(p => r.push([v, ...p]))); return r; }
let order = [];
let BEST = Infinity, WORST = 0, BESTORDER = null;
for (const p of perms(NAMES)) { const c = plan(p).total; if (c < BEST) { BEST = c; BESTORDER = p; } if (c > WORST) WORST = c; }
const fmt = n => n.toLocaleString('en-US');
function render() {
document.getElementById('tables').innerHTML = NAMES.map(n =>
i18nFmt('<div class="tbl%v%" data-n="%n%">%n%<small>%fmt% rows</small></div>', { v: order.includes(n) ? ' used' : '', n: n, fmt: fmt(T[n]) })).join('');
document.querySelectorAll('.tbl').forEach(el => el.addEventListener('click', () => { const n = el.dataset.n; if (!order.includes(n)) { order.push(n); render(); } }));
document.getElementById('track').innerHTML = order.map((n, i) => '<span class="chip" data-i="' + i + '">' + n + '</span>').join('') || '<span style="color:#aaa;font-size:.85rem">pick tablesâŠ</span>';
document.querySelectorAll('.chip').forEach(el => el.addEventListener('click', () => { order.splice(+el.dataset.i); render(); }));
const stepsEl = document.getElementById('steps');
if (order.length >= 2) {
const pl = plan(order);
stepsEl.innerHTML = pl.steps.map(s => i18nFmt('â %s% â <b>%fmt%</b> rows%v%', { s: s.join.join(' â '), fmt: fmt(s.rows), v: s.cartesian ? ' <span style="color:#c0392b">â cartesian!</span>' : '' })).join('<br>');
document.getElementById('cost').textContent = fmt(pl.total);
const cmp = document.getElementById('cmp');
if (order.length === NAMES.length) {
if (pl.total <= BEST) { cmp.style.color = '#0a7d33'; cmp.textContent = i18nFmt('â optimal! (worst order â %fmt%)', { fmt: fmt(WORST) }); }
else { cmp.style.color = '#b9770e'; cmp.textContent = i18nFmt('%round%Ă the best plan (%fmt%)', { round: Math.round(pl.total / BEST), fmt: fmt(BEST) }); }
} else cmp.textContent = '';
} else { stepsEl.innerHTML = ''; document.getElementById('cost').textContent = 'â'; document.getElementById('cmp').textContent = ''; }
}
document.getElementById('best').addEventListener('click', () => { order = BESTORDER.slice(); render(); });
document.getElementById('reset').addEventListener('click', () => { order = []; render(); });
render();
Watch the cost swing enormously: join in a smart order and the intermediate results stay tiny; join in a bad order (or two unrelated tables first) and you get a giant cartesian blow-up. Hit Best plan to see the optimum â often hundreds of times cheaper than the worst. That gap is exactly what the optimizer exists to avoid.
Comments
Loading comments...