# sql-desde-python
SQL desde Python
Volver a la portada del cursoGestión de Errores en SQL
Sección 4 · Interacción Avanzada con Bases de Datos
Gestión de errores en SQL
Un programa que trabaja con una base de datos va a encontrar errores: un correo que ya existe, un precio negativo, un pedido de un cliente que no está registrado, una tabla mal escrita. La pregunta no es cómo evitar que ocurran, sino qué hacer cuando ocurren: deshacer lo que quedó a medias, avisar algo útil y no esconder el problema. Esta lectura recorre las excepciones de sqlite3, las restricciones que las provocan y los patrones para manejarlas. El notebook transacciones-y-errores provoca cada caso sobre la base de PyShop.
1. La jerarquía de excepciones
sqlite3 sigue la especificación DB-API 2.0 (PEP 249), así que sus excepciones tienen los mismos nombres que en otras bibliotecas de bases de datos de Python. Todas heredan de sqlite3.Error; las que vienen de la base heredan además de sqlite3.DatabaseError:
IntegrityError: los datos violan una restricción (UNIQUE,CHECK,NOT NULL,FOREIGN KEY). El SQL es correcto; el problema son los valores.OperationalError: algo impide ejecutar la operación: una tabla que no existe, SQL mal escrito, una base bloqueada por otra conexión o un archivo que no se puede abrir.ProgrammingError: uso incorrecto de la API, como pasar más parámetros que marcadores o usar una conexión ya cerrada.
Existen otras (DataError, InternalError, NotSupportedError, InterfaceError), menos frecuentes. La cadena de herencia de IntegrityError es IntegrityError → DatabaseError → Error → Exception: capturar sqlite3.Error atrapa cualquiera de ellas.
2. Cada restricción, su error
El esquema de PyShop declara restricciones en cada tabla. El notebook intenta violarlas una por una; estos son los mensajes reales:
| Intento | Excepción y mensaje |
|---|---|
| Cliente con un correo ya registrado | IntegrityError: UNIQUE constraint failed: clientes.correo |
| Producto con precio -5 | IntegrityError: CHECK constraint failed: precio > 0 |
| Pedido del cliente 9999 | IntegrityError: FOREIGN KEY constraint failed |
| Producto sin nombre | IntegrityError: NOT NULL constraint failed: productos.nombre |
SELECT * FROM producto | OperationalError: no such table: producto |
SELEC * FROM productos | OperationalError: near "SELEC": syntax error |
Un ? y dos valores | ProgrammingError: Incorrect number of bindings supplied |
Un caso merece atención: las claves foráneas. SQLite no revisa REFERENCES salvo que la conexión ejecute PRAGMA foreign_keys = ON, y esa configuración vale solo para esa conexión. Sin el PRAGMA, el pedido del cliente 9999 se inserta sin error y queda huérfano. Por eso conviene activarlo en la función que abre la conexión, una sola vez.
3. Errores y transacciones
Cuando una sentencia falla dentro de una transacción, las sentencias anteriores de esa transacción siguen pendientes. Si el código atrapa el error y sigue como si nada, el siguiente commit() guarda un estado a medias. La regla: si algo falla, deshaz todo. with con: lo hace solo: confirma si el bloque termina bien y llama a rollback() si sale una excepción, que después se vuelve a lanzar.
def registrar_cliente(con, nombre, correo, ciudad):
try:
with con:
cur = con.execute(
"INSERT INTO clientes (nombre, correo, ciudad, alta) "
"VALUES (?, ?, ?, '2025-10-07')",
(nombre, correo, ciudad))
except sqlite3.IntegrityError as e:
if "UNIQUE" in str(e):
return False, f"Ya existe un cliente con el correo {correo}."
return False, "Faltan datos obligatorios del cliente."
else:
return True, cur.lastrowid
La función atrapa solo IntegrityError, el error que sabe explicar, y lo traduce a un mensaje para el usuario. Un OperationalError (por ejemplo, la tabla no existe) no se atrapa: sube, porque indica un fallo del programa y no de los datos. El bloque else corre solo si no hubo error; finally, si lo usas, corre siempre y es el lugar para cerrar recursos.
4. Excepciones propias
Un mensaje como «CHECK constraint failed: stock >= 0» le sirve al programador, no a quien hace el pedido. Una excepción propia lleva información de negocio:
class StockInsuficiente(Exception):
pass
fila = con.execute(
"SELECT nombre, stock FROM productos WHERE id = ?", (15,)
).fetchone()
if fila[1] < 10:
raise StockInsuficiente(f"{fila[0]}: pides 10, hay {fila[1]}")
Validar antes de escribir permite un mensaje claro («Juego de cuchillos: pides 10, hay 9»), y el CHECK de la tabla queda como última red de seguridad. Al traducir un error, usa raise MiError(...) from e para conservar el original en el rastreo.
Trampas comunes
except Exception: pass. Esconde el error y puede dejar una transacción abierta con cambios a medias que el siguientecommitguardará.- Capturar demasiado. Atrapar
sqlite3.Errorpara mostrar «correo repetido» también disfraza una tabla mal escrita. - Olvidar
PRAGMA foreign_keys = ON. Sin él, SQLite acepta referencias a filas que no existen. - Mostrar el mensaje técnico al usuario. Regístralo para depurar; muestra uno que explique qué corregir.
- Decidir por el texto del mensaje sin necesidad. Úsalo solo para distinguir restricciones; el tipo de excepción es la primera señal.
En el notebook, provoca cada error de la tabla y escribe clase_de_error(con, sql), que devuelve el nombre de la excepción sin dejar cambios pendientes. Es la mejor forma de aprender a reconocerlas.