Avançado 13 minSQL

Text-to-SQL local: consultar sua base de dados em linguagem naturel

O text-to-SQL com um LLM local permite fazer uma pergunta em francês (« quel est le chiffre d'affaires par région le mois dernier ? ») e obter uma consulta SQL executável no seu PostgreSQL ou MySQL — sem que o esquema nem os dados saiam da sua infraestrutura. Este guia aborda o funcionamento real: injetar o esquema no contexto, construir um pipeline Python com Ollama e, sobretudo, estabelecer as salvaguardas (somente leitura, validação, limites) sem as quais nenhuma solução de text-to-SQL pode ser implantada em produção.

Por Mohamed Meguedmi·Atualização 2026-08-27·Testado no Windows, macOS e Linux

#Por que usar text-to-SQL com um LLM local

As soluções de text-to-SQL na nuvem (assistentes de BI, copilotos de data warehouse) enviam seu esquema — nomes de tabelas, de colunas e, às vezes, amostras de linhas — para um servidor de terceiros. Para uma base de dados de clientes, de RH ou financeira, isso costuma ser impeditivo: o esquema por si só já revela a estrutura do seu negócio, e as amostras contêm dados pessoais.

Um LLM local resolve esse problema na raiz: o modelo roda na sua máquina via Ollama, o esquema permanece na memória local e a consulta gerada é executada na sua base de dados sem que nenhum byte passe pela internet. Também é gratuito para usar e independente de qualquer limite de taxa de requisições de API.

Privacidade
O esquema e os dados nunca saem da sua rede — isso facilita a conformidade com o RGPD e a proteção dos segredos comerciais.
Custo
Nenhum custo por requisição. Um analista de dados pode iterar centenas de vezes sem pagar.
Acessibilidade
Usuários das áreas de negócio que não conhecem SQL consultam o banco de dados em linguagem natural.
Controle
Você escolhe o modelo, o prompt e as salvaguardas — não há uma caixa-preta remota.
!
O text-to-SQL não é mágico
Um LLM gera SQL plausível, não SQL com garantia de estar correto. Em esquemas complexos (múltiplas junções, colunas ambíguas), a taxa de erro continua sendo uma realidade. Trate a saída como uma proposta a ser validada, nunca como uma fonte de verdade — especialmente se uma pessoa sem conhecimentos técnicos se basear nela para tomar decisões.

#Como funciona na prática

O kit Copiloto Local

Este guia leva você ao modelo. O kit leva você ao copiloto que programa no seu editor.

  • Espaço online vitalício
  • PDF + arquivos
  • Atualizações vitalícias

O princípio do text to SQL com um LLM se resume a três etapas. Primeiro, descrevemos o esquema do banco de dados para o modelo (o DDL das tabelas relevantes). Em seguida, enviamos a ele a pergunta do usuário com uma instrução rigorosa: produzir apenas uma consulta SQL para o dialeto de destino. Por fim, recuperamos a consulta, a validamos e a executamos em modo somente leitura.

  1. 01
    Introspecção do esquema
    Extraímos a estrutura das tabelas (colunas, tipos, chaves) da base — automaticamente, em vez de manualmente, para manter a sincronização.
  2. 02
    Construção do prompt
    Montamos um prompt de sistema contendo o dialeto SQL, o esquema relevante e as regras (apenas SELECT, LIMIT obrigatório, sem comentários).
  3. 03
    Geração
    O LLM local retorna uma consulta. Ela é limpa (remoção dos delimitadores Markdown ```sql, se houver).
  4. 04
    Validação + execução
    Verificamos se é realmente um SELECT, executamos a consulta usando uma função de banco de dados com acesso somente de leitura e retornamos as linhas.

#Pré-requisitos

Ollama instalado
O daemon deve escutar em http://localhost:11434. Verifique com « ollama ps ».
Um modelo capaz
Um modelo de código recente (Qwen3-Coder 30B-A3B, Devstral 24B) dá resultados muito melhores em SQL do que um modelo generalista pequeno (ver a seção de modelos).
Python 3.10+
Com o cliente de banco de dados adequado: psycopg2-binary (PostgreSQL) ou PyMySQL (MySQL).
Acesso ao banco de dados somente para leitura
Idealmente, um papel SQL dedicado que só permita executar SELECT — o mecanismo de proteção mais importante.
Terminal
# Récupérer un modèle adapté au SQL
ollama pull qwen3-coder:30b

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

#Fornecer o esquema do seu banco de dados ao modelo

Esta é a etapa que determina 80 % da qualidade do resultado. O modelo só consegue gerar uma consulta correta se conhecer os nomes exatos das tabelas e colunas, seus tipos e as relações entre elas. Duas abordagens: colar o DDL bruto ou fazer a introspecção do banco de dados para construir uma descrição compacta.

Para um banco de dados pequeno (menos de cerca de vinte tabelas), é possível injetar tudo. Acima disso, o esquema ultrapassa o contexto útil e sobrecarrega o modelo: é necessário então selecionar as tabelas relevantes para a pergunta (por meio de uma primeira etapa de busca ou de um mapeamento de negócio). Veja a seguir uma introspecção do PostgreSQL que gera um esquema legível pelo 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)
→
Adicione comentários de negócio
Uma coluna « ca_ht » é ambígua para o modelo. Enriqueça o esquema com anotações: « ca_ht (faturamento sem impostos, em euros) ». Essas poucas palavras reduzem drasticamente os erros de seleção de coluna. No PostgreSQL, os COMMENT ON COLUMN podem ser recuperados via information_schema e pg_description.

#Pipeline Python completo com Ollama

Aqui está um pipeline mínimo, mas funcional: esquema → prompt → geração → limpeza → validação → execução. Ele utiliza o cliente Python oficial do Ollama e um papel de banco de dados com acesso somente para leitura.

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(";") + ";"

A parte de execução separa deliberadamente a validação da chamada ao banco de dados. Tudo o que não for um único SELECT é rejeitado antes mesmo de abrir o cursor.

text_to_sql.py (continuação)
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
Para text-to-SQL, sempre defina a temperatura em 0. Não queremos criatividade: queremos a consulta mais provável e reprodutível. Uma temperatura alta introduz variações de colunas e junções que fazem a execução falhar.

#Tornar o SQL gerado confiável e seguro

É a seção que diferencia uma demonstração de uma implantação real. Um LLM pode gerar uma consulta destrutiva se isso lhe for solicitado — ou por acidente, por meio de uma injeção na pergunta. A defesa nunca deve depender apenas do prompt: ela deve atuar em várias camadas, no banco de dados.

  1. 01
    Papel no banco de dados com acesso somente para leitura (defesa principal)
    Crie um papel SQL que tenha APENAS o privilégio SELECT. Mesmo que o modelo gere um DROP TABLE, a base o recusa. Esse é o único mecanismo realmente confiável: 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;' e nada mais.
  2. 02
    Validação na aplicação
    Antes da execução, analise o SQL com sqlparse e rejeite tudo que não seja uma única instrução SELECT. Isso cria uma dupla barreira junto com o papel de acesso no banco de dados.
  3. 03
    Timeout de consulta
    O comando SET statement_timeout impede que uma consulta mal formulada (produto cartesiano sobre milhões de linhas) sature o banco de dados.
  4. 04
    LIMIT forçado
    Imponha um LIMIT tanto no prompt QUANTO no código, para nunca carregar tabelas inteiras na memória.
  5. 05
    Loop de correção
    Se a execução retornar um erro SQL, envie a mensagem de erro de volta ao modelo e solicite uma consulta corrigida (no máximo 1 ou 2 tentativas).
Papel de acesso somente de leitura no 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;
!
Nunca interpolar a pergunta no SQL
A pergunta do usuário vai para o prompt do LLM, nunca concatenada em uma consulta. O SQL executado é o produzido pelo modelo, validado e executado tal como foi gerado via cur.execute(sql), sem nenhum parâmetro do usuário injetado. O risco clássico de injeção se desloca, portanto, para a validação de que a consulta é somente de leitura — daí a importância do papel de acesso no banco de dados.

O ciclo de correção melhora significativamente a taxa de sucesso. Muitos erros são triviais (nome de coluna ligeiramente incorreto, função de data específica do dialeto) e o modelo os corrige na segunda tentativa se vir a mensagem de erro do mecanismo de banco de dados.

Loop de correção
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"])

#Quais modelos locais se destacam em SQL

O SQL é uma tarefa de programação: os modelos especializados em código superam claramente os generalistas de mesmo tamanho. Em 2026, um modelo de código recente como Qwen3-Coder 30B-A3B muda tudo — os pequenos modelos de 2B a 8B improvisam consultas simples, mas falham quando são necessárias várias junções ou uma agregação com função de janela. (Codestral 22B, por muito tempo citado para SQL, agora está sob uma licença que não permite uso em produção: deve ser descartado em empresas.)

Qwen3-Coder 30B-A3B
A escolha padrão em 2026. Modelo MoE voltado a código com 3B de parâmetros ativos: rápido, 256k de contexto para grandes esquemas, ≈19 GB em Q4 em uma RTX 4090 ou em um Mac recente. Licença Apache 2.0.
Devstral 24B
Especialista em código desenvolvido pela Mistral AI (Apache 2.0), ≈14 GB em Q4 — cabe em uma placa de 16 GB, como a RTX 4080. O melhor equilíbrio para SQL em uma estação de trabalho modesta.
Qwen 3.8 27B
Modelo generalista recente com raciocínio sólido em junções complexas (≈18 GB, 262k de contexto, visão). Ajuste o nível de raciocínio para « low »: em uma tarefa tão estruturada quanto o SQL, ele tende a raciocinar em excesso com a configuração padrão.
Modelos pequenos 2B–8B (Qwen 3.5 4B, Granite 4.2 8B)
Apenas para esquemas muito simples e perguntas diretas. Evitar assim que a base tiver relações não triviais.
→
Quantização Q4_K_M
Para text-to-SQL, Q4_K_M oferece o melhor equilíbrio entre qualidade e VRAM. A perda de precisão em relação à Q8 é desprezível nessa tarefa estruturada, enquanto o ganho de VRAM permite usar um modelo maior — e o tamanho do modelo conta muito mais do que a quantização para a precisão do SQL.

#Solução de problemas

O modelo inventa colunas
O esquema está incompleto ou muito grande. Reduza às tabelas relevantes e adicione comentários de negócio nas colunas ambíguas.
Respostas com texto ao redor do SQL
Reforce a instrução « Apenas SQL, sem explicações » e mantenha a remoção dos delimitadores de blocos de código Markdown em clean_sql.
Erros na função de data
Especifique o dialeto no prompt de sistema (PostgreSQL e MySQL diferem em DATE_TRUNC, YEAR(), etc.). O loop de correção resolve o restante.
Consultas lentas ou que ultrapassam o tempo limite
O statement_timeout está funcionando. Adicione "filtrar sempre em uma faixa de datas razoável" ao prompt para tabelas grandes.
« Connection refused » no Ollama
O daemon não está em execução. Verifique o comando 'ollama ps' e confira se o serviço está ouvindo em http://localhost:11434.

#Para se aprofundar

O text-to-SQL reutiliza vários componentes já abordados no site. Estes guias dão continuidade a este guia:

Integrar Ollama em um aplicativo Python via API REST
Para expor esse pipeline por meio de uma API FastAPI, gerenciar o streaming e o modo JSON.
Function calling e saídas JSON estruturadas com Ollama
Uma alternativa para estruturar a saída (consulta + explicação) de forma garantida, em vez de recorrer à limpeza de texto.
Escolher sua quantização (Q4, Q5, Q8, FP16)
Para equilibrar o tamanho do modelo SQL com a VRAM disponível na sua placa.
Este guia ajudou você?

Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.