tsvsheet logo tsvsheet

SQLite

sqlite3 imports tab-separated values natively, so a rendered sheet is one pipe away from being a queryable table — and a query result is one formula row away from being a sheet again.

Everything here runs against grades.tsvt from tsvsheet.examples. Fetch it first:

curl -fsSO https://raw.githubusercontent.com/tsvsheet/tsvsheet.examples/main/grades.tsvt

Query a sheet

tsv render computes the grid and writes TSV; .mode tabs makes .import read exactly that. The formulas — Average and Result are computed cells — arrive as plain values:

$ tsv render grades.tsvt | sqlite3 :memory: '.mode tabs' '.import /dev/stdin grades' \
    'SELECT Result, count(*) AS n, round(avg(Average),1) AS avg FROM grades GROUP BY Result;'
Fail	1	61.7
Pass	3	83.0

sqlite3 can even run the pipeline itself — .import treats a |-prefixed source as a command to execute:

$ sqlite3 :memory: '.mode tabs' '.import "|tsv render grades.tsvt" grades' \
    'SELECT Student, Average FROM grades WHERE Average >= 80 ORDER BY Average DESC;'
Carol	91.3
Alice	84.3

Join sheets

Each sheet imports as its own table, so SQL joins work across sheets. Make a second sheet and join on Student:

$ printf 'Student\tClub\nAlice\tChess\nBob\tRobotics\nCarol\tChess\nDave\tDebate\n' > clubs.tsvt
$ sqlite3 :memory: '.mode tabs' \
    '.import "|tsv render grades.tsvt" grades' \
    '.import "|tsv render clubs.tsvt" clubs' \
    'SELECT c.Club, round(avg(g.Average),1) AS club_avg FROM grades g JOIN clubs c USING (Student) GROUP BY c.Club ORDER BY club_avg DESC;'
Chess	87.8
Robotics	73.3
Debate	61.7

From query result to living sheet

The reverse direction is just as short. sqlite3 -tabs -header emits a TSV grid — append a =formula row and the query result is a spreadsheet, ready to compute, edit, and diff:

$ sqlite3 grades.db '.mode tabs' '.import "|tsv render grades.tsvt" grades'
$ { sqlite3 -tabs -header grades.db 'SELECT Student, Average FROM grades ORDER BY Average DESC;'
    printf 'Class average\t=round(avg(B2:B5), 1)\n'; } > report.tsvt
$ tsv render report.tsvt
Student	Average
Carol	91.3
Alice	84.3
Bob	73.3
Dave	61.7
Class average	77.6

Try report.tsvt in the playground →

A database of sheets

A .tsvt is text, so it stores happily inside a database too. Keep a library of sheet sources in a table and pipe any of them back through the processor:

$ sqlite3 sheets.db 'CREATE TABLE IF NOT EXISTS sheets(name TEXT PRIMARY KEY, tsvt TEXT);'
$ sqlite3 sheets.db "INSERT OR REPLACE INTO sheets VALUES ('grades', readfile('grades.tsvt'));"
$ sqlite3 sheets.db "SELECT tsvt FROM sheets WHERE name='grades';" | tsv render -
Student	Math	Reading	Science	Average	Result
Alice	85	90	78	84.3	Pass
Bob	72	68	80	73.3	Pass
Carol	95	88	91	91.3	Pass
Dave	60	55	70	61.7	Fail

The source — formulas and all — lives in the database; the computation still happens wherever the pipe lands.