高级 13 分钟SQL

本地文本转 SQL:使用语言查询数据库 naturel

通过本地大语言模型实现文本到 SQL 功能,您可以用法语提问(例如:“上个月各地区的营业额是多少?”),并获得可在您的 PostgreSQL 或 MySQL 上执行的 SQL 查询语句——数据库模式和数据都不会离开您的基础设施。本指南介绍具体的实现机制:将数据库模式注入上下文,使用 Ollama 构建 Python 处理流程,尤其是设置防护措施(只读、验证、限制);缺少这些措施,任何文本到 SQL 系统都无法部署到生产环境。

作者: Mohamed Meguedmi·更新于 2026-08-27·已在 Windows、macOS 和 Linux 上测试

#为什么用本地大语言模型进行文本到SQL转换

云端的文本转SQL解决方案(如BI助手、数据仓库协作者)会将您的表结构——表名、字段名,有时还包括部分数据行——发送至第三方服务器。对于客户、人力资源或财务类数据库而言,这种情况通常不可接受:仅表结构就已暴露了您的业务逻辑,而示例数据中可能包含个人隐私信息。

本地 LLM 从根源上解决了这个问题:模型通过 Ollama 在您的机器上运行,数据库 schema 保留在本地内存中,生成的查询在您的数据库上执行,没有任何字节经过互联网传输。使用时也无需付费,并且不受任何 API 速率限制。

隐私
数据库结构和数据始终不会离开您的网络——更容易满足 GDPR 合规和商业秘密保护要求。
成本
每次请求免费。数据分析师可反复迭代数百次而无需付费。
可访问性
不具备 SQL 知识的业务用户通过自然语言查询数据库
控制
您决定模型、提示词和安全限制——而非由远程黑箱决定。
!
文本到SQL并非魔法
LLM 生成的是看似合理的 SQL,并不保证 SQL 正确。面对复杂的数据库结构(多重连接、含义不明确的列),仍有实际的出错概率。请将输出视为需要验证的提案,绝不能将其当作可靠的事实依据——尤其是在非技术人员据此做决策时。

#具体如何运作

本地副驾驶套件

本指南带你上手模型。工具包则帮你用上能在你的编辑器中编写代码的编程助手。

  • 在线空间,终身可用
  • PDF + 文件
  • 30 天内退款

