46 · SQLite con sqlite3: conexión, cursor, CRUD
sqlite3 está en la biblioteca estándar — sin configuración, un solo archivo, totalmente ACID. Domina el ciclo conexión → cursor → execute → commit, aprende a usar consultas parametrizadas (la única forma segura de construir SQL) y ve por qué ocurren los ataques de inyección y cómo los parámetros los detienen.
SQLite vive dentro de tu proceso — sin servidor, sin instalación, sin puerto. Un único archivo `.db` contiene una base de datos relacional con soporte SQL completo, transacciones ACID y lecturas concurrentes. El módulo `sqlite3` viene con cada instalación de CPython, así que literalmente **no hay nada que instalar**. El patrón es siempre el mismo: abrir una conexión, obtener un cursor, ejecutar SQL con marcadores `?` para los datos, hacer commit, cerrar — o dejar que el gestor de contexto `with` se encargue de los dos últimos pasos.
Sin esto:
Sin conocer SQLite recurrirás a archivos JSON o CSVs para persistir resultados de experimentos y configuración — y luego pelearás con bugs de serialización, corrupción por escrituras concurrentes y la falta de un lenguaje de consultas. SQLite te da todo eso gratis, con un import.
SQLite es el motor de base de datos más ampliamente desplegado del mundo — impulsa iOS, Android, Firefox, WhatsApp e innumerables sistemas embebidos. La idea clave: no es un servidor. La base de datos vive en un único archivo ordinario y el motor de consultas corre dentro de tu propio proceso a través de la biblioteca C sqlite3, que Python envuelve en su módulo de biblioteca estándar del mismo nombre.
¿Por qué importa esto para Python/ML?
- Cero infraestructura —
import sqlite3es todo lo que necesitas. Sin contenedor Docker, sin credenciales, sin puerto que abrir. - SQL completo —
JOIN,GROUP BY, funciones de ventana, índices, claves foráneas. No es un juguete. - Transacciones ACID — las escrituras son seguras aunque tu proceso se cuelgue a mitad de operación.
- Un archivo = portable — copia el archivo
.dbdonde quieras y la base de datos viaja con él.
El ciclo de vida de la conexión
conn = sqlite3.connect(ruta) # abrir o crear
cur = conn.cursor() # ejecutor de sentencias
cur.execute(sql, params) # ejecutar SQL
conn.commit() # volcar al disco
conn.close() # liberar el manejador
Usa ':memory:' como ruta para una base de datos efímera en proceso que desaparece cuando la conexión se cierra — ideal para pruebas y scripts de un solo uso. Usa '/tmp/foo.db' o cualquier ruta real para persistencia.
Leer resultados
Tras un SELECT, el cursor se convierte en un iterador sobre filas:
cur.fetchone()— siguiente fila como tupla, oNonesi el conjunto de resultados está vacío.cur.fetchall()— todas las filas restantes como lista de tuplas.for row in cur:— iterar de forma perezosa (amigable con la memoria para resultados grandes).
Atajo con gestor de contexto
with sqlite3.connect(ruta) as conn: hace commit automáticamente al salir sin errores y rollback si hay una excepción. Aún llamas a conn.cursor() dentro del bloque; el with solo gestiona el límite de transacción, no conn.close().
Python (in browser)
`executemany` inserta una secuencia de filas en una sola llamada — más eficiente que un bucle de llamadas `execute` individuales. Observa cómo cada valor se pasa como elemento de una tupla, nunca interpolado directamente en la cadena SQL.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
El driver Python de SQLite bloquea las llamadas `execute()` con múltiples sentencias, por lo que el DROP TABLE nunca se ejecuta — pero el principio aplica en todas las bases de datos. La forma parametrizada es la **única** forma correcta independientemente del motor de BD.
Python (in browser)
El bloque `with sqlite3.connect(...) as conn:` hace commit cuando el cuerpo del `with` se completa con éxito y hace rollback si se lanza una excepción — automatizando el manejo de errores. Aún necesitas llamar a `conn.close()` explícitamente cuando termines de verdad.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
El gotcha #3 es especialmente traicionero: `(42)` es simplemente `42` en Python (los paréntesis son agrupación, no una tupla). Necesitas una coma final — `(42,)` — para crear una tupla de un elemento.
¿Qué devuelve `cursor.fetchone()` cuando la consulta no coincide con ninguna fila?
- `sqlite3` está en la biblioteca estándar — `import sqlite3`, sin instalación.
- El patrón: `connect` → `cursor` → `execute` → `commit` → `close`. Usa `':memory:'` para DBs efímeras.
- Usa siempre marcadores `?` para los datos — nunca f-strings ni concatenación de strings en SQL.
- `with sqlite3.connect(...) as conn:` hace auto-commit al tener éxito y auto-rollback en excepciones.
- `fetchone()` devuelve `None` (no una tupla vacía) cuando no hay filas que coincidan.
Los rastreadores de experimentos ligeros (el tipo que construyes tú mismo antes de llegar a MLflow) casi siempre almacenan metadatos de ejecución en SQLite: hiperparámetros, métricas por epoch, timestamps, rutas de artefactos. SQLite también es el formato de almacenamiento detrás de muchos arneses de evaluación de ML, el formato en disco de DuckDB y las BDs de configuración para infraestructura de servicio de ML. Si usaste el modo offline de Weights & Biases o un caché de configuración de Hydra, SQLite probablemente estuvo involucrado.
Si lo quitas: Sin un almacén consultable retrocedes a archivos JSON o CSVs — ninguno soporta búsquedas indexadas, agregación ad-hoc o escrituras concurrentes seguras. Terminas reimplementando la mitad de una base de datos en pandas, y aún pierdes datos si dos procesos escriben simultáneamente.