Saltar al contenido

# sql-desde-python

SQL desde Python

Volver a la portada del curso
Notebook

Ejecutar 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; sqlite3 lanza ProgrammingError porque cuenta un parámetro por letra.
  • Usar ? para un nombre de tabla. No funciona; valida el nombre contra una lista permitida.
  • Confundir WHERE y HAVING. WHERE filtra 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.

Recursos