Stop writing prompts that start with "Act as a senior SQL developer" and hoping for the best. That approach burns tokens and returns vague, syntactically broken queries that ignore your actual schema. These SQL Query Prompts are engineered to force the LLM to reason about your data model, state assumptions, and output production-ready SQL.
These templates work across Claude, GPT-4, Gemini, and DeepSeek, with minor tweaks. I've used variations of these on client projects and internal tools, and they consistently cut the back-and-forth from five iterations to one or two.
Why Generic Prompts Fail Here
Generic prompts like "write a query for revenue by month" fail because they lack three things: schema context, business logic definitions, and output constraints. The LLM guesses at column names, joins, and date filtering, producing SQL that either errors out or returns wrong numbers.
The specific failure mode this template category fixes is implicit ambiguity. When you ask for "churned users," the model doesn't know if you mean cancellation date, last login, or subscription status. These templates force you to define the business rule in the prompt, so the LLM doesn't have to guess.
Template 1: Schema-Aware Query Builder
This is my go-to for any new query. It forces the LLM to see your exact schema before writing a single line of SQL.
Given the following PostgreSQL schema:
Table: users (id INT PK, email VARCHAR, created_at TIMESTAMP, status VARCHAR)
Table: orders (id INT PK, user_id INT FK, amount DECIMAL, ordered_at TIMESTAMP, status VARCHAR)
Table: order_items (id INT PK, order_id INT FK, product_name VARCHAR, quantity INT, price DECIMAL)
Write a SQL query that:
1. Calculates total revenue per month for the last 6 months
2. Revenue = SUM(order_items.quantity * order_items.price) for orders with status = 'completed'
3. Only include users who registered before the order date
Return ONLY the SQL query. No explanation. Use CTEs for readability.
Placeholder meaning:
Table:lines — paste yourCREATE TABLEstatements or\d table_nameoutput. The more accurate, the better.Write a SQL query that:— list every business rule explicitly. Don't say "revenue" if you mean "net revenue after refunds."Return ONLY...— this suppresses the LLM's tendency to explain its reasoning, which wastes tokens and adds noise.
Template 2: Business Logic Definition Prompt
This one is for when the query isn't just about joins — it's about how your business defines a metric. Use it when you need to encode a specific rule that isn't obvious from the schema.
Using the schema from the previous message, write a SQL query for "monthly active users" (MAU).
Business definition of MAU:
- A user is active if they have at least one order with status IN ('completed', 'pending') in that calendar month
- Exclude users with status = 'banned' in the users table
- Exclude test users (email LIKE '%@test.com' OR email LIKE '%@example.com')
Requirements:
- Group by month (YYYY-MM format)
- Include months with zero active users
- Return columns: month, mau_count
Output the SQL in a single code block. Handle the zero-month case with generate_series().
Placeholder meaning:
Business definition of MAU:— this is the critical section. Write your actual rule in plain English. The LLM will translate it to SQL.Requirements:— these are the non-negotiable output constraints. Thegenerate_series()hint prevents the common mistake of dropping empty months.- The "zero-month case" explicitly tells the model what edge case to handle, so it doesn't just write a naive
GROUP BY.
Template 3: Edge-Case Handling Prompt
This one is for the hard stuff — when your query has to handle NULLs, duplicates, or timezone issues that would trip up a junior dev.
Schema:
- events (id INT PK, user_id INT, event_name VARCHAR, occurred_at TIMESTAMPTZ)
- users (id INT PK, timezone VARCHAR)
Write a query that returns the first 3 events per user per day, in the user's local timezone.
Rules:
- A "day" is defined in the user's local timezone, not UTC
- If a user has no timezone set, default to 'UTC'
- Exclude events with event_name = 'internal_ping'
- Order events within each day by occurred_at ASC
- Use ROW_NUMBER() for ranking
- Handle users with fewer than 3 events (still show what they have)
Return the SQL only. Add a brief comment explaining your timezone conversion approach.
Placeholder meaning:
Rules:— each bullet is a specific edge case. Don't assume the LLM knows thatoccurred_atis UTC.- The
ROW_NUMBER()hint guides the model toward the correct window function instead of a self-join. - The comment request is deliberate — it makes the LLM articulate its timezone logic, which you can then verify.
How to Adapt These for Your Own Codebase
First, replace the schema with your actual CREATE TABLE statements. Don't paraphrase — paste them verbatim. The LLM's join logic is only as good as the column names it sees.
Second, add your database dialect to the first line. PostgreSQL, MySQL, and SQL Server have different syntax for limits, string functions, and date handling. One word in the prompt saves an entire rewrite.
Third, include sample data for tricky columns. If you have a status column with values like 'active', 'inactive', 'pending', 'suspended', list them all. The LLM will otherwise assume 'active' and 'inactive' are the only options.
Fourth, for complex queries, ask for the output in a specific format — WITH clauses, subqueries, or window functions. I've found that specifying "use CTEs" or "use window functions" produces more maintainable SQL than letting the model pick.
Do These Prompts Work With Any LLM?
Yes, but with caveats. Claude and GPT-4 handle these templates well because they excel at following multi-step instructions. Gemini is slightly weaker at complex joins but handles the business logic template fine. DeepSeek works, but you may need to explicitly state "use standard SQL" to avoid dialect-specific quirks.
The template structure is model-agnostic — the schema and rules sections work everywhere. The main difference is output quality, not prompt compatibility. For production-critical queries, I still verify the output manually, but these prompts reduce that verification to a quick review instead of a full rewrite.
One note: if you're using a model with a smaller context window, trim the schema to only the tables involved in the query. Pasting your entire database schema wastes tokens and can confuse the model with irrelevant tables.
The single adjustment that improves these prompts the most: add one example row of expected output to the prompt. A single | month | revenue | table with two rows of sample data eliminates 90% of ambiguity about what "revenue" or "active" means — and it's the difference between a query that's close and a query that's exactly right.