# sql-desde-python
SQL desde Python
Volver a la portada del cursoEjecutar Consultas con Python
Sección 3 · Consultas Básicas en SQL
Ejecutar consultas con Python
Saber escribir un SELECT es la mitad del trabajo. La otra mitad ocurre en Python: pasarle a la consulta los valores que vienen del usuario, leer las filas de la forma más cómoda y llevar el resultado a donde se va a usar. Esta guía acompaña al notebook consultas-con-python, que trabaja sobre la base de PyShop (300 clientes, 40 productos, 2000 pedidos). Aquí se explica qué buscar en cada sección.
1. El cursor entrega las filas de a poco
con.execute(sql) devuelve un cursor. Las filas no se copian todas de golpe: el cursor las va entregando, y cada lectura continúa donde quedó la anterior.
cur = con.execute("SELECT id, nombre FROM productos ORDER BY id")
print(cur.fetchone()) # (1, 'Audífonos Bluetooth')
print(cur.fetchmany(3)) # filas 2, 3 y 4
print(cur.fetchmany(2)) # filas 5 y 6: continúa
print(len(cur.fetchall())) # 34: las que faltaban
Usa fetchone() cuando esperas una fila (buscar por id), fetchall() para resultados pequeños y for fila in cur o fetchmany(n) para recorrer resultados grandes sin cargarlos completos en memoria.
2. Repaso de SELECT dentro de Python
El notebook repasa las consultas de la lección 454 con cifras reales: los tres productos más caros (Monitor 24 pulgadas a 3299, Disco SSD 1 TB a 1599, Teclado mecánico a 1499), el único producto con stock menor que 10 (Juego de cuchillos, 9), las ciudades con más de 50 clientes (HAVING: Ciudad de México 111 y Guadalajara 60) y el ingreso por categoría con un JOIN de tres tablas. Una cadena triple permite escribir el SQL en varias líneas legibles, con las palabras clave en mayúsculas:
sql = """
SELECT c.id, c.nombre, c.ciudad
FROM clientes AS c
LEFT JOIN pedidos AS p ON p.cliente_id = c.id
WHERE p.id IS NULL
"""
print(con.execute(sql).fetchall())
# [(120, 'Ana Romero', 'Monterrey')]
El LEFT JOIN conserva todos los clientes aunque no tengan pedidos; los que no tienen pareja quedan con NULL en las columnas de pedidos, y el WHERE los selecciona. En PyShop hay exactamente uno.
3. Parámetros: los valores van aparte
Cuando un valor viene de afuera, se escribe un marcador en el SQL y el valor se pasa en el segundo argumento de execute. sqlite3 admite dos estilos:
sql = "SELECT COUNT(*) FROM productos WHERE categoria = ?"
print(con.execute(sql, ("Hogar",)).fetchone()[0]) # 8
sql = "SELECT COUNT(*) FROM productos WHERE precio > :minimo"
print(con.execute(sql, {"minimo": 1000}).fetchone()[0]) # 6
Con ? se pasa una tupla en orden (una tupla de un solo valor lleva coma: ("Hogar",)); con :nombre, un diccionario. Para una lista de valores en un IN se genera un marcador por valor con ", ".join("?" for _ in valores). Los parámetros sirven solo para valores: los nombres de tablas y columnas no se pueden pasar así.
4. La inyección SQL, en un ejemplo
El notebook arma una búsqueda por correo con una f-string y le pasa como «correo» el texto x' OR '1'='1. La comilla cierra la cadena y el resto se vuelve SQL: la condición queda correo = 'x' OR '1'='1', que es verdadera para todas las filas. La consulta devuelve 300 clientes. Con el mismo texto como parámetro ?, SQLite busca un correo que se llame literalmente así y devuelve 0. La documentación de sqlite3 lo dice sin rodeos: no armes consultas con operaciones de cadenas de Python; usa marcadores.
5. Filas con nombre: sqlite3.Row
Las tuplas obligan a recordar la posición de cada columna. Con row_factory = sqlite3.Row, cada fila admite acceso por nombre y por posición, keys() y conversión a diccionario:
cur = con.cursor()
cur.row_factory = sqlite3.Row
r = cur.execute("SELECT * FROM productos WHERE id = ?", (4,)).fetchone()
print(r["nombre"], r["precio"]) # Monitor 24 pulgadas 3299.0
print(dict(r))
En el cursor afecta solo a ese cursor; en la conexión, a todas sus consultas.
6. Del resultado a pandas
pd.read_sql_query(sql, con, params=...) ejecuta la consulta y devuelve un DataFrame con los nombres de columna del SELECT. En el notebook, el resumen por categoría sale como una tabla de 5 filas y 3 columnas, y los pedidos cancelados con params=("cancelado",) dan un DataFrame de 200 filas y 4 columnas. La misma función acepta un engine de SQLAlchemy, y DataFrame.to_sql hace el camino inverso: copia los 40 productos a otra base.
7. Un vistazo a lo avanzado
La última sección adelanta dos herramientas de la lección 462. Una CTE (WITH u AS (...)) nombra una subconsulta, y RANK() OVER (PARTITION BY categoria ORDER BY unidades DESC) numera los productos dentro de cada categoría. El más vendido es Botella térmica (158 unidades, Deportes); en Hogar y en Electrónica hay empates, y RANK les da el puesto 1 a ambos.
Trampas comunes
- Pegar valores con f-strings. Es la puerta de la inyección SQL y además falla con nombres como «O'Neill».
- Olvidar la coma en la tupla.
("Hogar")es una cadena, no una tupla;sqlite3lanzaProgrammingErrorporque cuenta un parámetro por letra. - Usar
?para un nombre de tabla. No funciona; valida el nombre contra una lista permitida. - Confundir
WHEREyHAVING.WHEREfiltra filas antes de agrupar;HAVING, grupos después.
Termina con los cuatro ejercicios del notebook: buscar un cliente por correo con Row, listar productos con poco stock, el ingreso por ciudad en pandas y un IN con marcadores generados. Las pruebas con assert te dicen si quedaron bien.