How Large Language Models Actually Parse a SQL Schema, and Where They Get It Wrong
A model does not read your database the way you do. Understanding what it actually sees explains most of its confident, wrong joins.
When a text-to-SQL assistant writes a join that looks plausible and returns garbage, the failure is almost never random. It comes from a mismatch between what you assume the model knows about your warehouse and what it was actually handed. If you work with these tools daily, the single most useful mental model is this: the language model never touches your database. It reads a text description of your schema that something else assembled, then it predicts SQL token by token. Everything good and bad about the output follows from that.
What the model is actually given
Tools like the dbt MCP server, Snowflake Cortex Analyst, and the various LangChain SQL agents all do a version of the same thing before the model ever sees your question. They query the system catalog, usually information_schema.columns or the warehouse equivalent, and serialize the result into text. A typical serialization looks like a set of CREATE TABLE statements or a compact list of table names with their columns and types. That text goes into the prompt alongside your natural-language question.
So the model's entire universe is that serialized block. It sees orders(order_id INT, customer_id INT, status VARCHAR, created_at TIMESTAMP). It does not see your data. It does not know that status only ever contains four values, that customer_id is null for 12% of guest checkouts, or that created_at is stored in UTC while your finance team reports in Eastern. It infers relationships from names and types, and from patterns it learned during training across millions of other schemas.
This is why naming carries so much weight. A column called cust_id in one table and customer_id in another is obvious to you and genuinely ambiguous to the model. It will often guess they join, and it will usually be right, but "usually" is doing a lot of work in a revenue query.
Where inference quietly breaks
The errors cluster into a few recognizable families, and once you can name them you start catching them on sight.
Join inference on weak signals
The model reconstructs foreign keys from naming conventions because most warehouses do not enforce or even declare them. If your schema has explicit foreign key constraints, include them in the serialization; many tools omit constraint metadata by default, which throws away the strongest signal available. Without it, the model joins orders.customer_id to customers.id because that pattern dominates its training data, and it will do the same thing even when your actual key is a composite of tenant_id and customer_id. Multi-tenant schemas break text-to-SQL constantly for exactly this reason: the model forgets the tenant predicate because nothing in the column names screams that every join and filter must carry it.
Semantics the schema doesn't encode
A column named amount tells the model nothing about whether it is gross or net, in cents or dollars, or already refunded. It will happily SUM(amount) across a table that mixes charges and reversals stored as negative numbers, or worse, stored as positive with a separate type flag it never checked. These are not hallucinations. The SQL is valid. It answers a question you did not ask.
Status and enum blindness
Ask for "completed orders" and the model will write WHERE status = 'completed' when your actual value is 'COMPLETE', 'fulfilled', or the integer 3. It is guessing at literals it has never seen. This is the most common single cause of queries that run cleanly and return zero rows.
Grain and fan-out
Join a header table to a line-items table and every header-level metric doubles, triples, or worse. The model does not know the grain of each table unless you tell it, and grain is invisible in a column list. A revenue number inflated by a silent fan-out is the error most likely to reach a dashboard before anyone notices.
Date and timezone assumptions
The model defaults to naive date arithmetic. "Last month" becomes calendar month in whatever the session timezone happens to be, which may not match how your business closes its books. Fiscal calendars are effectively invisible to it.
Why the model sounds so certain anyway
Language models are trained to produce fluent, well-formed output. Syntactically correct SQL is easy; semantically correct SQL against your specific business logic is hard, and the two look identical in the response. There is no confidence signal in the query text itself. A join that is dead wrong is written with exactly the same fluency as one that is right. This is the trap for analysts who are new to these tools: the output reads like it came from a colleague who knows your warehouse, when it came from a model that read a column list ninety seconds ago.
Practical ways to give the model a better chance
The fix is almost always upstream of the model, in what you feed it and how you constrain it.
- Feed a semantic layer, not raw tables. If you run dbt, its models, column descriptions, and the metrics defined in the semantic layer are the best schema context you can provide. A model reasoning over
total_revenuedefined once, correctly, beats a model reinventing revenue from raw tables every time. The dbt MCP server exposes exactly this. - Include column descriptions and enum values. Many serializers support passing comments. A description like "status: one of pending, shipped, delivered, cancelled" eliminates the enum-guessing failure entirely. Sample distinct values for low-cardinality columns and put them in the context.
- Declare grain and keys explicitly. One line per table stating its grain ("one row per order line") and its join keys prevents most fan-out and join errors. If real foreign key constraints exist, surface them.
- Prune the schema you send. Dumping 400 tables into the context degrades accuracy and burns your context window. Retrieve only the tables relevant to the question, which is what the better tools now do with a retrieval step over table descriptions before generation.
- Always inspect the SQL, then validate the numbers. Read the joins and the filters before you trust the result. Run the query with a known answer you can check by hand. Treat a zero-row or suspiciously round result as a signal, not an answer.
A reasonable division of labor
The right role for text-to-SQL in 2026 is a fast first draft and an exploration accelerator, not an unsupervised query author for anything that feeds a decision. It is genuinely good at boilerplate joins across well-named, well-documented tables, at translating a clear question into a starting query, and at reminding you of window-function syntax you half-remember. It is unreliable at business logic that lives in your head or in tribal knowledge, and it will never tell you when it has crossed from one into the other.
The analysts getting real value from these tools are not the ones who trust them more. They are the ones who understood early that the model is reasoning over a text snapshot of a catalog, invested in making that snapshot rich and accurate, and kept their own eyes on every join that touches money.
Put this into practice
Work out what an AI model actually costs per month from your token usage, and compare the major models side by side.
Open the AI API Cost Calculator →A note on shelf life. AI products change fast. This guide deliberately focuses on the parts that stay true — how to judge a tool, what the trade-offs are — rather than ranking products that will have changed by the time you read it. Prices and feature claims should always be checked against the provider before you rely on them.