AI for SQL: how to write and optimize queries with a model
Schema, sample data and a query plan in the request to the model, an EXPLAIN check and read-only access. Working Python code and a cost estimate.
6 min · · development · API keys
An AI model for SQL programming can write queries from a description and explain why a query is slow. But without the database schema it guesses table names, and without checking it can produce a query that runs but returns the wrong thing. Worst of all is a generated UPDATE or DELETE landing in a production database. Below is a working order that reduces these risks: what to give the model, how to verify a query with EXPLAIN and how to limit access.
The pipeline "question in plain language, SQL in reply" for reporting interfaces is covered in the article generating SQL queries with AI. Here the accent is different: a developer's and analyst's daily work, optimization and safety.
AI for SQL programming: what to give the model
A request like "write SQL for the top 10 customers by revenue" with no context produces a query over invented tables. Pass four things:
- DBMS and version. PostgreSQL 16, MySQL 8, SQLite: date functions, window functions and
LIMITdiffer. - Schema.
CREATE TABLEwith keys, constraints and indexes fits best. The model sees types and relations. - A few sample rows. Two or three anonymized rows per table. They show the value format: statuses as strings, dates as text or
timestamp, how amounts are stored. - Table sizes. The order of magnitude of row counts. For optimization it matters more than it seems: a table with a hundred rows needs no index.
You can export the schema with your DBMS's standard tools (for example pg_dump --schema-only for PostgreSQL). Do not send real customer data: the structure and anonymized examples are enough.
Request template
DBMS: PostgreSQL 16.
Schema:
CREATE TABLE orders (id bigint PRIMARY KEY, customer_id bigint NOT NULL,
status text NOT NULL, created_at timestamptz NOT NULL, total numeric(12,2));
CREATE INDEX orders_customer_idx ON orders (customer_id);
CREATE TABLE customers (id bigint PRIMARY KEY, name text, region text);
Volume: orders about 40M rows, customers about 800K.
Statuses: 'new', 'paid', 'cancelled'.
Task: revenue by region for October 2026 from paid orders.
Requirements: SELECT only, month bounds without BETWEEN on a timestamp,
explain the logic in one paragraph after the query.Two details save edits: list the allowed status values explicitly (otherwise the model invents completed) and ask for date bounds as >= '2026-10-01' AND < '2026-11-01'. The typical BETWEEN error on timestamp loses the last day.
Verification: EXPLAIN instead of faith
A readable-looking query may turn out to be a full scan of 40 million rows. So every generated query gets a plan step:
EXPLAINshows the plan without running the query. Start with it.EXPLAIN (ANALYZE, BUFFERS)in PostgreSQL really executes the query and shows actual time. For aSELECTthat is safe with a timeout and on a replica, but for queries that modify dataANALYZEwill execute them, so do not run those through it. Syntax and output in MySQL and other DBMSs differ; consult your own documentation.
Then a "query, plan, explanation" loop works well: you send the model the original query and plan output, ask it to explain the bottleneck and offer options (query rewrite, index), and then test each option on a copy of the database. A suggested index is created by a person through a normal migration, not by a script with a model.
Protecting the database: four barriers
The code below shows a chain of barriers for generated SELECT queries:
import os
import psycopg
import sqlglot
from sqlglot import exp
from openai import OpenAI
client = OpenAI(base_url="https://api.zukko.pro/v1",
api_key=os.environ["ZUKKO_API_KEY"])
def generate_sql(schema, task):
resp = client.chat.completions.create(
model="claude-sonnet-5-5",
messages=[
{"role": "system", "content": "You are a PostgreSQL 16 assistant. "The barriers in order:
- Query parsing.
sqlglotchecks that there is exactly one statement and it is aSELECT, notSELECT 1; DROP TABLE .... This is a syntactic check: it will not catch function side effects, so the next barrier is mandatory. - A read-only role. The connection string uses a DBMS user that has only
SELECTon the needed tables. This is the main barrier: it works even if all other checks are bypassed. - A read-only transaction and a timeout. The query cannot change data and will not occupy the database for long.
- Execution on a replica or a copy. Heavy analytical queries must not interfere with production load.
If the model was asked to fix data, that is a separate task: the model produces a script, a person reads it, runs it on a copy, wraps it in a transaction and launches it themselves.
Typical errors worth looking for
- Duplicates after JOIN. Joining a one-to-many table multiplies rows, and
SUMreturns an inflated total. NOT INwith NULL. The subquery returns NULL and the condition stops matching.NOT EXISTSis often what you need.- Functions of another DBMS. The model confuses
DATE_TRUNC,STRFTIME,DATEADDif the dialect is not stated. - Implicit type casts that prevent index use.
SELECT *in production queries and sorting withoutLIMIT.
Check results against control numbers: run the query by hand on a small slice and compare the total. And when optimizing, compare the results of the old and new queries on a copy, for example with EXCEPT in both directions: an empty result in both directions means identical row sets.
Which model to choose
Cheap models suffice for simple queries; complex analytics with window functions and plan optimization are worth running on several. Make 15-20 of your own queries with known answers and compare. Zukko prices at October 18, 2026 ($ per 1M tokens, input / output) and strength index:
| Model | Price | Strength index |
|---|---|---|
deepseek-v4.1-flash | 0.013 / 0.038 | 39 |
gpt-6.1-sol | 0.27 / 1.32 | 52 |
claude-sonnet-5-5 | 0.4 / 2 | 48 |
claude-opus-5-5 | 0.8 / 4 | 58 |
The index does not measure SQL quality on your schema; it is a reference point. A roundup on choosing a model for code: the best AI for programming.
What it costs
Formula: (input × input price + output × output price) / 1,000,000. Assumptions: an optimization request, 3,000 schema tokens, 100 question tokens, 800 plan tokens, in total 3,900 input and 500 output tokens.
deepseek-v4.1-flash: 3,900 × 0.013 / 1,000,000 + 500 × 0.038 / 1,000,000 = 0.0000507 + 0.000019, about $0.00007.gpt-6.1-sol: 3,900 × 0.27 / 1,000,000 + 500 × 1.32 / 1,000,000 = 0.001053 + 0.00066, about $0.0017.claude-sonnet-5-5: 0.00156 + 0.001, about $0.0026.claude-opus-5-5: 0.00312 + 0.002, about $0.0051.
Twenty such requests a day on claude-sonnet-5-5 come to about $0.05. The schema repeats in every request, so with frequent calls it pays to cache it: the Claude cache discount comes from the Anthropic format, details in the article prompt caching: reads and writes. Fresh prices are always in the GET https://api.zukko.pro/v1/models response.
Connecting
Create a key in the dashboard, set the base URL https://api.zukko.pro/v1 and connect following the chat completions reference. The balance is topped up from $1: SBP (the payment page adds 8.5% on top), cryptocurrency or a MasterCard.
FAQ
Can I give an AI direct access to the database?
Only with a role that has read rights on the needed tables, and preferably on a replica. Never give a model write permissions.
How does an AI for generating SQL code learn the schema?
Only from your request. Pass CREATE TABLE with indexes and sample rows, otherwise it will guess names.
Does the model really optimize queries?
It offers options based on the plan you showed it. The effect is verified on a copy of the database, and a person creates the index.
How do I check that an optimized query returns the same result?
Compare the results of the two queries on a copy with EXCEPT in both directions and check the totals.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Your Own Neural Network Proxy on Node.js: Keep the API Key Off the FrontendA small Express server between your frontend and the model API: the key stays on the server, and models, request size and rate are capped.
- DeepSeek API in Python: Requests, Streaming, JSON and Error HandlingReady code for DeepSeek V4.1 Flash and V4 Pro on the OpenAI SDK: a request, streaming, JSON parsing and careful error handling.
- Gemini API in Python: requests via the OpenAI SDK and OpenAI formatA working Gemini API example in Python with no extra library: change base_url in the OpenAI SDK, add streaming and JSON, and compare with google-genai.
- Anthropic SDK for Python: base_url, key and a first request via a gatewayThe official anthropic package for Python just needs a different address. We cover base_url, the key, streaming, token counting, caching and errors.