cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / docs / files / sources.md
sources.md 10.4K source

title: "Linked sources" sidebar_position: 8

Linked sources

A Parquet file, or a SQLite table or query, too big for any grid links as a source: a read-only table on a tab of its own, read in place rather than imported. The tab shows a window of the rows as it scrolls, with the row numbers and the scrollbar over every row, and formulas and pivot tables read the whole source by streaming it. A source of ten million rows opens at once and takes a few MB.

A source of ten million rows on its tab, sorted by amount, the scrollbar's thumb halfway down A source of ten million rows on its tab, sorted by amount, the scrollbar's thumb halfway down

Linking a source

Ten million rows of Parquet linked as a source, sorted by amount in the background, then read whole by formulas on Sheet1: a count with thousands separators, an AVERAGEIFS, the last day as a date and a MEDIAN

Data > Linked file > Link a source lists the Parquet files and SQLite databases in the folder, or takes a path. For a database a second list asks which table or view to read, or A query… to read what a SELECT returns.

The source gets a tab after the one shown, named after the file (sales for sales.parquet), with ▦ before its name. The source is a table of that name, which formulas on any sheet read as a table's:

Formula Reads
=SUM(sales[amount]) a column's values, every row
=ROWS(sales) the rows under the header
=COUNTA(nu.sales) the whole table, header row included
=sales!C5 one cell: row 1 is the header, so row 5 is the source's fourth row

Renaming the tab keeps the table's name. Undo takes the link back. A source's rows may run past the grid's last row, 1,048,576: its table's name reaches them all, where a reference typed as C5 stops at the grid's edge.

The tab

Row 1 is the header, the columns' names, and stays put as the rows scroll under it. The rows are numbered as the tab's rows, so the source's first row is row 2, and sales!C2 is its first amount. The scrollbar at the right edge is as long as the share of the rows shown, and as far down as they are.

Key or mouse Does
Arrows, Tab Move the active cell
Page Up, Page Down, the wheel Scroll
Home, End The first or last column
Ctrl+Up, Ctrl+Down The first or last row
Ctrl+Home, Ctrl+End The first cell, the last
A click on the scrollbar, or dragging its thumb Jumps there
Ctrl+G, F5 Goes to a row, or a cell such as C5000000
Ctrl+C Copies the active cell

The rows come from the file a page (128 rows) at a time, in the background, the pages either side of the window read ahead, so scrolling finds them there. A row on its way shows …. The name box shows the active cell as the tab names it, and the formula bar its value; the context line says what the source is: ▦ sales.parquet 10,000,000 rows, 4 columns. The mode is SOURCE, and typing, pasting or formatting there says the tab is read-only.

Sorting and filtering

The tab's rows sort and filter as a sheet's do, without changing the file:

  • Data > Sort sheet A to Z (or Z to A) sorts by the active column; blanks go last either way.
  • Data > Create a filter or Filter by column asks for a condition on the active column: the conditions of a sheet's filter. There is no list of values to tick: a source's are too many to list. Each column's condition applies at once.
  • Data > Remove filter drops the conditions, and Data > Linked file > Original order shows the source's own rows in its own order.

Each is an undo step and is saved with the workbook. Formulas read the source in its own order, every row, as formulas read the rows a sheet's filter hides.

Ordering reads the whole source once, in the background, while the context line says sorting and filtering…; the tab then shows the first of the rows. SQLite does the work: the sort and the conditions become SQL, the rows' order is kept in a temporary database beside the system's temporary files, and the database itself is never written. A Parquet file is streamed: a filter is one pass over the columns it tests, and a sort writes sorted runs to temporary files and merges them, so memory stays a few MB and up to about 100 MB while sorting, whatever the size. Either way a page anywhere in the ordered rows costs what one at the top does:

once, in the backgrounda page not reada sort or a filterthe rows' order: SQL into atemporary table, orstreamed into temporaryfilesthe tab scrollsthe pages around thewindowread 128 rows at theirpositionskept, the least used let gopast 48 pagesthe next frame drawsthemonce, in the backgrounda page not reada sort or a filterthe rows' order: SQL into atemporary table, orstreamed into temporaryfilesthe tab scrollsthe pages around thewindowread 128 rows at theirpositionskept, the least used let gopast 48 pagesthe next frame drawsthem

In SQLite a text condition compares the text as SQLite writes it, so a number reads as 1e+20 where the sheet shows 1E+20, a date held as text (2026-09-26) sorts and compares as that text, and letters beyond ASCII are compared by case.

