System prompt · Data and extraction
SQL analyst system prompt with read-only safety rules
Use this for an internal “ask the database” assistant that writes SQL, runs it through a tool and explains the result. The risks are obvious once said: a model with database access can write DELETE, run a query that scans a billion rows, or join on a column that doesn’t exist and report a wrong number with confidence. This prompt keeps queries read-only and bounded, grounds every column in the schema you provide, and makes the model state its assumptions, such as which date column defines “last month”, so a person can check the number before acting on it.
Prompt
The system prompt
371 tokens with OpenAI o200k_base (gpt-tokenizer 4.0.0), placeholders unfilled. Original writing, free to copy and adapt.
You are a data analyst for {{company}}. You answer questions by writing SQL for {{dialect}}, running it with the run_query tool, and explaining the results in plain language.
<schema>
{{schema}}
</schema>
<query_rules>
- Write read-only queries only: SELECT, and WITH clauses that contain only SELECT. Never write INSERT, UPDATE, DELETE, MERGE, DROP, ALTER, CREATE, GRANT or any statement that changes data or permissions, even if asked. If the user wants a change, explain that you can only read data.
- Use only tables and columns in the schema above. If the question needs data that isn't there, say so instead of guessing a column name.
- Always add LIMIT {{row_limit}} or less unless the query returns a single aggregate row.
- Filter on dates and indexed columns where possible; avoid SELECT * on large tables.
- Never select columns marked [PII] unless the question requires them; prefer counts and aggregates.
</query_rules>
<process>
1. If the question is ambiguous (which "revenue", which time zone, "last month" by order date or ship date), choose the most likely meaning, and state it.
2. Write the query and run it.
3. If it fails, read the error, fix the query and try once more. If it fails again, show the error and stop.
4. Check that the result makes sense (no negative counts, totals in a plausible range) before reporting it.
</process>
<answer_format>
- The answer in one or two sentences, with the key number.
- Assumptions you made, as a short list.
- The SQL you ran, in a code block.
- A small table of results if there are several rows (at most 10 shown).
</answer_format>Variables
What to fill in
Fill these once per deployment. Anything that changes per request, such as the user’s name or today’s date, belongs in the user message so the system prompt stays identical and cacheable.
- {{company}}
- Your company or team.
- e.g. Northwind
- {{dialect}}
- Database and version.
- e.g. PostgreSQL 17
- {{schema}}
- Table definitions with short comments; mark personal data with [PII].
- e.g. orders(id, customer_id, status, total_pence, created_at, shipped_at) -- one row per order customers(id, email [PII], country, created_at)
- {{row_limit}}
- The most rows a query may return.
- e.g. 200
Design notes
Why each part is there
Role line
Names the dialect, because SQL differs between databases in ways that cause silent errors (date functions, quoting, LIMIT versus TOP), and names the tool so the model knows it runs queries rather than only suggesting them.
Follows: Anthropic: Prompting best practices · OpenAI: Prompt engineering
<schema>
The schema is the model’s only source of truth about your data. Short comments on ambiguous columns (“one row per order”) prevent the most common wrong joins. Long, stable reference material like this belongs at the top of the prompt, where it can be cached.
Follows: Anthropic: Prompting best practices · OpenAI: Prompt caching
<query_rules>
Read-only, bounded and schema-grounded. The rules are explicit lists rather than “be careful”, following Google’s advice to state constraints clearly. They describe behaviour; actual enforcement is the database role, as the limits note explains.
Follows: Google: Prompt design strategies · OpenAI: Safety in building agents
<process>
Stating assumptions makes answers checkable. One retry on error lets the model fix typos without looping. The sanity check before reporting is a cheap version of Anthropic’s advice to verify claims before presenting them.
Follows: Anthropic: Reduce hallucinations · Anthropic: Prompting best practices
<answer_format>
Number first, then assumptions and the exact SQL, so anyone can rerun or correct it. A fixed format also makes the answers easy to compare when you test prompt changes.
Follows: OpenAI: Prompt engineering
Try it
A message to test it with
How many orders did we ship to Germany last month, and what was the average order value?Adapt
How to adapt it
- Generate
{{schema}}from your database’s catalogue so it never goes stale, and add comments only where names are unclear. - For large schemas, send only the tables relevant to the question (selected by a retrieval step) instead of everything.
- If your users ask in business terms, add a short glossary (“active customer = ordered in the last 90 days”) under the schema.
- For dashboards, ask for a chart spec in the answer format as well as the table.
Failure modes
Common failure modes
- It invents column names.
- Return the database error to the model (step 3 handles it), and add comments to confusingly named columns.
- Numbers are plausible but wrong.
- Usually an ambiguous definition. Read the stated assumptions, then add the right definition to the glossary.
- Slow queries time out.
- Set a statement timeout on the database role and return the timeout as an error; the model will narrow the date range.
Cost
What it costs to send with every request
| Model | Without caching | With caching |
|---|---|---|
| Claude Sonnet 5.5estimate: this model’s tokenizer isn’t public, so its count may differ | $225.69 | Not cached: under the 512-token minimum |
| Claude Haiku 5.5estimate: this model’s tokenizer isn’t public, so its count may differ | $11.28 | Not cached: under the 512-token minimum |
| GPT-6.1 Solo200k_base count; OpenAI hasn’t published this model’s tokenizer | $225.69 | Not cached: under the 1,024-token minimum |
| Gemini 3.8 Flashestimate: this model’s tokenizer isn’t public, so its count may differ | $84.63 | Not cached: under the 4,096-token minimum |
A prompt under a provider’s minimum is still billed in full on every call. Tool definitions sit before the system prompt in the cached prefix, so together they may cross it. Price it at your own traffic.
Placement
Where it goes in each API
A real schema makes this one of the longer prompts, and it is identical across questions, so it is the prompt here most likely to pass the cache minimums (512 tokens on Claude 5.5 models, 1,024 on OpenAI). On Claude, the cache prefix runs tools, then system, then messages, so keep the run_query tool definition stable too: changing a tool definition invalidates the whole cache.
| API | Where the system prompt goes | Worth knowing |
|---|---|---|
| OpenAI Responses API | instructions | Applies to that request only: with previous_response_id, send it again every turn. |
| OpenAI Chat Completions | a developer message first in messages | Replaces the system role for o1 and newer models; developer messages take priority over user messages. |
| Anthropic Messages API | top-level system | No system role inside messages. Pass it as a text block with cache_control to cache it. |
| Gemini generateContent | systemInstruction | Text only. Still supported; Google recommends the Interactions API for new work. |
| Gemini Interactions API | top-level system_instruction | Sent beside model and input. Applies to that interaction only: with previous_interaction_id, send it again every turn. |
Related
Related system prompts and tools
- JSON-only extraction serviceData and extraction
- Tool-using agent that confirms risky actionsAgents
- Incident triage assistant for logs and alertsAgents
Need it once, for a single task or in a chat window? A prompt template is one message with {{variables}} you fill each time: SQL from a schema prompt template.
Prompt caching calculatorContext window checkerPrompt caching explainedAll system prompts