Uso de IA no trabalho

Verificar uma consulta SQL escrita por IA com resultados do SQLite calculados à mão

Trate uma consulta SQL gerada por IA como um rascunho e teste-a em um pequeno banco SQLite com respostas conhecidas. Compare o resultado da IA com uma tabela esperada calculada manualmente antes de usar a consulta em dados reais.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é para quem usa IA para criar rascunhos de SQL e quer um teste pequeno e reproduzível antes de executar a consulta em dados importantes.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • Uma pasta de trabalho em que os scripts possam criar pastas dentro de outputs.
  • Familiaridade básica com SELECT, JOIN, GROUP BY e funções de agregação.
  • É necessária apenas a biblioteca padrão do Python: csv, pathlib e sqlite3.

01Defina a pergunta de negócio antes de revisar o SQL

Suponha que o requisito seja: para cada cliente, informar o valor total dos pedidos PAID feitos durante agosto de 2026. Clientes sem pedidos que atendam às condições ainda devem aparecer com total 0.

Um assistente de IA propõe um INNER JOIN entre customers e orders, seguido de condições WHERE para status e date. A consulta parece razoável, mas o INNER JOIN remove clientes que não têm linhas de pedido correspondentes. Isso viola o requisito de incluir clientes com total zero.

02Crie um banco de dados SQLite sintético

O banco de dados a seguir é sintético e foi criado especificamente para este artigo. Ele tem três clientes e seis pedidos escolhidos para testar status pagos e não pagos, limites entre meses e um cliente sem pedido PAID em agosto.

python
import sqlite3
from pathlib import Path

SOURCE_DIR = Path("outputs") / "ai_sql_demo"
DATABASE = SOURCE_DIR / "orders.sqlite"

CUSTOMERS = [
    (1, "Ana"),
    (2, "Ben"),
    (3, "Cara"),
]

ORDERS = [
    (101, 1, "2026-08-03", "PAID", 40),
    (102, 1, "2026-08-20", "PAID", 60),
    (103, 1, "2026-08-25", "CANCELLED", 30),
    (104, 2, "2026-08-10", "PAID", 25),
    (105, 2, "2026-09-01", "PAID", 50),
    (106, 3, "2026-08-12", "CANCELLED", 80),
]

SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir()  # Stop if the synthetic source already exists.

connection = sqlite3.connect(DATABASE)
try:
    connection.executescript("""
        CREATE TABLE customers (
            customer_id INTEGER PRIMARY KEY,
            customer_name TEXT NOT NULL
        );

        CREATE TABLE orders (
            order_id INTEGER PRIMARY KEY,
            customer_id INTEGER NOT NULL,
            order_date TEXT NOT NULL,
            status TEXT NOT NULL,
            amount INTEGER NOT NULL,
            FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
        );
    """)
    connection.executemany(
        "INSERT INTO customers VALUES (?, ?)", CUSTOMERS
    )
    connection.executemany(
        "INSERT INTO orders VALUES (?, ?, ?, ?, ?)", ORDERS
    )
    connection.commit()
finally:
    connection.close()
text
python create_ai_sql_demo.py

03Calcule a resposta correta à mão

Ana tem dois pedidos PAID em agosto: 40 e 60, então o total dela é 100. O pedido CANCELLED não conta. Ben tem um pedido válido em agosto no valor de 25; o pedido de setembro não conta. Cara tem apenas um pedido CANCELLED em agosto, então ainda precisa aparecer com 0.

customer_idcustomer_nameTotal PAID esperado de agosto
1Ana100
2Ben25
3Cara0

O total geral esperado é 125. Deve haver exatamente 3 linhas de saída porque o requisito diz que todos os clientes devem aparecer.

04Compare a consulta da IA com uma consulta revisada

A consulta da IA abaixo filtra os pedidos válidos na cláusula WHERE depois de um INNER JOIN. Cara não tem nenhum pedido PAID em agosto que corresponda, então desaparece completamente.

sql
SELECT
    c.customer_id,
    c.customer_name,
    SUM(o.amount) AS august_paid_total
FROM customers AS c
JOIN orders AS o
    ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
  AND o.order_date >= '2026-08-01'
  AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;

A consulta revisada parte de todos os clientes e usa LEFT JOIN. Os filtros de pedidos são colocados dentro da condição ON para que clientes sem correspondência sejam mantidos. COALESCE converte o agregado NULL resultante em 0.

sql
SELECT
    c.customer_id,
    c.customer_name,
    COALESCE(SUM(o.amount), 0) AS august_paid_total
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
   AND o.status = 'PAID'
   AND o.order_date >= '2026-08-01'
   AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;

05Execute as duas consultas e compare com as linhas esperadas

Salve o script a seguir como ai_sql_check.py. Ele executa as duas consultas, compara os resultados com a tabela esperada calculada à mão e grava um relatório CSV de revisão. O banco sintético não é modificado.

python
import csv
import sqlite3
from pathlib import Path

DATABASE = Path("outputs") / "ai_sql_demo" / "orders.sqlite"
OUTPUT_DIR = Path("outputs") / "ai_sql_check_result"
REPORT = OUTPUT_DIR / "sql_check.csv"

AI_SQL = """
SELECT c.customer_id, c.customer_name, SUM(o.amount)
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
  AND o.order_date >= '2026-08-01'
  AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;
"""