使用 LLM 将文本转换为 SQL,原理分为三个步骤。首先,向模型描述数据库结构(即相关表的 DDL)。然后,将用户的问题传给模型,并给出严格指令:只生成一条适用于目标 SQL 方言的查询语句。最后,获取查询语句,验证后以只读方式执行。

  1. 01
    数据库模式自省
    从数据库中提取表的结构(列、类型、键)——自动完成,而非手动操作,以确保同步。
  2. 02
    提示词构建
    构建一个系统提示词,包含所用的 SQL 方言、相关数据库结构以及规则(仅允许 SELECT,必须带 LIMIT,禁止注释)。
  3. 03
    生成
    本地大语言模型返回一条查询语句。我们对其进行清理(移除可能存在的 Markdown ```sql 标记)。
  4. 04
    验证 + 执行
    确认为SELECT语句,通过只读数据库角色执行,返回对应行数据。

#先决条件

Ollama 已安装
守护进程必须监听 http://localhost:11434. 请使用「ollama ps」进行验证。
一个能力足够的模型
新近推出的代码模型(Qwen3-Coder 30B-A3B、Devstral 24B)在 SQL 任务上的表现远优于小型通用模型(参见模型部分)。
Python 3.10+
使用适配的数据库客户端:psycopg2-binary(PostgreSQL)或PyMySQL(MySQL)。
只读数据库访问权限
理想情况下应设置一个仅能执行SELECT操作的专用SQL角色——这是最重要的安全防护。
终端
# Récupérer un modèle adapté au SQL
ollama pull qwen3-coder:30b

# Dépendances Python
pip install ollama psycopg2-binary sqlparse

#将数据库结构提供给模型

这一步决定了结果质量的 80%。模型只有知道表和列的确切名称、它们的数据类型以及彼此之间的关系,才能生成正确的查询。有两种方法:粘贴原始 DDL,或通过数据库内省构建简洁的结构描述。

对于小型数据库(不到二十张表左右),可以将整个数据库结构注入上下文。超过这一规模后,数据库结构会超出有效上下文范围,让模型淹没在信息中:此时需要选择与问题相关的表(通过第一轮检索或业务映射)。下面的 PostgreSQL 结构自省操作可生成 LLM 能够读取的数据库结构。

schema.py
import psycopg2

def get_schema(conn):
    """Retourne le schéma sous forme de CREATE TABLE simplifiés."""
    query = """
        SELECT table_name, column_name, data_type
        FROM information_schema.columns
        WHERE table_schema = 'public'
        ORDER BY table_name, ordinal_position;
    """
    tables = {}
    with conn.cursor() as cur:
        cur.execute(query)
        for table, col, dtype in cur.fetchall():
            tables.setdefault(table, []).append(f"{col} {dtype}")

    lines = []
    for table, cols in tables.items():
        cols_str = ", ".join(cols)
        lines.append(f"TABLE {table} ({cols_str});")
    return "\n".join(lines)
→
添加业务注释
名为“ca_ht”的列对模型来说含义不明确。请为数据库模式添加注释:“ca_ht(不含税营业额,单位:欧元)”。这几句说明能大幅减少选错列的情况。在 PostgreSQL 中,可以通过 information_schema 和 pg_description 获取 COMMENT ON COLUMN 注释。

#使用 Ollama 的完整 Python 处理流程

以下是一个最简但可正常工作的流程:数据库结构 → 提示词 → 生成 → 清理 → 验证 → 执行。它使用 Ollama 的官方 Python 客户端,以及一个只有读取权限的数据库角色。

text_to_sql.py
import re
import ollama
import psycopg2
import sqlparse

MODEL = "qwen3-coder:30b"

SYSTEM_PROMPT = """Tu es un expert PostgreSQL. Génère UNE seule requête SQL
qui répond à la question de l'utilisateur, en respectant ces règles :
- Uniquement des requêtes SELECT (jamais INSERT/UPDATE/DELETE/DROP).
- Utilise exactement les noms de tables et colonnes du schéma fourni.
- Ajoute toujours LIMIT 100 si la question ne précise pas de limite.
- Réponds UNIQUEMENT avec le SQL, sans explication ni balise Markdown.

Schéma de la base :
{schema}"""

def generate_sql(question, schema):
    resp = ollama.chat(
        model=MODEL,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT.format(schema=schema)},
            {"role": "user", "content": question},
        ],
        options={"temperature": 0},  # déterminisme : crucial pour du SQL
    )
    return clean_sql(resp["message"]["content"])

def clean_sql(raw):
    # Retire les fences Markdown ```sql ... ``` si le modèle en ajoute
    raw = re.sub(r"```(?:sql)?", "", raw).strip()
    return raw.rstrip(";") + ";"

执行部分有意将验证与数据库调用分开。在打开游标之前,就会拒绝任何不是单条 SELECT 语句的内容。

text_to_sql.py(续)
def is_read_only(sql):
    statements = sqlparse.parse(sql)
    if len(statements) != 1:
        return False  # une seule requête, pas d'empilement
    stmt = statements[0]
    if stmt.get_type() != "SELECT":
        return False
    forbidden = ("insert", "update", "delete", "drop",
                 "alter", "truncate", "grant", "create")
    lowered = sql.lower()
    return not any(kw in lowered for kw in forbidden)

def run_query(sql):
    if not is_read_only(sql):
        raise ValueError(f"Requête refusée (non lecture seule) : {sql}")
    # Rôle 'readonly' : ne dispose QUE du privilège SELECT côté base
    conn = psycopg2.connect(
        dbname="analytics", user="readonly",
        password="...", host="localhost",
    )
    with conn.cursor() as cur:
        cur.execute("SET statement_timeout = '5s';")  # anti-requête folle
        cur.execute(sql)
        cols = [d[0] for d in cur.description]
        rows = cur.fetchall()
    conn.close()
    return cols, rows

if __name__ == "__main__":
    from schema import get_schema
    ro = psycopg2.connect(dbname="analytics", user="readonly",
                          password="...", host="localhost")
    schema = get_schema(ro)
    question = "Combien de commandes par mois en 2025 ?"
    sql = generate_sql(question, schema)
    print("SQL généré :", sql)
    cols, rows = run_query(sql)
    print(cols)
    for r in rows:
        print(r)
i
temperature = 0
文本转SQL时,始终将温度设置为0。我们不希望有创造性:我们希望得到最可能且可复现的查询。较高的温度会引入列和连接的变动,导致查询执行失败。

#提高生成 SQL 的可靠性与安全性

这一部分是区分演示与实际部署的关键。LLM可能在有人要求时生成破坏性查询,也可能因问题中的提示注入而意外生成这类查询。防御绝不能只依赖提示词:必须在数据库端实施纵深防御。

  1. 01
    数据库只读角色(主要防线)
    创建一个仅拥有 SELECT 权限的 SQL 角色。即使模型生成了 DROP TABLE 语句,数据库也会拒绝执行。这是真正可靠的唯一防护措施:‘GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;’ 且仅此而已。
  2. 02
    应用验证
    执行前,使用 sqlparse 解析 SQL,拒绝所有不是单条 SELECT 语句的内容。再结合数据库角色的权限限制,形成双重防线。
  3. 03
    请求超时
    SET statement_timeout 可防止错误的查询(如在数百万行上产生笛卡尔积)导致数据库资源耗尽。
  4. 04
    强制使用 LIMIT
    在提示和代码侧均设置 LIMIT,以确保从不将整张表加载到内存中。
  5. 05
    纠错循环
    若执行返回SQL错误,请将错误信息返回给模型,并请求修正后的查询(最多尝试1或2次)。
PostgreSQL 只读角色
-- À exécuter une fois par un admin
CREATE ROLE readonly WITH LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE analytics TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
-- Les tables créées plus tard héritent aussi du SELECT
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO readonly;
!
切勿通过字符串插值将问题嵌入 SQL
用户的问题放入 LLM 的提示词中,绝不拼接到 SQL 查询中。实际执行的 SQL 由模型生成,经过验证后,通过 cur.execute(sql) 原样执行,不注入任何用户参数。因此,传统的注入风险转移到了只读验证环节——这也说明了数据库角色的重要性。

纠错循环能显著提高成功率。许多错误都很简单(列名略有偏差、日期函数是某种 SQL 方言特有的),只要模型看到数据库引擎的错误消息,就会在第二次尝试时纠正这些错误。

纠错循环
def answer(question, schema, max_retries=2):
    sql = generate_sql(question, schema)
    for attempt in range(max_retries + 1):
        try:
            return sql, run_query(sql)
        except Exception as e:
            if attempt == max_retries:
                raise
            # On renvoie l'erreur au modèle pour correction
            fix_prompt = (
                f"La requête suivante a échoué :\n{sql}\n\n"
                f"Erreur PostgreSQL : {e}\n"
                f"Corrige la requête. SQL uniquement."
            )
            resp = ollama.chat(model=MODEL, messages=[
                {"role": "system", "content": SYSTEM_PROMPT.format(schema=schema)},
                {"role": "user", "content": fix_prompt},
            ], options={"temperature": 0})
            sql = clean_sql(resp["message"]["content"])

#哪些本地模型在SQL任务上表现优异

SQL 是一项编程任务:专门用于编程的“coder”模型明显优于同等规模的通用模型。2026 年,像 Qwen3-Coder 30B-A3B 这样的近期代码模型彻底改变了局面——2B 到 8B 的小模型只能勉强拼出简单查询,一旦需要多个连接或窗口聚合就会失败。(Codestral 22B 长期以来常被推荐用于 SQL,但如今采用非生产用途许可证:企业使用时应排除该模型。)

Qwen3-Coder 30B-A3B
2026 年的默认选择。面向代码的 MoE 模型,具有 30 亿活跃参数:速度快,256k 上下文可容纳大型数据库模式,Q4 量化后约占 19 GB,可在 RTX 4090 或较新的 Mac 上运行。采用 Apache 2.0 许可证。
Devstral 24B
Mistral AI 推出的编程专用模型(Apache 2.0),Q4 量化后约占 14 GB,可装入 RTX 4080 这类配备 16 GB 显存的显卡。对于配置不高的工作站上的 SQL 任务,这是最佳折中方案。
Qwen 3.8 27B
较新的通用模型,在复杂连接操作上具备扎实的推理能力(约 18 GB,262k 上下文,支持视觉)。请将其推理设置调为「low」:对于 SQL 这样高度结构化的任务,默认设置容易让它过度思考。
小模型 2B–8B (Qwen 3.5 4B, Granite 4.2 8B)
仅适用于非常简单的数据库结构和直接的问题。一旦数据库中的关系不再简单,就应避免使用。
→
量化 Q4_K_M
对于文本到SQL任务,Q4_K_M在质量与显存的平衡上表现最佳。与Q8相比,在这种结构化任务中精度损失可以忽略不计,而显存的节省则允许使用更大规模的模型——模型大小对SQL准确性的贡献远大于量化方式。

#故障排除

模型编造了不存在的列
数据库结构不完整或规模过大。请将范围缩小到相关表,并为含义不明确的列添加业务说明。
回答中在 SQL 前后夹带其他文字
强化「仅返回SQL,不提供任何解释」这一指令,并保留clean_sql中清除Markdown代码块围栏标记的处理。
日期函数错误
在系统提示中明确指定方言(PostgreSQL 与 MySQL 在 DATE_TRUNC、YEAR() 等函数上的行为存在差异)。纠错循环可弥补其余差距。
查询缓慢或超时
statement_timeout生效。请在提示中添加「始终限制在合理的日期范围内」以应对大型表。
Ollama 报错:“Connection refused”
守护进程未启动。请运行「ollama ps」进行检查,并确认服务是否在 http://localhost:11434 上监听。

#深入了解

Text-to-SQL 复用了本站已经介绍过的几个基础组件。以下指南是本指南的延伸:

通过 REST API 在 Python 应用中集成 Ollama
用于通过 FastAPI API 对外提供这条流水线,并处理流式传输和 JSON 模式。
使用 Ollama 进行函数调用和结构化 JSON 输出
另一种方案,可确保输出具有规定的结构(查询语句 + 解释),而不是依靠文本清理。
选择量化方案(Q4、Q5、Q8、FP16)
用于在SQL模型的大小与您显卡的可用显存容量之间进行权衡。
这份指南对您有帮助吗?

有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。