Apariencia
📙 Clase 30 — SQLite I: crear, insertar, leer
Fase 6 · Especialización: Apps de escritorio 🎯 ⬅️ Volver al índice de clases
🎯 Qué aprendí
- SQLite: una base de datos real, en un archivo, incluida con Python (
import sqlite3). CREATE TABLE,INSERTcon placeholders ySELECT(fetchall/fetchone).- Por qué los placeholders
?no son opcionales (inyección SQL, demostrada).
📖 PARTE TEÓRICA
🗄️ 1. ¿Por qué SQLite (y no más JSON)?
| JSON (Clase 20) | SQLite | |
|---|---|---|
| Buscar/filtrar | cargas TODO y filtras en Python | la BD filtra (WHERE) |
| Modificar 1 registro | reescribes el archivo entero | UPDATE ... WHERE id=? |
| Miles de registros | lento y frágil | su hábitat natural |
| Instalación | — | ninguna: viene con Python |
SQLite guarda todo en un archivo (app.db) — sin servidor, sin configuración. Es la BD más desplegada del mundo (está en cada teléfono) y la estándar para apps de escritorio.
🏗️ 2. Conectar y crear la tabla
python
import sqlite3
con = sqlite3.connect("app.db") # crea el archivo si no existe
con.execute("""
CREATE TABLE IF NOT EXISTS contactos (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- ID automático (Clase 14!)
nombre TEXT NOT NULL, -- obligatorio
tel TEXT,
email TEXT UNIQUE -- no admite repetidos
)
""")| Pieza | Qué garantiza |
|---|---|
IF NOT EXISTS | correr el CREATE en cada arranque sin error |
PRIMARY KEY AUTOINCREMENT | cada fila nace con ID único (tu Tarea.contador, pro) |
NOT NULL / UNIQUE | integridad: la BD rechaza basura aunque tu código falle |
Tipos de SQLite: TEXT, INTEGER, REAL (float), BLOB. No hay booleano: se usa 0/1.
➕ 3. INSERT — siempre con placeholders
python
with con: # transacción (Clase 28): commit automático
con.execute(
"INSERT INTO contactos (nombre, tel, email) VALUES (?, ?, ?)",
("Ana", "999-111", "ana@x.com"), # ? posicionales (tupla)
)
cur = con.execute(
"INSERT INTO contactos (nombre, tel, email) VALUES (:nombre, :tel, :email)",
{"nombre": "Luis", "tel": "988-222", "email": "luis@x.com"}, # :nombre (dict!)
)
print(cur.lastrowid) # 2 ← el ID que la BD le asignó🗄️ Los placeholders
:nombrecasan con tu dict del formulario tal cual — el puente dict=fila de la Clase 04 en su forma final.
¿Por qué JAMÁS concatenar strings en SQL? Demostrado en tu venv:
python
maligno = "'; DROP TABLE contactos; --" # entrada malintencionada del usuario
# ❌ f"SELECT ... WHERE nombre = '{maligno}'" → ejecutaría el DROP TABLE 💀
# ✅ con placeholder es un TEXTO inofensivo:
con.execute("SELECT * FROM contactos WHERE nombre = ?", (maligno,)).fetchall()
# → [] (buscó literalmente ese nombre raro; la tabla sigue viva)🧪 Tip de entrevista: "¿Qué es la inyección SQL y cómo se evita?" → Meter SQL en un campo de entrada para que se ejecute. Se evita con consultas parametrizadas (placeholders), nunca concatenando input del usuario en el SQL.
🔍 4. SELECT — leer
python
todas = con.execute("SELECT id, nombre FROM contactos").fetchall()
# [(1, 'Ana'), (2, 'Luis')] ← lista de TUPLAS (Clases 01/05)
fila = con.execute("SELECT * FROM contactos WHERE id = ?", (1,)).fetchone()
# (1, 'Ana', '999-111', 'ana@x.com') ← UNA fila, o None si no existe
con.row_factory = sqlite3.Row # modo dict-like
f = con.execute("SELECT * FROM contactos WHERE id = 1").fetchone()
print(f["nombre"]) # Ana ← por nombre de columna
print(dict(f)) # {'id': 1, 'nombre': 'Ana', ...}| Método | Devuelve | Úsalo cuando |
|---|---|---|
.fetchall() | lista de filas | quieres todo (pintar una lista) |
.fetchone() | una fila o None | buscas por ID / compruebas existencia |
| iterar el cursor | fila por fila (perezoso) | recorrido único de muchas filas (Clase 25) |
🖥️ EN TU APP DE ESCRITORIO
La capa datos.py de la Clase 29, ahora con SQLite de verdad:
python
# datos.py — lo ÚNICO que sabe SQL en toda la app
import sqlite3
from pathlib import Path
RUTA_BD = Path.home() / ".mi_agenda" / "app.db" # pathlib (Clase 22)
def conectar():
RUTA_BD.parent.mkdir(exist_ok=True)
con = sqlite3.connect(RUTA_BD)
con.row_factory = sqlite3.Row
con.execute("""CREATE TABLE IF NOT EXISTS contactos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL, tel TEXT, email TEXT UNIQUE)""")
return con
def insertar_contacto(con, contacto: dict) -> int:
with con:
cur = con.execute(
"INSERT INTO contactos (nombre, tel, email) VALUES (:nombre, :tel, :email)",
contacto)
return cur.lastrowid
def obtener_contactos(con) -> list:
return con.execute("SELECT * FROM contactos ORDER BY nombre").fetchall()Y la vista ni se entera de que hay SQL:
python
def _guardar(self):
contacto = {"nombre": self.e_nombre.get(), "tel": self.e_tel.get(),
"email": self.e_email.get()}
try:
datos.insertar_contacto(self.con, contacto)
except sqlite3.IntegrityError: # el email UNIQUE (verificado)
self.status.configure(text="⚠️ Ese email ya está registrado")
return
self._pintar()🏋️ EJERCICIOS CON SOLUCIÓN
Ejercicio 1 — Tu primera tabla
Crea notas.db con tabla notas(id AUTOINCREMENT, titulo NOT NULL, texto) e inserta dos notas.
Ver solución
python
import sqlite3
con = sqlite3.connect("notas.db")
con.execute("""CREATE TABLE IF NOT EXISTS notas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
texto TEXT)""")
with con:
con.execute("INSERT INTO notas (titulo, texto) VALUES (?, ?)", ("Compra", "pan"))
con.execute("INSERT INTO notas (titulo, texto) VALUES (?, ?)", ("Idea", "app gastos"))
print(con.execute("SELECT * FROM notas").fetchall())
# [(1, 'Compra', 'pan'), (2, 'Idea', 'app gastos')]Ejercicio 2 — Buscar por ID (sin morir)
Escribe buscar_nota(con, id) que devuelva la fila o None (y no explote nunca).
Ver solución
python
def buscar_nota(con, id):
return con.execute("SELECT * FROM notas WHERE id = ?", (id,)).fetchone()
print(buscar_nota(con, 1)) # (1, 'Compra', 'pan')
print(buscar_nota(con, 99)) # None ← fetchone devuelve None, no explotaEjercicio 3 — Del formulario a la fila
Simula un formulario: nota = {"titulo": "Curso", "texto": "repasar SQLite"} e insértala con placeholders :con_nombre.
Ver solución
python
nota = {"titulo": "Curso", "texto": "repasar SQLite"}
with con:
cur = con.execute("INSERT INTO notas (titulo, texto) VALUES (:titulo, :texto)", nota)
print("ID asignado:", cur.lastrowid) # 3❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué necesitas instalar para usar SQLite en Python?
Nada:
sqlite3viene en la librería estándar; la BD es un archivo.
2. ¿Por qué placeholders (? / :nombre) y nunca f-strings en SQL?
Por la inyección SQL: el input del usuario podría ejecutar SQL propio. El placeholder lo trata siempre como dato.
3. ¿Qué devuelve fetchone() si no hay resultados?
None(perfecto paraif fila is None: ...).
4. ¿Qué hace row_factory = sqlite3.Row?
Las filas se acceden por nombre de columna (
fila["nombre"]) y se convierten condict(fila)— de vuelta a tu formato favorito.
📎 Apuntes relacionados
- dict = fila (el origen de todo) → Clase 04
with con:transacciones → Clase 28- La capa donde vive este código → Clase 29 · Arquitectura
➡️ Siguiente
Clase 31 · SQLite II — UPDATE, DELETE, búsqueda: el CRUD completo en tu GUI.