Spreadsheets with SQL
Query the cells of every spreadsheet with one SQL statement and fill reports without the data passing through the agent.
Every uploaded .xlsx, .xlsm or .csv spreadsheet becomes facts the agent queries with SQL. A question about ten spreadsheets fits in one sql call instead of ten file reads. sql and sql_schema run inside query scripts; sql_write and sheet_write run inside execute scripts (MCP).
When the facts are ready
Every file has a status, which get, files_list, file_read and document_search report; one that is not ready says what to do in statusMessage:
| Status | Meaning |
|---|---|
processing |
The file is still being read. |
ready |
Its cells or text can be queried. |
too_large |
The file exceeds the plan’s per-spreadsheet cell or size limit. It stays downloadable; split it into smaller parts and upload them again, or change plans. |
limit_reached |
The organization’s processed data reached the plan’s limit. The file stays stored and is read on its own once there is room: after a plan change, or deleting files or SQL tables. |
failed |
The file could not be read. |
stored |
A type Siglata keeps but does not read, such as a video. |
Siglata stores deterministic facts only: it never recalculates formulas and never guesses versions or locales. Interpretation is up to the agent.
Tables and functions
Call sql_schema for each table’s columns, the functions and the objects you can read.
| Table | Contents |
|---|---|
sheet_files |
One row per spreadsheet, with the state of its facts. |
cells |
One row per cell: file, sheet, address, row, column, raw text, typed value, formula and number format. |
styles |
Each style’s font, fill, border and alignment. |
sheets |
Sheets and their used ranges. |
excel_tables |
Excel Tables. |
defined_names |
Defined names. |
merges |
Merged cells. |
| Function | Use |
|---|---|
sheet_rows(file, sheet) |
A sheet’s wide rows, with columns by letter (A, B, C…). |
parse_number(text, locale) |
Turns text such as 1.234,56 (pt-BR) or 1,234.56 (en-US) into a number. |
parse_date(text, format) |
Turns text into a date with a pattern such as DD/MM/YYYY. |
column_letters(col) |
Turns a column number into letters. |
CSV values stay text; use parse_number and parse_date to interpret them.
Query
sql (files:read) runs one SELECT in Postgres syntax, in a query script:
return await sql({
query: `SELECT raw AS filial, count(*) AS celulas
FROM cells
WHERE sheet = 'Vendas' AND col = 2 AND row >= 2
GROUP BY raw`,
});
- It returns the rows that fit in 100,000 bytes of JSON, with no row limit, and runs for at most 10 s. When something is left out, the result carries
truncated: trueandnext, which says how to page withORDER BYandLIMIT/OFFSETor narrow theSELECT. - Name tables without a schema. Only the listed functions and types are accepted; system catalogs and statements that change data are refused.
- Before a statement runs, Siglata bounds the database memory it could take at worst, from the longest values the tables it reads hold and a size rule for each function, operator and cast, through every CTE, subquery and view. A statement over 128 MiB is refused with
too_large, before it runs, inSELECT,CREATE VIEWandCREATE TABLE AS: build smaller values (fewer nestedconcat,replace, casts or JSON and array constructors), select fewer columns, or split the work.WITH RECURSIVEis refused (usegenerate_series), and a regular-expression orSIMILAR TOpattern must be a string constant of at most 2,000 compiled states. At most 4 statements run at once; a statement that finds them all busy fails withconflict, so retry it. - Every refusal has a code, such as
relation_not_allowed,function_not_allowed,timeoutorconflict(retry the call).
Views and snapshots
sql_write (files:write) creates and drops your own objects, in an execute script:
| Statement | Result |
|---|---|
CREATE VIEW name AS SELECT … |
A view shared with the organization. Each member reads it with their own access. |
CREATE TABLE name AS SELECT … |
A snapshot only you read, over sources (the ids of the files it may read; every file you can read when omitted). It counts toward the plan’s limit for spreadsheets the agent queries. |
DROP VIEW name or DROP TABLE name |
Drops an object you created. |
return await sql_write({
query:
"CREATE TABLE vendas_agosto AS SELECT raw FROM cells WHERE sheet = 'Agosto'",
sources: ["<spreadsheet id>"],
});
A snapshot follows its source files: it is hidden while a source is in the trash, returns when the source is restored, and is dropped when the source is purged.
Fill a report
sheet_write, in an execute script, accepts a query patch { sheet, anchor, query } (it also needs files:read). It runs the SELECT and writes every row from the anchor cell of a new edition of the template, keeping its formatting:
return await sheet_write({
fileId: "<template id>",
requestId: "vendas-setembro-2026",
patches: [
{
sheet: "Relatório",
anchor: "A2",
query: "SELECT filial, receita FROM vendas_mes",
},
],
});
The rows never pass through the agent’s context; the response carries only the new file and the written range. A result over 5,000 rows, 8 MiB or 1,000,000 cells fails with too_large and creates no file.
Permissions
SQL sees only the cells of active files the connection’s user can read. A restricted file needs a grant; another organization never appears. Trashed files stay hidden until they are restored.