# Your LLM writes good SQL. That’s not the same as safe SQL

> Source: <https://www.infoworld.com/article/4244721/your-llm-writes-good-sql-thats-not-the-same-as-safe-sql.html>
> Published: 2026-11-05 09:00:00+00:00

A few years into working with data, I’ve learned that the hardest part of an AI system is rarely the model. Usually it’s the question of who gets to ask what. That question came up for me while building an LLM analytics tool on Snowflake Cortex for healthcare engagement and field-performance data. The tool let people ask questions in plain English, and it cut recurring analytics turnaround from days to minutes. It also made me realize that a fast, accurate answer can still be the wrong answer.

Most of us test text-to-SQL the same way. We ask a question, check that the query runs, and compare the output to what we expected. That works right up until the answer is correct for the wrong person. Picture a sales rep who types “quarterly sales for my hospitals” and gets every hospital in the region back. The SQL is clean. The result is still a problem, and the same thing can happen in a multi-tenant app, an HR system, or a finance tool.

The first change I’d make is to what the model is allowed to see. Before anything is generated, look up who is asking: their role, business unit, territory, and assigned accounts. Then give the model only the tables and columns that person can use. Sensitive fields such as customer IDs, contract terms, or salaries should be tagged in your metadata catalog so they are never offered in the first place. It sounds obvious, but it is easy to skip when a demo is working.

It is tempting to add “only show my territory” to the system prompt and move on. I understand the appeal. But a prompt is a suggestion, and the model can ignore it, forget it, or misread it on a hard question. A better setup parses the generated SQL and rewrites it before anything runs, so the rules live in code that the model cannot talk its way around. In the benchmark behind this work, the most common fix across 900 test queries was adding a row filter to the `WHERE` clause (285 times). Column masking came next (240), followed by scope restriction (165).

Most real questions don’t fit neatly into allow or deny. In the same 900 queries, 40% could run unchanged. Another 20% needed a row filter, 15% needed column masking, and 11.7% needed a join rewrite. Around 6.7% went to a human reviewer, and another 6.7% were blocked. Most people still got a useful answer. It was just a safer one, and I think that trade is the right one to make.

Validate every query before it executes. Reject `SELECT *`, pulls that cross scope boundaries, oversized result sets, and risky joins. Then send everything through a gateway that connects with a narrow database account. That way, a prompt injection or a bad generation can’t walk around your rules, even when the model gets creative.

Accuracy numbers can hide a real problem. Plain text-to-SQL returned up to about 80 unauthorized rows on some queries. Static role-based access control still leaked, because it can’t see context. The policy-aware approach leaked none, and it added only about 6.5 milliseconds per query, which nobody will notice in practice. Keep a record of the question, the user context, the policy applied, the final SQL, and the result. When a compliance team asks who saw what and why, you want the answer in minutes, not weeks.

My advice is to decide who the user is before you decide how smart the model is. Get the access rules right first, then tune accuracy. The model will keep getting better at writing SQL. What makes it trustworthy is everything that happens before and after the query runs.

*Source note: All results come from a synthetic, de-identified benchmark. No real patient, provider, or commercial records were used. This work is based on “Governed LLM-to-SQL for Pharma Commercial Data,” accepted at IEEE ICICNIS 2026.*
