Data analytics and text-to-SQL
Text-to-SQL works like this: put the structure and field notes of the relevant tables in the prompt, let the model write SQL, check in code that it is a single read-only query, run it with a read-only database user, and have a model turn the result into a few sentences of findings. For 20,000 questions a month plus 300 report and spreadsheet summaries, at live prices in each model’s cheapest group, model costs are about ¥395 a month.
Prices updated Nov 13, 2026, 1:33 AM
Monthly cost estimate
Assumptions: 20,000 questions a month (about 100 business users asking 10 questions each working day); SQL generation at about 4,000 input tokens (1,000 instructions and examples, 2,500 schema, 500 question) and 300 output tokens, with about 10% failing validation and rewritten once; result explanations at about 1,500 input tokens (up to 50 rows); plus 300 report and spreadsheet summaries at about 8,000 input tokens of computed figures each.
¥394.66per month
- Question embedding¥0.04
- SQL generation¥291.2
- Rewrites after failed validation (about 10%)¥31.92
- Explaining results¥67.2
- Report and spreadsheet summaries¥4.3
The business problem
Operations, sales and management keep needing one-off numbers: last week’s return rate by channel, how many repeat buyers the east region has, which store’s average order value dropped most. Each time someone has to ask the data team for SQL, and even simple questions wait in line.
An LLM can translate such questions into SQL, but it does not know your table names, field meanings or metric definitions. It may invent columns or leave out conditions such as “refunded orders do not count as sales”. Run without checks, a generated statement could also modify data, or scan a huge table and slow the database down.
The safe setup gives the model only the relevant tables, validates the generated SQL in code, runs it with a read-only user on a read-only replica with row and time limits, and shows the SQL next to the result so users can check the definition at a glance.
Recommended models
Recommended models are picked from the live catalog; prices are each model's cheapest group, updated Nov 13, 2026, 1:33 AM. Click a model to see every group's price.
Finding the relevant tables (Embeddings)
text-embedding-3-smallOpenAI
input ¥0.04 / output ¥0.04 per 1M tokensStandard group
With many tables you cannot put every schema in the prompt. Write a short description of each table (purpose, field meanings, typical questions), embed it, and send only the few most relevant tables to the model. With twenty or thirty tables you can skip this step and put the whole schema in the prompt.
SQL generation (chat model)
deepseek-v4.1-flashDeepSeek
input ¥2.8 / output ¥11.2 per 1M tokensChina Models group
Writes SQL from the schema, field notes and a few example question–SQL pairs. This is the step where accuracy matters most, so evaluate a few of the alternatives on your own past questions and checked SQL before you settle. Schema and examples are the same in every request: put them at the start of the prompt, and if the model supports context caching that part is billed at the lower cache-read price.
Explaining results and reports (chat model)
qwen3.5-plusQwen
input ¥1.12 / output ¥6.72 per 1M tokensChina Models group
Turns query results, report data or spreadsheet summaries into a few sentences: changes against last period, rankings, outliers. Inputs are mostly numbers and outputs are short, so a low-priced model is enough; to read chart screenshots, switch to one of the alternatives that can read images.
Reference architecture
- 1
Describe the schema
For every table you open up, write down what it is for, what each field means and its unit, enum values, how tables join, and metric definitions such as whether sales include refunds. The clearer the notes, the better the SQL.
- 2
Find the relevant tables
With many tables, embed each table description; for each question fetch the 3 to 5 most relevant tables and put their CREATE TABLE statements and notes in the prompt.
- 3
Generate SQL
State the database (MySQL, PostgreSQL and so on), the available schema and a few “question → checked SQL” examples in the prompt, and ask for exactly one query with no explanation.
- 4
Validate in code
Before running it, check that there is a single statement, that it starts with SELECT or WITH, that it has no INSERT, UPDATE, DELETE, DROP or other writes, that it only touches allowed tables and that it has a LIMIT. On failure send the reason back for one rewrite; if it still fails, ask the user to rephrase.
- 5
Run read-only
Connect with a database user that can only read, on a read-only replica or the data warehouse, with a query timeout and a row limit. Even if validation misses something, that user cannot change data.
- 6
Explain and show
Send the result to the model for two or three sentences of findings and show them with the SQL and the raw rows. For monthly reports and uploaded spreadsheets, compute totals, period-over-period changes and rankings in code first and let the model write the text; the numbers come from your code.
Cost estimate
Assumptions: 20,000 questions a month (about 100 business users asking 10 questions each working day); SQL generation at about 4,000 input tokens (1,000 instructions and examples, 2,500 schema, 500 question) and 300 output tokens, with about 10% failing validation and rewritten once; result explanations at about 1,500 input tokens (up to 50 rows); plus 300 report and spreadsheet summaries at about 8,000 input tokens of computed figures each.
| Item | Model | Usage assumed | Cost |
|---|---|---|---|
| Question embedding | text-embedding-3-small | 20,000 × 50 input tokens | ¥0.04 |
| SQL generation | deepseek-v4.1-flash | 20,000 × (4,000 input + 300 output tokens) | ¥291.2 |
| Rewrites after failed validation (about 10%) | deepseek-v4.1-flash | 2,000 × (4,500 input + 300 output tokens) | ¥31.92 |
| Explaining results | qwen3.5-plus | 20,000 × (1,500 input + 250 output tokens) | ¥67.2 |
| Report and spreadsheet summaries | qwen3.5-plus | 300 × (8,000 input + 800 output tokens) | ¥4.3 |
| Total (per month) | ¥394.66 | ||
Calculated from live prices in each model's cheapest group, updated Nov 13, 2026, 1:33 AM.
Model usage only: databases, vector store and servers are not included. Embedding the table descriptions is a one-off: 300 tables at about 400 tokens each is about 120,000 tokens. Schema and examples make up most of the SQL prompt; if the model supports context caching, cached input is billed at the lower cache-read price and the real bill is lower. Reasoning models bill their thinking as output, so output may exceed the figures above. Twice the questions means roughly twice the cost.
Sample code
The model writes SQL; code checks that it is a single read-only query on allowed tables with a LIMIT, runs it over a read-only connection, then asks a model to explain the result. The sample uses SQLite; swap in your database and read-only user.
import os
import re
import sqlite3
from openai import OpenAI
client = OpenAI(base_url="https://www.tutujin.com/v1", api_key=os.environ["TUTUJIN_API_KEY"])
# Only the tables relevant to the question (with many tables, find them by embedding search with text-embedding-3-small).
SCHEMA = """
CREATE TABLE stores (id INTEGER PRIMARY KEY, name TEXT, region TEXT); -- region: east, north, south, west
CREATE TABLE orders (id INTEGER PRIMARY KEY, store_id INTEGER, paid_at TEXT, amount REAL, status TEXT);
-- status: paid or refunded. Sales = sum(amount) of paid orders. paid_at looks like 2026-10-31 18:05:00.
"""
ALLOWED = {"stores", "orders"}
WRITES = re.compile(r"\b(insert|update|delete|drop|alter|create|truncate|attach|pragma|grant)\b", re.I)
def write_sql(question, problem=None):
prompt = "Question: " + question
if problem:
prompt += "\nYour previous query was rejected (" + problem + "). Write it again."
resp = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[
{"role": "system", "content": "Write one SQLite SELECT query for the question, using only these tables:\n"
+ SCHEMA + "\nAlways add a LIMIT. Reply with the SQL only."},
{"role": "user", "content": prompt},
],
)
sql = resp.choices[0].message.content.strip()
return re.sub(r"^\x60{3}(?:sql)?\s*|\s*\x60{3}$", "", sql).strip().rstrip(";") # drop a code fence
def check(sql):
"""Why the query is unsafe, or None. A SQL parser is sturdier than regexes; keep the read-only user anyway."""
if ";" in sql:
return "more than one statement"
if not re.match(r"(select|with)\b", sql, re.I):
return "not a SELECT query"
if WRITES.search(sql):
return "contains a write keyword"
ctes = set(re.findall(r"\b(\w+)\s+as\s*\(", sql, re.I))
tables = set(re.findall(r"\b(?:from|join)\s+(\w+)", sql, re.I)) - ctes
if not tables <= ALLOWED:
return "unknown table " + ", ".join(sorted(tables - ALLOWED))
if not re.search(r"\blimit\s+\d+", sql, re.I):
return "no LIMIT"
return None
def ask(question):
sql = write_sql(question)
problem = check(sql)
if problem: # one rewrite, then give up
sql = write_sql(question, problem)
problem = check(sql)
if problem:
raise ValueError("no safe query: " + problem)
# read-only connection; in production, a SELECT-only database user on a replica, with a query timeout
db = sqlite3.connect("file:shop.db?mode=ro", uri=True)
rows = db.execute(sql).fetchmany(50)
db.close()
resp = client.chat.completions.create(
model="qwen3.5-plus",
messages=[
{"role": "system", "content": "Explain the result in two or three sentences for a business reader. Use only the numbers given."},
{"role": "user", "content": f"Question: {question}\nSQL: {sql}\nRows: {rows}"},
],
)
return sql, rows, resp.choices[0].message.content
sql, rows, summary = ask("Which 5 stores in the east region had the highest sales last month?")
print(sql, rows, summary, sep="\n\n")Try this with one key
Sign up, create an API key in the console, and put your base_url and key into the code above. For company purchasing or invoices, add our WeChat support JsnonoChat.

