What's in the box
Everything ships as static files. The whole application — database engine, data, UI, and AI wiring — is a single HTML file.
| File | What it is |
|---|---|
| sql-workbench.html | The app — database, data, UI, AI, all in one file. |
| help.html | Architecture, workflow, and a line-by-line code map. |
| index.html | This page — the tutorial. |
| Script_Create_Tables.sql.txt | The 5-table schema. |
| *.csv | The raw HR dataset (headerless), embedded into the page. |
The dataset — 5 tables, 36 rows
| Table | Rows | Key columns |
|---|---|---|
| EMPLOYEES | 10 | EMP_ID, F_NAME, L_NAME, SALARY, JOB_ID, DEP_ID |
| JOBS | 10 | JOB_IDENT, JOB_TITLE, MIN_SALARY, MAX_SALARY |
| JOB_HISTORY | 10 | EMPL_ID, START_DATE, JOBS_ID, DEPT_ID |
| DEPARTMENTS | 3 | DEPT_ID_DEP, DEP_NAME, MANAGER_ID, LOC_ID |
| LOCATIONS | 3 | LOCT_ID, DEP_ID_LOC |
Quickstart
Just want SQL? Open sql-workbench.html in a browser — the editor and database work immediately, offline. Want the AI too? Point it at your local Ollama:
# Option A — allow the browser to reach Ollama, then open the file directly OLLAMA_ORIGINS='*' ollama serve # Option B — serve the page, then browse to it python3 -m http.server 8000 # → http://localhost:8000/sql-workbench.html # check the model name matches one you have installed: ollama list
Set the model in the top bar (default deepseek-v4.1-flash:cloud).
Part 1 — SQL, from zero
Run each of these in the Write SQL tab. Type it, press Ctrl+Enter, read the table that comes back. Every result below is real output from the dataset.
SELECT * FROM EMPLOYEES;
SELECT picks columns, * means all of them, FROM names the table. 10 rows come back.
SELECT F_NAME, L_NAME, SALARY FROM EMPLOYEES;
SELECT F_NAME, L_NAME, SALARY FROM EMPLOYEES WHERE SALARY > 70000;
Comparisons: =, >, <, >=, <=, <>. Combine with AND / OR.
SELECT F_NAME || ' ' || L_NAME AS name, SALARY FROM EMPLOYEES ORDER BY SALARY DESC LIMIT 5;
|| joins text in SQLite; AS names the result column; ORDER BY ... DESC sorts high→low.
| name | SALARY |
|---|---|
| John Thomas | 100000 |
| Nancy Allen | 90000 |
| Alice James | 80000 |
| Ahmed Hussain | 70000 |
| Andrea Jones | 70000 |
SELECT COUNT(*) AS employees, ROUND(AVG(SALARY), 0) AS avg_salary, MIN(SALARY) AS lowest, MAX(SALARY) AS highest FROM EMPLOYEES;
An aggregate collapses many rows into one number.
SELECT SEX, COUNT(*) AS n FROM EMPLOYEES GROUP BY SEX;
| SEX | n |
|---|---|
| F | 5 |
| M | 5 |
GROUP BY splits rows into buckets; the aggregate runs per bucket.
SELECT d.DEP_NAME, COUNT(*) AS people, ROUND(AVG(e.SALARY), 0) AS avg_salary FROM EMPLOYEES e JOIN DEPARTMENTS d ON e.DEP_ID = d.DEPT_ID_DEP GROUP BY d.DEP_NAME ORDER BY avg_salary DESC;
The heart of relational SQL. e and d are aliases; JOIN ... ON states the matching rule.
| DEP_NAME | people | avg_salary |
|---|---|---|
| Architect Group | 3 | 86667.0 |
| Design Team | 3 | 66667.0 |
| Software Group | 4 | 65000.0 |
SELECT e.F_NAME || ' ' || e.L_NAME AS name, j.JOB_TITLE, e.SALARY FROM EMPLOYEES e JOIN JOBS j ON e.JOB_ID = j.JOB_IDENT WHERE e.SALARY > 70000 ORDER BY e.SALARY DESC;
| name | JOB_TITLE | SALARY |
|---|---|---|
| John Thomas | Sr. Architect | 100000 |
| Nancy Allen | Lead Architect | 90000 |
| Alice James | Sr. Software Developer | 80000 |
The join map
EMPLOYEES.DEP_ID ──┐
├─► DEPARTMENTS.DEPT_ID_DEP
JOB_HISTORY.DEPT_ID ─┘
EMPLOYEES.JOB_ID ──► JOBS.JOB_IDENT
JOB_HISTORY.JOBS_ID ──► JOBS.JOB_IDENT
JOB_HISTORY.EMPL_ID ──► EMPLOYEES.EMP_ID
DEPARTMENTS.LOC_ID ──► LOCATIONS.LOCT_ID
Part 2 — How an LLM writes SQL
The Ask tab takes “who earns the most, with their department?” and produces the query you'd have written by hand. The mechanism is simpler than it looks.
The pipeline
The four ingredients of a good NL→SQL prompt
- Schema — the actual
CREATE TABLEstatements. The model must see real names; guessing produces wrong identifiers. - Glossary — the semantics the schema can't express. “Full name is
F_NAME || ' ' || L_NAME”, “joinDEP_IDtoDEPT_ID_DEP”. This is what makes joins work. - Rules — constrain output: “Output ONLY the SQL. One SELECT. No DDL/DML.”
- Examples — two or three question→SQL pairs. Few-shot examples teach shape better than instructions alone.
Guardrails
Generated SQL is untrusted input. The app accepts only statements starting with SELECT / WITH / EXPLAIN and containing no INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, REPLACE, TRUNCATE, or ATTACH. Anything else is shown but not run. And the database is in-memory — reloading resets it — so a mistake can't touch your files.
Failure modes and fixes
| Symptom | Cause | Fix |
|---|---|---|
| no such column | Model guessed a name | Tighten the schema block; list columns explicitly |
| wrong / missing join | Join keys not stated | Add the join to the glossary |
| prose not SQL | Prompt not strict enough | “Output ONLY the SQL” + few-shot |
| two statements | Ambiguous question | Ask narrower; keep one SELECT |
| odd result | Model hallucinated rows | Set temperature: 0 |
Try these in the Ask tab
- How many employees are there?
- List the top 3 salaries with full names.
- Which department has the highest average salary?
- Show female employees earning more than 70,000.
- What is the pay range for a Sr. Architect?
Watch the generated SQL appear above each answer — that's the model showing its work.
Part 3 — Exercises
Try each in the Write SQL tab. Every answer is a variation of Part 1.
- List every employee's full name and salary, cheapest first.
- How many people work in the Software Group?
- Show the highest-paid person in each department.
- Which job titles pay more than 90,000 at the top?
- Find employees hired before 2001 (hint:
JOB_HISTORY.START_DATEisYYYY-MM-DDtext — string comparison sorts correctly). - Count how many distinct departments appear in
JOB_HISTORY.
Architecture
Two paths, one engine. Direct SQL reaches SQLite straight. Ask goes through the model first, then the same engine. If Ollama is down, Direct mode still works. Full flow diagrams and a line-by-line code map are in help.html.
Deploy
Static site — GitHub Pages serves it with no build step. Settings → Pages → Source: Deploy from a branch → main / / (root).
github.io can't reach your local Ollama. Pages hosts the tutorial and the SQL editor; open the file locally for the full English→SQL experience.