Formulas over a source

Formulas on Sheet1 over two million trips: a count with thousands separators, sums of fares with their two decimals, the last day as a date, and the median fare, the pointer on it Formulas on Sheet1 over two million trips: a count with thousands separators, sums of fares with their two decimals, the last day as a date, and the median fare, the pointer on it

A function given a source's range is worked out in the background, by reading the source; its cell shows Loading… until then. The answer is kept until the file changes, so another formula asking the same, or the same formula recalculating, costs nothing.

These read a source of any size, holding only what they compute:

Kind Functions
Aggregates SUM, AVERAGE, COUNT, COUNTA, MIN, MAX, PRODUCT
Criteria COUNTIF, COUNTIFS, SUMIF, SUMIFS, AVERAGEIF, AVERAGEIFS, COUNTBLANK, SUMPRODUCT
Lookups MATCH, XLOOKUP, VLOOKUP, HLOOKUP, INDEX, ROWS, COLUMNS
Ranks RANK, RANK.EQ
Order statistics of one range MEDIAN, PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC, QUARTILE, QUARTILE.INC, QUARTILE.EXC, LARGE, SMALL, MODE, MODE.SNGL

The first four read the source once. An order statistic sorts the range's numbers on disk, beside the system's temporary files, and reads them back in order once: a MEDIAN of ten million rows takes about two seconds, 50 MB of memory and 160 MB of disk while it runs (Bounds of support). Its range is one argument, its others single values: MEDIAN(sales[amount]) and PERCENTILE(sales[amount],0.9) stream, where MEDIAN(sales[amount],sales[tax]) holds what it reads.

A result keeps what its column means, as a formula over a sheet takes its inputs' format: SUM, AVERAGE, MIN, MAX and the lookups show in the format the column's type gives it. In Parquet that's a DECIMAL with its scale's decimals, a DATE as a date, a TIMESTAMP as a date and time, a TIME as a time and an INTERVAL as a duration; in SQLite the declared type: DATE, DATETIME or TIMESTAMP, TIME, DECIMAL(10,2) or NUMERIC(10,2) with its decimals, and MONEY as currency. A count over a source (COUNT, COUNTA, COUNTIF, COUNTIFS, COUNTBLANK, ROWS, COLUMNS) shows as a whole number with the locale's thousands separators: 2,000,000. A format given to the cell wins over either.

Any other function (STDEV, SORT, FILTER, UNIQUE, TEXTJOIN) holds what it reads, and is given a source's range as long as that holds no more than max-cells cells (Configuration); past it the formula shows #VALUE!. So does an operator over a source's range (=SUM(sales[amount]*2)) and a function whose other arguments are ranges of a sheet or arrays: SUMPRODUCT((sales[cat]="north")*sales[amount]) computes an array as long as the source, where SUMIFS(sales[amount],sales[cat],"north") streams. The context line says how far to raise max-cells for it, and F1 the rest (Values and errors):

The fares' STDEV past max-cells: F1 shows its whole explanation in a box beside the cell, to raise max-cells to 2,000,000 or use a function that streams The fares' STDEV past max-cells: F1 shows its whole explanation in a box beside the cell, to raise max-cells to 2,000,000 or use a function that streams

Pivot tables

Data > Pivot table on a source's tab makes a pivot table over the whole source, and Data > Frequency table (column stats) one of the active column. The source's rows are grouped in the background, Loading… showing in the pivot table's first cell until they are, and again whenever the file changes. A filter of a pivot table over a source takes a condition, not values ticked.

When the file changes

012 looks at each source's file four times a second, with the operating system's file information alone. Once the file has changed and held still for 0.3 s, the source is read again: the tab, and every formula and pivot table reading it. Data > Linked file > Read the source again does the same at once. A file that isn't there shows ! after the tab's name and the reason on the context line, and the formulas reading it show #REF! with it.

Saving and sharing

The workbook keeps the link, never the rows: the file's path relative to the workbook's folder, the table or query, and the tab's order (The .012 format). Opening the workbook reads the sources again. A workbook from another computer asks before reading files outside its own folder, as for linked files.

012 get, recalc and export (Scripts) read the sources in the workbook's folder or below, to the end, before they answer. Under 012 serve sources are read in the served folder, and in a shared workbook the room reads them, as it follows linked files, while each participant scrolls a tab of their own.

What sources cost, measured on ten million rows, is in Bounds of support.