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.0sqlite3 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.3Join 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
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.