Every time I get a new data file, I do the same three things.
Open it in Excel to get a feel for it. Write a quick pandas snippet to answer one question. Notice halfway through that a column has garbage values I wasn't expecting.
The notebook is already open, the virtual environment is already running, and I've already spent ten minutes on something that should have taken two.
I wanted something faster. Type a command, load a file, write SQL, done. No notebook, no imports, no setup.
So I built duckboard.
What it does
duckboard is a file-first local SQL workspace powered by DuckDB. Load CSV, PSV, Parquet, or JSON files by name, query them with plain SQL, export results — all from the terminal.
pip install duckboard
Start a session:
duckboard
Load a file and query it immediately:
duckboard> :load sales.csv as sales
duckboard> SELECT region, SUM(amount) FROM sales GROUP BY 1;
┌────────┬─────────────┐
│ region │ sum(amount) │
├────────┼─────────────┤
│ eu │ 500.25 │
│ us │ 430.50 │
└────────┴─────────────┘
(2 rows)
duckboard> :save results.csv
Saved 2 rows to results.csv
Files are registered as DuckDB views — never copied into a database. Load once, query as many times as you want. Parquet and JSON work exactly the same way — :load data.parquet as t just works.
The part I didn't plan: automatic validation
While testing duckboard, I loaded a real file from work. One of the rows had a number in the gender column — clearly a data entry error. DuckDB loaded it fine. I only noticed because I happened to run a GROUP BY on that column.
That made me think: what if duckboard flagged this automatically?
So I added validation on load. When you load a CSV file, duckboard now scans the first 1,000 rows and checks for two things:
Structural errors — rows whose field count doesn't match the header. A trailing comma, a missing value, a malformed export.
Type anomalies — rows where a numeric value appears in a predominantly string column. A number in a name field. A code in a date field.
Both types get stored in a _errors_{name} table for the session:
duckboard> :load customers.csv as customers
Loaded 'customers' from customers.csv (csv)
1 validation error(s) found. Run 'SELECT * FROM _errors_customers' to inspect.
duckboard> SELECT * FROM _errors_customers;
┌────────────┬────────────────┬──────────────┬─────────────┬──────────────────────────────────────────┐
│ row_number │ raw_line │ error_type │ column_name │ reason │
├────────────┼────────────────┼──────────────┼─────────────┼──────────────────────────────────────────┤
│ 5 │ NULL │ type_anomaly │ gender │ column gender: expected non-numeric, │
│ │ │ │ │ got '42' │
└────────────┴────────────────┴──────────────┴─────────────┴──────────────────────────────────────────┘
The :tables command shows which tables have issues at a glance:
duckboard> :tables
┌───────────┬────────┬──────────────┬────────┐
│ name │ format │ path │ errors │
├───────────┼────────┼──────────────┼────────┤
│ customers │ csv │ customers.csv│ [!1] │
│ orders │ csv │ orders.csv │ ok │
└───────────┴────────┴──────────────┴────────┘
Once you've reviewed the errors, you can export just the clean rows:
duckboard> :export_clean customers clean_customers.csv
duckboard> :export_errors customers bad_rows.csv
A few other commands worth knowing
No-header files — some exports don't include a header row. Pass --no-header and duckboard will prompt you for column names or auto-generate them:
duckboard> :load dump.csv as dump --no-header
Enter column names (comma-separated) or press Enter for auto [col1, col2, col3]:
Rename a column without reloading the file:
duckboard> :rename_column sales cust_id customer_id
Multi-line SQL works naturally — statements execute on semicolon:
duckboard> SELECT region,
-> COUNT(*) AS n,
-> SUM(amount) AS total
-> FROM sales
-> GROUP BY 1;
Why not just use DuckDB directly?
You can — DuckDB has its own CLI. duckboard wraps it with a workflow built around files: named tables, validation on load, save/export commands, and a session that remembers what you've loaded. It's the same engine with less ceremony for the file-first use case.
Where to find it
It's at v0.2.0 — 67 tests, available now. If you work with structured files — CSV, Parquet, PSV, or JSON — and want something faster than spinning up a notebook, give it a try. Issues and feedback welcome.
Top comments (0)