DuckDB
DuckDB queries tab-separated files in place — no schema, no import step. A rendered sheet is a table the moment the pipe connects, with column types sniffed for free.
Everything here runs against grades.tsvt from tsvsheet.examples:
curl -fsSO https://raw.githubusercontent.com/tsvsheet/tsvsheet.examples/main/grades.tsvt
One pipe, no import
read_csv consumes the rendered grid straight from stdin — and notice the types: Average was a formula cell a moment ago, now it’s a sniffed double:
$ tsv render grades.tsvt | duckdb -c \
"SELECT Result, count(*) AS n, round(avg(Average),1) AS avg FROM read_csv('/dev/stdin', delim='\t', header=true) GROUP BY Result;"
┌─────────┬───────┬────────┐
│ Result │ n │ avg │
│ varchar │ int64 │ double │
├─────────┼───────┼────────┤
│ Fail │ 1 │ 61.7 │
│ Pass │ 3 │ 83.0 │
└─────────┴───────┴────────┘Query rendered files directly
Render sheets to .tsv files and DuckDB treats each file as a table — delimiter, header, and types all sniffed from the extension and content. Joins included:
$ tsv render grades.tsvt > grades.tsv $ printf 'Student\tClub\nAlice\tChess\nBob\tRobotics\nCarol\tChess\nDave\tDebate\n' > clubs.tsvt $ tsv render clubs.tsvt > clubs.tsv $ duckdb -c "SELECT c.Club, round(avg(g.Average),1) AS club_avg FROM 'grades.tsv' g JOIN 'clubs.tsv' c USING (Student) GROUP BY c.Club ORDER BY club_avg DESC;" ┌──────────┬──────────┐ │ Club │ club_avg │ │ varchar │ double │ ├──────────┼──────────┤ │ Chess │ 87.8 │ │ Robotics │ 73.3 │ │ Debate │ 61.7 │ └──────────┴──────────┘
And back to a sheet
COPY ... TO emits TSV, so a query result flows back into the format it came from — append a =formula row and it computes:
$ { duckdb -c "COPY (SELECT Student, Average FROM 'grades.tsv' ORDER BY Average DESC) TO '/dev/stdout' (FORMAT csv, DELIMITER '\t', HEADER);"
printf 'Class average\t=round(avg(B2:B5), 1)\n'; } | tsv render -
Student Average
Carol 91.3
Alice 84.3
Bob 73.3
Dave 61.7
Class average 77.6Sheet → SQL → sheet, and every intermediate stage is diffable text.