In Part 1 we established the core problem: paste a raw schema into a prompt and even current frontier models land in the low 60s at best on realistic benchmarks, failing through fan-out joins, hallucinated columns, and missing business logic. The single biggest lever for fixing that is not a better model. It is a better schema representation.
This is worth stating plainly because it runs against instinct. Teams burn weeks comparing models when the honest answer is that the model was never the bottleneck. Recall the pattern from Part 1: in BIRD's launch-era evaluation, the strongest model of the day swung by roughly 20 points of execution accuracy based purely on whether it received human-written context about the data, and three model generations later, that same spread separates zero-shot frontier models from the context-heavy pipelines topping the 2026 leaderboard. Researchers noticed schema presentation effects as far back as 2022, when Rajkumar, Li, and Bahdanau showed that prompt format and the inclusion of sample rows meaningfully changed Codex's text to SQL performance on Spider. The field has been refining schema representation ever since, up through compact formats like M-Schema (introduced with the XiYan-SQL system) that pack names, types, descriptions, sample values, and key relationships into a dense structured block, and Oracle's April 2026 Spider 2.0 Lite leader lists schema enrichment as a core pillar of its pipeline.
This post covers the five schema-context techniques that deliver the most accuracy per hour of effort, with the SQL to implement each one.
Step 1: Curate, never dump
The instinct is to give the model everything: all 340 tables, just in case. This backfires twice. First, irrelevant tables actively cause errors, because the model now has 340 chances to join to the wrong thing, and similarly named tables (orders, orders_archive, orders_staging_v2) become traps. Second, on genuinely large schemas you exhaust the useful context budget on noise.
The research term for choosing what to include is schema linking: identifying the tables and columns relevant to a given question. Serious systems for large databases, like the CHESS pipeline, treat this as its own retrieval problem with dedicated selection stages. You do not need that on day one. You need its manual equivalent:
- Define the subject areas your users actually ask about (sales, inventory, marketing spend).
- For each area, list the 5 to 20 tables that answer 95 percent of questions.
- Exclude staging tables, archive tables, ETL scratch space, and anything with
_bakin the name. - Exclude columns nobody should query: internal flags, encrypted blobs, and anything containing PII the assistant has no business reading.
A curated 15-table context beats a complete 340-table dump on accuracy, latency, and cost simultaneously. When your estate is large enough that even curated subject areas strain the context window, automated schema linking becomes step one of your pipeline, and the enriched metadata you are about to build becomes the search index it runs against.
Step 2: Enrich every column with descriptions
A column named dlvry_dt_est means nothing to a model, and honestly not much to your newer employees either. The fix is the least glamorous work in data engineering: writing descriptions. The payoff is that this documentation becomes machine-usable forever after, and the database itself is the right place to store it, versioned alongside the schema instead of rotting in a wiki.
On PostgreSQL:
COMMENT ON COLUMN orders.order_status IS
'Order lifecycle state. Values: pending, paid, shipped, cancelled, refunded.
Revenue reporting counts paid and shipped only.';
COMMENT ON COLUMN customers.is_deleted IS
'Soft delete flag. 1 = deleted. Exclude these rows from all reporting.';On SQL Server, the equivalent is extended properties:
EXEC sys.sp_addextendedproperty
@name = N'MS_Description',
@value = N'Order lifecycle state: pending, paid, shipped, cancelled, refunded. Revenue = paid + shipped only.',
@level0type = N'SCHEMA', @level0name = N'dbo',
@level1type = N'TABLE', @level1name = N'orders',
@level2type = N'COLUMN', @level2name = N'order_status';Notice what those descriptions contain: not just what the column is, but how it should be used in analysis. "Revenue counts paid and shipped only" is exactly the business rule the baseline query in Part 1 violated. Putting rules where the schema lives means every future prompt carries them automatically.
Then extract everything programmatically so your assistant's context is always generated fresh from the source of truth. On SQL Server:
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.IS_NULLABLE,
CAST(ep.value AS NVARCHAR(500)) AS column_description
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
LEFT JOIN sys.extended_properties ep
ON ep.major_id = OBJECT_ID(t.TABLE_SCHEMA + '.' + t.TABLE_NAME)
AND ep.minor_id = COLUMNPROPERTY(ep.major_id, c.COLUMN_NAME, 'ColumnId')
AND ep.name = 'MS_Description'
WHERE t.TABLE_TYPE = 'BASE TABLE'
ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION;Prioritize ruthlessly: document the tables from Step 1 first, and within them, the columns involved in money, dates, statuses, and joins. Twenty well-described columns outperform four hundred bare ones.
Step 3: Show the model actual values
Here is a failure that descriptions alone cannot fix. A user asks for "cancelled orders in Illinois." The model writes WHERE order_status = 'Cancelled' AND state = 'Illinois'. Your database stores 'cancelled' (lowercase) and 'IL'. The query runs perfectly and returns zero rows, which the user reads as "we had no cancelled orders in Illinois." A wrong answer delivered with total confidence.
The fix is profiling low-cardinality columns and putting real values in the context:
-- Profile a status column before trusting anyone's memory of it
SELECT order_status, COUNT(*) AS row_count
FROM orders
GROUP BY order_status
ORDER BY row_count DESC;Run this and you will usually learn something yourself: the 'test' status nobody mentioned, the 'CANCELED' and 'cancelled' spelling split from an old migration, the NULLs. Every one of those is a wrong answer waiting to happen, for the model and for humans alike.
For each enum-like column (statuses, types, categories, state codes, channels), include the distinct values directly in the schema context. For high-cardinality columns like customer names, include two or three representative examples so the model learns the format without you shipping the whole table. Part 3 covers dynamic value lookup for the cases where static samples are not enough.
Step 4: Make relationships and grain explicit
Foreign keys tell a model which joins are possible. They do not say which joins are correct for a given question, and they say nothing about grain, which is what caused the double-counting disaster in Part 1. Spell both out in plain language as part of the context:
RELATIONSHIPS
orders.customer_id -> customers.id (many orders per customer)
order_items.order_id -> orders.id (many items per order)
GRAIN AND AGGREGATION RULES
- orders is one row per order. order_total is the complete order value
including all items. To sum revenue, use orders alone.
- NEVER join order_items when aggregating order_total. It multiplies
totals by line-item count.
- order_items is one row per SKU per order. Use it for product-level
questions only, aggregating qty * unit_price.If your database predates disciplined foreign keys (plenty of real ones do), this written relationship map is even more important, because the model cannot infer joins from constraints that do not exist.
Step 5: Build views as the AI's interface
The highest-leverage move on this list: stop pointing the model at raw tables and give it a curated semantic surface instead. A small set of wide, well-named reporting views lets you rename cryptic columns, pre-apply soft-delete and test-data filters, and bake in the joins you have already validated, so the model cannot get them wrong.
CREATE VIEW analytics.order_facts AS
SELECT
o.id AS order_id,
o.created_at AS ordered_at,
o.order_status,
o.order_total,
c.id AS customer_id,
c.company_name,
c.state_code
FROM dbo.orders o
JOIN dbo.customers c
ON c.id = o.customer_id
WHERE c.is_deleted = 0
AND o.order_status <> 'test';Now "revenue by customer this year" is a single-table query with no join to invent, no soft-delete rule to forget, and no test orders to accidentally count. Three to eight views like this typically cover the bulk of real business questions, and they double as guardrails: grant the assistant's database role access to the analytics schema only, and the raw tables, along with every column you excluded, are physically out of reach. Part 4 builds on exactly this foundation.
Putting it together: the enriched schema block
Everything above compiles into a dense context block, structurally similar to the M-Schema format used by leading research systems:
DIALECT: T-SQL (SQL Server 2019). Current date: 2026-07-18.
TABLE analytics.order_facts
One row per order. Excludes deleted customers and test orders.
order_id int PK
ordered_at datetime Order placement timestamp (UTC)
order_status varchar(20) Values: pending, paid, shipped, cancelled,
refunded. Revenue = paid + shipped only.
order_total decimal(12,2) Complete order value. Sum this for revenue.
customer_id int FK to customer_facts.customer_id
company_name varchar(200) e.g. 'Acme Mechanical', 'Lakeside HVAC'
state_code char(2) USPS codes, e.g. 'IL', 'WI'
RULES
- Answer only from the tables above. If you cannot, say so.
- Read-only. Single statement. Include a reasonable TOP/LIMIT.Generate this block from INFORMATION_SCHEMA, comments, and profiling queries on a schedule, so it never drifts from reality. Stale context is worse than no context, because the model trusts it completely.
Key takeaways
Schema context engineering converts the baseline approach's worst failure modes into non-events: curation kills wrong-table joins, descriptions and grain rules kill double-counting, real values kill zero-row false negatives, and curated views make whole categories of mistakes structurally impossible. It is unglamorous work, and it routinely moves accuracy more than any model upgrade, which is exactly what the research record from 2022 onward keeps showing.
What static context cannot do is teach the model your organization's query patterns and vocabulary. That takes retrieval, which is Part 3.
Frequently asked questions
How many tables can I put in an LLM prompt?
Modern context windows fit hundreds of table definitions, but fitting is not the constraint: relevance is. Accuracy degrades as irrelevant, similarly named tables give the model more wrong options. Curate to the tables a competent analyst would consider for the question domain, and add automated schema linking when a single curated set no longer covers your users' questions.
Do column descriptions really improve text to SQL accuracy?
Yes, and it is one of the most consistent findings in the field. BIRD's launch evaluation showed roughly a 20-point execution accuracy swing for the strongest model of the day depending on whether human-written knowledge about the data was provided, and the gap between zero-shot models and context-heavy pipelines on the 2026 leaderboard tells the same story. Descriptions that encode usage rules ("revenue counts paid and shipped only") do the most work.
Should the AI query tables or views?
Views, almost always. They let you fix naming, pre-apply business filters, eliminate error-prone joins, and enforce access boundaries at the database level. Raw-table access makes sense mainly for internal data-team tooling where flexibility outweighs safety.
What about ClickHouse, MySQL, or other engines?
Every technique here transfers: comments (ClickHouse supports column comments natively), value profiling, relationship maps, and curated views are universal. What changes is dialect syntax, which is exactly why the context block pins dialect and version explicitly.
Let us build your context layer
This is the part of the stack Field Craft Labs lives in. We audit your schema, build the annotated context layer and reporting views, and wire them into an assistant your whole team can use, so pulling a number or drafting a report stops requiring a SQL expert. It is working documentation for your humans and fuel for your AI, delivered as one project. Talk to us about your database.
Previous: Part 1: Why the Baseline Approach Fails. Next: Part 3: Retrieval, Golden Queries, and Business Context.
// Work with us
Want this working on your data?
Thirty minutes, no sales pitch. Bring the problem costing you the most time and leave with a rough plan and a price range.
Prefer email? hello@fieldcraftlabs.com