Uso de IA en el trabajo

Comprobar una consulta SQL escrita por IA frente a resultados de SQLite calculados a mano

Trata una consulta SQL generada por IA como un borrador y pruébala en una pequeña base de datos SQLite con respuestas conocidas. Compara el resultado de la IA con una tabla esperada calculada manualmente antes de usar la consulta con datos reales.

Ver el índice

La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English

Para quién esEsta guía es para personas que usan IA para redactar SQL y quieren una prueba pequeña y reproducible antes de ejecutar la consulta sobre datos importantes.

Preparación
  • Python 3.12 y un comando de terminal que inicie esa versión.
  • Una carpeta de trabajo donde los scripts puedan crear carpetas dentro de outputs.
  • Conocimientos básicos de SELECT, JOIN, GROUP BY y funciones de agregación.
  • Solo se necesita la biblioteca estándar de Python: csv, pathlib y sqlite3.

01Define la pregunta de negocio antes de revisar el SQL

Supongamos que el requisito es: para cada cliente, informar el valor total de los pedidos PAID realizados durante agosto de 2026. Los clientes sin pedidos que cumplan las condiciones deben seguir apareciendo con un total de 0.

Un asistente de IA propone un INNER JOIN entre customers y orders, seguido de condiciones WHERE para status y date. La consulta parece razonable, pero el INNER JOIN elimina a los clientes que no tienen filas de pedido coincidentes. Esto incumple el requisito de incluir a los clientes con total cero.

02Crea una base de datos SQLite sintética

La siguiente base de datos es sintética y se creó específicamente para este artículo. Tiene tres clientes y seis pedidos seleccionados para probar estados pagados y no pagados, límites entre meses y un cliente sin pedidos PAID en 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

03Calcula manualmente la respuesta correcta

Ana tiene dos pedidos PAID en agosto: 40 y 60, por lo que su total es 100. Su pedido CANCELLED no cuenta. Ben tiene un pedido válido de agosto por valor de 25; su pedido de septiembre no cuenta. Cara solo tiene un pedido CANCELLED en agosto, por lo que debe seguir apareciendo con 0.

customer_idcustomer_nameTotal PAID esperado de agosto
1Ana100
2Ben25
3Cara0

El total general esperado es 125. Debe haber exactamente 3 filas de salida porque el requisito indica que todos los clientes deben aparecer.

04Compara la consulta de IA con una consulta revisada

La consulta de IA siguiente filtra los pedidos válidos en la cláusula WHERE después de un INNER JOIN. Cara no tiene ningún pedido PAID de agosto que coincida, por lo que desaparece por completo.

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;

La consulta revisada parte de todos los clientes y utiliza LEFT JOIN. Los filtros de pedidos se colocan dentro de la condición ON para conservar a los clientes sin coincidencias. COALESCE convierte el agregado NULL resultante en 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;

05Ejecuta ambas consultas y compáralas con las filas esperadas

Guarda el siguiente script como ai_sql_check.py. Ejecuta ambas consultas, compara sus resultados con la tabla esperada calculada a mano y escribe un informe CSV de revisión. No modifica la base de datos sintética.

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

06Comprueba la salida esperada

La consulta de IA debe devolver solo a Ana y Ben, por lo que produce 2 filas y no coincide con el resultado esperado. La consulta revisada debe devolver a los 3 clientes y coincidir exactamente con la tabla calculada a mano.

ConsultaFilas devueltasTotal general esperadoCoincide con la tabla esperada
IA2125No
Revisada3125Sí

El resultado de la IA puede seguir teniendo el total general correcto de 125 aunque sea incorrecto, porque falta la fila obligatoria de Cara con valor cero. Por eso no basta con comprobar solo los totales.

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

07Añade comprobaciones y entiende los límites

  • Comprueba el número de filas además de los totales.
  • Incluye en los datos sintéticos clientes con cero registros coincidentes cuando el requisito indique que deben aparecer.
  • Prueba límites de fecha como August 31 y September 1.
  • Incluye estados excluidos como CANCELLED.
  • Vuelve a ejecutar el comprobador sin cambiar OUTPUT_DIR. Debe detenerse con FileExistsError en lugar de sobrescribir el informe.
Problema comúnQué revisar
Faltan filas de forma inesperadaComprueba el tipo de JOIN y si los filtros en WHERE eliminan filas no coincidentes de un LEFT JOIN.
Totales demasiado altosBusca joins uno-a-muchos que dupliquen filas antes de la agregación.
Intervalo de fechas incorrectoUsa un límite inferior explícito y un límite superior exclusivo adecuado al formato de fecha almacenado.
NULL en lugar de ceroDecide si las coincidencias ausentes deben quedarse como NULL o convertirse con COALESCE.
Total correcto pero detalle incorrectoCompara todas las filas esperadas, no solo el total general.

Esta prueba solo demuestra que la consulta revisada coincide con este pequeño ejemplo sintético. Los esquemas reales pueden contener relaciones duplicadas, valores NULL, timestamps, time zones, refunds o reglas de negocio adicionales. Amplía los datos de prueba cuando esas condiciones sean importantes.

Registro de ejecución y verificación

ChatGPT Python tool · Python 3.13.5 · standard library csv/pathlib/sqlite3

  • Las filas esperadas calculadas manualmente se confirmaron como (1, Ana, 100), (2, Ben, 25) y (3, Cara, 0), con un total general de 125.
  • La consulta de IA devolvió exactamente 2 filas: Ana=100 y Ben=25. Cara se omitió, por lo que el resultado no coincidió con EXPECTED.
  • La consulta revisada con LEFT JOIN devolvió exactamente 3 filas: Ana=100, Ben=25 y Cara=0, coincidiendo exactamente con EXPECTED.
  • La ejecución principal imprimió exactamente: 'Expected rows: 3.', 'AI rows: 2; matches expected: False.', 'Revised rows: 3; matches expected: True.' y 'Report: outputs/ai_sql_check_result/sql_check.csv'.
  • sql_check.csv contenía un encabezado y dos filas de datos. La fila AI registró "[(1, 'Ana', 100), (2, 'Ben', 25)]" con False, y la fila REVISED registró "[(1, 'Ana', 100), (2, 'Ben', 25), (3, 'Cara', 0)]" con True.
  • El resultado de IA seguía sumando 125 a pesar de omitir a Cara, confirmando la afirmación del artículo de que el total general por sí solo no es suficiente.
  • El hash SHA-256 de orders.sqlite permaneció sin cambios después de ejecutar el comprobador, confirmando que la base de datos se abrió en modo de solo lectura y no se modificó.
  • Una segunda ejecución del comprobador se detuvo con FileExistsError porque outputs/ai_sql_check_result ya existía.
Límites de la verificación
  • No se realizaron pruebas de estrés específicas sobre valores amount NULL, relaciones de unión duplicadas, casos adicionales de límites de fechas como el 31 de agosto, marcas de tiempo, zonas horarias, reembolsos, bases de datos dañadas, fallos de permisos ni conjuntos de datos grandes.
  • El entorno de Python emitió en stderr una advertencia no relacionada del calentamiento de la herramienta de hojas de cálculo artifact-tool, pero ambos scripts del artículo se ejecutaron correctamente y el comprobador terminó con código 0 en la ejecución de verificación.

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.