Stockio

Write SQL With AI: Generate and Optimize Queries Safely

Turn reporting questions into tested SQL with explicit data contracts, edge-case fixtures, execution-plan evidence, and safe application types before trusting an AI-generated query.

By Sofia Marchetti

JavaScript Engineer ·

On this page

AI is useful for translating a reporting question into a first SQL draft. It is much less reliable at guessing what your data means. A query can execute successfully while duplicating revenue, excluding unpaid customers, or comparing timestamps in incompatible time zones.

The safest workflow separates semantic correctness from performance. First establish exactly which rows and aggregates answer the question. Then use execution evidence to improve the query without quietly changing its meaning. The assistant proposes; your fixtures, database, and review process decide.

Define the question as a data contract

Write the business question in language that includes a grain, time window, and inclusion rules. “Monthly revenue” is incomplete. Does revenue mean invoices raised, payments settled, or recognised amounts? Are refunds negative transactions? Which currency and reporting time zone apply?

Specify one row per customer, invoice, day, or another entity before generating SQL. This exposes a common source of errors: joining a parent table to multiple child rows and then summing a parent-level amount. The result looks reasonable until a customer with several related records appears.

Give the assistant the SQL dialect, relevant table definitions, primary keys, foreign keys, nullability, and useful indexes. Do not provide an entire production dump. A small, sanitised schema and synthetic examples are usually enough to reason about the query structure.

State what the answer must not do. Perhaps archived accounts must remain visible, or cancelled orders must never contribute revenue. These negative requirements are easy to lose in a broad prompt. The AI coding assistants directory can help you find integrations, but schema access and database permissions matter more than conversational polish.

Ask for assumptions before executable SQL

Start with an explanation of the proposed joins and filters. Ask the assistant to identify missing definitions instead of filling gaps silently. Explicit assumptions are easier to review than a long query whose business logic is hidden inside nested expressions.

A compact prompt can establish that boundary:

Dialect: PostgreSQL.
Goal: one row per customer, including customers with no settled payments.
Window: January 2026 in UTC, start inclusive and end exclusive.
Input: customers(id), payments(customer_id, amount, status, paid_at).
First list missing assumptions and explain the join strategy.
Then propose parameterised SQL. Do not execute anything.
Include fixtures for zero payments, two payments, and a boundary timestamp.

Follow up with real column types and decisions about currencies, refunds, and null amounts. Do not treat this abbreviated schema as sufficient for a financial report. Ask the assistant to explain how each predicate affects customers with no matching payments.

Use parameterised queries through your database driver instead of interpolating user-supplied strings. The OWASP SQL injection prevention guidance explains why escaping and generated query text are not substitutes for binding values. Dynamic identifiers require a separate allowlist strategy; a value parameter cannot safely stand in for an arbitrary table name.

Build fixtures that expose wrong answers

Test the draft against a small database where you can calculate expected results manually. Include an empty table, a parent with no children, multiple matching children, null values, and records just outside the reporting window. Add duplicate-looking business events with distinct identifiers if your data permits them.

Check join cardinality explicitly. Count rows before and after each join, and inspect which keys repeat. A DISTINCT clause may conceal a faulty join rather than repair it. Aggregating a child table before joining can be appropriate, but only when that aggregation matches the intended grain.

Pay attention to null semantics. Filtering a nullable column after a left join can remove the unmatched rows you intended to retain. Comparisons with null do not behave like ordinary equality. Ask the assistant to explain a specific fixture row through every stage rather than merely declaring the query correct.

Consult the PostgreSQL SQL documentation for the actual syntax and semantics when using PostgreSQL. Other databases differ in date functions, grouping rules, casts, and optimisation behaviour. Translating function names does not automatically preserve results.

Save these fixtures as regression tests. They become especially valuable when the next optimisation changes a join, introduces a common table expression, or moves a filter across an aggregation boundary.

Read plans before requesting optimisation

