Duskel Start a project
Blog/AI & RAG

How to build a RAG system on Excel files without shredding the numbers

Chunking a spreadsheet destroys the only thing that made it useful: the schema. Here is the pipeline that answers numeric questions exactly, with the SQL as the citation and a cell range behind every claim.

Duskel·6 Aug 2026·6 min read·AI & RAG

The screenshot arrived on a Tuesday. The assistant had answered "Q3 Midwest gross margin was 34.1%." The board deck had said 31.4% for five weeks. Nothing hallucinated. The retriever pulled five rows out of a 3,000-row transactions tab, two of them subtotal rows, and the model averaged what it was handed. That is the standard Excel RAG recipe — split, embed, top-k, answer — working exactly as designed.

A spreadsheet is a database that lost its schema: the types live in the number format, the joins live in someone's head, and the grain changes three tabs in. Across the Excel projects we have shipped, roughly seven of every ten questions users actually asked were aggregations — sums, filters, period-over-period deltas. Cosine similarity cannot add. So the architecture is a router in front of two unrelated retrieval paths, and about 80% of the build is the ingestion step nobody puts in the demo video.

Chunking a sheet destroys the sheet

Push a 3,000-row transactions tab through a recursive character splitter at 512 tokens and you get roughly 190 chunks. The header row appears in exactly one of them, so chunk 137 is a wall of unlabeled values. Merged cells that visually spanned six rows collapse into one populated cell and five empty ones, and the region column comes out blank for most of the file. The cell that displays 42% is stored as 0.4231; the cell that displays $1.2M is stored as 1204887.33. The model reads the raw value, the analyst remembers the formatted one, and the two disagree in the answer with no error thrown anywhere. Subtotal rows get retrieved alongside the rows they summarize, and the model cheerfully counts both.

Then there is the near-duplicate problem. When every row is a date, a region, a SKU, and a number, pairwise cosine similarity between arbitrary rows sits around 0.97 — inside the noise floor of any embedding model. Top-k over near-duplicates is a lottery, and the model has no signal that it received five arbitrary rows instead of the five that mattered. This is why the demo on a 30-row sample passes review and the 40,000-row workbook falls apart: the failure scales with row count, and the sample never had enough rows to expose it.

Route the question before you retrieve anything

Questions against spreadsheets come in three classes. Lookups want one row or cell — "what was the invoice total for Acme in March." Aggregations want computation — "which three regions grew fastest year over year." Semantic questions want prose that happens to live in a workbook — "what did the notes say about the pricing exception policy." One pipeline cannot serve all three. Forcing vector search to cover aggregation is where these projects die, usually four months in, after someone adds a re-ranker, then raises k to 50, then buys a bigger context window, and the numbers stay wrong.

A cheap classifier is enough: a dozen labeled examples per class clears the high nineties, because the classes differ in surface form — aggregations carry superlatives, date ranges, and grouping words. The case to watch is the aggregation smuggled inside a lookup: "what was our best region in March" reads like a lookup and needs a GROUP BY. Lookups and aggregations go to SQL, semantic questions go to vectors. Below the confidence threshold, run both paths and label the output — this number came from a query, this sentence came from retrieved text — because those two have nothing like the same reliability.

Turn workbooks into tables, then query them

Ingestion is the real work. Detect the header band per tab instead of assuming row one; finance sheets routinely open with a title, a blank row, and a two-level header. Forward-fill merged cells so every row stands alone. Unpivot wide layouts into long format, because nobody writes sane SQL against forty columns named Jan-24 through Apr-27. Keep the display format beside the raw value. Tag rows whose formula is a SUBTOTAL or a SUM over the block above, and exclude them by default. Read cached formula results, not formula strings — and check for the trap where a workbook generated by another tool and never opened in Excel has no cached values at all, so every formula cell loads as null. Then land the result in DuckDB or Postgres and let it be a table.

What the model sees is not the table, it is the data dictionary: per column, the name, inferred type, units, cardinality, five sample values, and min/max for numerics. Forty tabs of that is roughly 12k tokens; three million cells is not a prompt. Generate SQL against the dictionary, execute it as a read-only role with a row limit and a five-second statement timeout, and return the query with the answer. The query is the citation. A finance lead who reads SQL will argue with your join, which is a fixable bug report, instead of filing "the AI is hallucinating."

What actually belongs in the vector store

Embeddings still matter, just not for rows. Embed the data dictionaries, tab names, README and assumption tabs, comment threads, and genuinely free-text columns like ticket notes or product descriptions. Retrieval then happens at table level: find the two or three tabs that could answer this, hand their schemas to the SQL step, ignore the other 397 workbooks. Tabs are semantically distinct from one another in a way individual rows never are, so this retrieval step is far more forgiving.

Where you do embed rows, write them as sentences with column names inlined. "Region: Midwest. Quarter: Q3 2026. Gross margin: 31.4%" retrieves; "Midwest,Q3 2026,0.314" does not. Carry a stable row identifier so any chunk traces back to a precise address — Budget_FY26.xlsx, tab Actuals, B412:H412 — and key the index on file hash plus sheet name, so when someone uploads v7 of the budget you replace v6's vectors instead of retrieving both and averaging two versions of the truth.

How you know it works

Collect sixty real questions from the people who will use it, with answers computed by hand, before you write a line of the pipeline. Then score three things separately: did the router pick the right path, did retrieval surface the right table in its top three, and was the final number exactly right. A blended accuracy score tells you 71% and hides which of the three stages is broken. Numeric answers are graded on exact match — 31.4 and 34.1 are not partially correct, and any eval that scores them as close is lying to you.

The last piece is refusal. When router confidence is low, when the SQL returns zero rows, or when no retrieved table has the requested column, say what was missing and name the workbook you searched. Spreadsheet people are excellent at spotting a wrong number and remarkably patient with "I don't have that column." Assert the header band and column types at load time, so when someone inserts a column in front of C the ingest fails loudly that morning — rather than silently shifting every downstream query and surfacing as a bad margin figure six weeks later.

Written by Duskel

A software studio that ships and maintains its own products — KeepChats, Gwora and MoveProof — and builds the same way for clients. Founded and led by codewithumar.

Talk to the studio →
RELATED READING
AI & RAG · 6 min read

How to build a multimodal RAG system that finds the chart

AI & RAG · 8 min read

How to build a graph RAG system (and when to skip it)

AI & RAG · 9 min read

How to choose a RAG development company (what to look for)

AI & RAG

If you have a folder of spreadsheets and a queue of questions nobody has time to answer, tell us what people are actually asking and we will map out the pipeline.

Send the problem. You get one fixed number and a plan back within a business day.

Duskel
Duskel
AI AUTOMATIONSOFTWARE

We build software worth keeping — for clients, and for ourselves.

Founded & led by codewithumar

© 2026 Duskel. All rights reserved.DUSKEL SMC-Private Limited · Incorporated 2021 · Lahore, PakistanBuilt to last, not to demo.