Scan the code or search the WeChat ID to add us
FAQ
How much does natural-language querying cost per month?
With 20,000 questions a month, about 4,000 tokens of schema and examples per question and 300 report summaries, the cheapest groups come to about ¥395 a month. The main drivers are question volume and how much schema you put in each prompt.
Can I run the generated SQL directly?
Not without checks. In code, make sure it is a single query on allowed tables with a LIMIT; run it with a read-only database user on a read-only replica or warehouse, with a query timeout. Then even a wrong query cannot change data or overload your primary database.
Are the numbers always right?
No. SQL that runs is not necessarily SQL with the right definition: it may miss refunds, time zones or deduplication. Show the SQL with the result, and for key metrics such as sales or gross margin use your own standard SQL or metric tables, letting the model only map questions to those metrics.
What if we have many tables with cryptic column names?
Write a description for every table and field, including units, enum values and metric definitions. With many tables, use vector search to pick the relevant few and put only those in the prompt. Add typical questions with checked SQL to the prompt as examples.
Can it analyze Excel files?
Yes, but do not ask the model to add up or compare a whole sheet. Read the file in code (pandas, for example), compute totals, changes and rankings, and give those results to the model to write up. LLMs make arithmetic mistakes; the numbers should come from your code.
Is our business data sent to the model?
To generate SQL the request only needs the schema and field notes, not real data. To explain results, the rows are forwarded through this service to the model provider. For personal data or sensitive business figures, mask them or send only aggregates, and assess the setup against your own data-compliance rules.