Once results are correct, capture a baseline with representative data and parameters. Record duration, returned rows, database version, relevant indexes, and whether the cache was warm. A query on twenty fixture rows cannot predict the behaviour of a production-sized table.

Use the database's plan tools and read the distinction between estimated and actual rows. Large estimation errors can point to stale statistics, skewed values, or correlated columns. An assistant can explain a plan excerpt, but it should not prescribe indexes without understanding the workload.

The PostgreSQL EXPLAIN guide describes plan interpretation and the consequences of ANALYZE. Remember that EXPLAIN ANALYZE actually executes the statement. Use a safe staging environment, read-only roles where appropriate, and statement timeouts; do not casually run generated write statements under analysis on production.

Ask for one hypothesis at a time: perhaps a selective filter could use an index, or repeated work could be reduced. Compare measured plans after each change. Lower estimated cost alone is not proof of a faster user-facing request, and an additional index imposes storage and write overhead elsewhere.

Integrate the result without losing its meaning

After approving the query, inspect the data shape your application receives. Counts, decimals, and timestamps may arrive as strings depending on the driver. An inferred TypeScript number is not evidence that a large integer or money value can be represented safely.

Use the JSON to TypeScript tool with a sanitised result sample to draft response interfaces, then correct nullable fields and driver-specific scalar types manually. A sample containing only successful payments cannot reveal every permitted null or empty-result shape. Keep runtime validation separate from compile-time types.

Apply least-privilege access to any assistant integration that can contact a database. Prefer schema-only context and read-only staging connections for exploration. Require a human approval boundary before migrations, index creation, or queries that export sensitive records.

Document the query's business definitions alongside its tests. Your development workflow should preserve those decisions during code review so the next developer understands why a seemingly simpler query was rejected. Recheck performance after meaningful data growth, not just after code changes. Optimisation is a workload-specific observation, not a permanent certificate attached to generated SQL.

Key takeaways

  • Define the result grain, reporting window, and business rules before requesting SQL.
  • Supply a precise dialect and sanitised schema, not production records.
  • Verify joins and null handling with deliberately awkward fixtures.
  • Optimise from measured execution plans after proving correctness.
  • Preserve result types, access controls, and semantic regression tests during integration.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Dev Tools & AI Coding
Dev Tools & AI Coding

10 Best AI Coding Assistants

Compare ten AI coding assistants for editor help, repository questions, and terminal workflows, with practical notes on strengths, trade-offs, privacy, and choosing a tool you can review.

Includes: JSON to TSSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Cursor AI Tips: Give the AI the Right Codebase Context

Give Cursor focused repository context, trustworthy API examples, and clear edit boundaries so generated changes fit your architecture instead of inventing a parallel one.

Includes: JSON to TSGrace ChenGrace Chen
Dev Tools & AI Coding

Refactor Code With AI: Legacy JavaScript in Safe Steps

Refactor legacy JavaScript through narrow AI-assisted patches, characterisation tests and boundary types, while preserving hidden contracts and a practical rollback path.

Includes: JSON to TSRavi DesaiRavi Desai
Dev Tools & AI Coding

AGENTS.md and Cursor Rules Examples for AI Coding Agents

A coding agent needs more than a feature request. It needs to know which commands are safe, where architectural decisions live and what counts as finished. Without that context, an assistant can produce a.

Includes: JSON to TSSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Reviewing AI-Written Code: A Workflow for AI Assistants

Review AI-generated changes with a repeatable workflow for defining scope, verifying dependencies, testing behavior, and keeping human ownership of every merged line.

Includes: JSON to TSGrace ChenGrace Chen
Dev Tools & AI Coding

Essential VS Code Extensions for Frontend Developers

Build a focused VS Code setup with ESLint, Prettier, framework support, and repeatable workspace settings, without piling on conflicting extensions.

Includes: JSON to TSRavi DesaiRavi Desai