REVISED_SQL = """
SELECT c.customer_id, c.customer_name, COALESCE(SUM(o.amount), 0)
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
   AND o.status = 'PAID'
   AND o.order_date >= '2026-08-01'
   AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;
"""

EXPECTED = [
    (1, "Ana", 100),
    (2, "Ben", 25),
    (3, "Cara", 0),
]


def run_query(connection, sql):
    return connection.execute(sql).fetchall()


def main() -> None:
    if not DATABASE.is_file():
        raise FileNotFoundError(f"Database not found: {DATABASE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    connection = sqlite3.connect(f"file:{DATABASE.resolve()}?mode=ro", uri=True)
    try:
        ai_rows = run_query(connection, AI_SQL)
        revised_rows = run_query(connection, REVISED_SQL)
    finally:
        connection.close()

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()

    with REPORT.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.writer(stream)
        writer.writerow(["query", "rows", "matches_expected"])
        writer.writerow(["AI", repr(ai_rows), ai_rows == EXPECTED])
        writer.writerow(["REVISED", repr(revised_rows), revised_rows == EXPECTED])

    print(f"Expected rows: {len(EXPECTED)}.")
    print(f"AI rows: {len(ai_rows)}; matches expected: {ai_rows == EXPECTED}.")
    print(
        f"Revised rows: {len(revised_rows)}; "
        f"matches expected: {revised_rows == EXPECTED}."
    )
    print(f"Report: {REPORT.as_posix()}")

    if revised_rows != EXPECTED:
        raise RuntimeError("Revised SQL does not match the expected result.")


if __name__ == "__main__":
    main()
text
python ai_sql_check.py

06Confira a saída esperada

A consulta da IA deve retornar apenas Ana e Ben, produzindo 2 linhas e não correspondendo ao resultado esperado. A consulta revisada deve retornar todos os 3 clientes e corresponder exatamente à tabela calculada à mão.

ConsultaLinhas retornadasTotal geral esperadoCorresponde à tabela esperada
IA2125Não
Revisada3125Sim

O resultado da IA ainda pode ter o total geral correto de 125 mesmo estando errado, porque a linha obrigatória de Cara com valor zero está ausente. Por isso, verificar apenas os totais é insuficiente.

text
Expected rows: 3.
AI rows: 2; matches expected: False.
Revised rows: 3; matches expected: True.
Report: outputs/ai_sql_check_result/sql_check.csv

07Adicione verificações e entenda os limites

  • Confira a quantidade de linhas, além dos totais.
  • Inclua clientes com zero registros correspondentes nos dados sintéticos quando o requisito disser que eles devem aparecer.
  • Teste limites de data como August 31 e September 1.
  • Inclua status excluídos, como CANCELLED.
  • Execute o verificador novamente sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError em vez de sobrescrever o relatório.
Problema comumO que verificar
Linhas ausentes inesperadamenteConfira o tipo de JOIN e se filtros em WHERE removem linhas não correspondentes de um LEFT JOIN.
Totais altos demaisProcure joins um-para-muitos que dupliquem linhas antes da agregação.
Intervalo de datas incorretoUse um limite inferior explícito e um limite superior exclusivo adequado ao formato de data armazenado.
NULL em vez de zeroDecida se correspondências ausentes devem permanecer NULL ou ser convertidas com COALESCE.
Total correto, mas detalhes erradosCompare todas as linhas esperadas, não apenas o total geral.

Este teste comprova apenas que a consulta revisada corresponde a este pequeno exemplo sintético. Esquemas reais podem conter relações duplicadas, valores NULL, timestamps, time zones, refunds ou regras de negócio adicionais. Amplie os dados de teste sempre que essas condições forem importantes.

Registro de execução e verificação

2026-09-20 · exemplo verificado manualmente · alvo: Python 3.12 · biblioteca padrão: csv, pathlib, sqlite3 · sem execução

  • O total válido de Ana em agosto foi calculado manualmente como 40 + 60 = 100.
  • O total válido de Ben em agosto foi calculado manualmente como 25, excluindo o pedido de setembro.
  • Foi determinado manualmente que Cara deve aparecer com 0 porque seu único pedido de agosto é CANCELLED.
  • O total geral esperado foi calculado manualmente como 125 e a quantidade esperada de linhas como 3.
  • A consulta da IA foi inspecionada e foi determinado que o INNER JOIN e os filtros WHERE omitem Cara, produzindo 2 linhas.
  • A consulta revisada com LEFT JOIN foi inspecionada e esperava-se que retornasse as três linhas calculadas à mão.
  • A saída esperada do console e a comparação do relatório foram derivadas manualmente.
Limites da verificação
  • O autor desta resposta não executou o código; o comportamento do SQLite e do sistema de arquivos não foi testado aqui.
  • Apenas as linhas sintéticas listadas foram avaliadas; valores NULL em amount, joins duplicados, timestamps, time zones, refunds e conjuntos grandes não foram testados.
  • Corresponder a este exemplo não prova que a consulta esteja correta para todo esquema de produção ou regra de negócio.
  • As URLs da documentação oficial foram fornecidas a partir de locais conhecidos, mas não foram verificadas em tempo real.

Princípios de redação e verificação de todo o site

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.