Skip to content
TutujinAPI

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
See the usage assumed

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)

    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)

    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)

    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. 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. 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. 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. 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. 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. 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.

ItemModelUsage assumedCost
Question embeddingtext-embedding-3-small20,000 × 50 input tokens¥0.04
SQL generationdeepseek-v4.1-flash20,000 × (4,000 input + 300 output tokens)¥291.2
Rewrites after failed validation (about 10%)deepseek-v4.1-flash2,000 × (4,500 input + 300 output tokens)¥31.92
Explaining resultsqwen3.5-plus20,000 × (1,500 input + 250 output tokens)¥67.2
Report and spreadsheet summariesqwen3.5-plus300 × (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.

Sample code (Python, natural-language query)
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.

Tutujin support WeChat QR code
WeChatJsnonoChat

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.