DuckDB con Python permite ejecutar SQL directamente sobre archivos CSV, conjuntos Parquet y DataFrames sin instalar ni mantener un servidor. La respuesta directa es: instala duckdb, abre una conexión y consulta la fuente mediante read_csv, read_parquet o un DataFrame registrado. Es una solución práctica para análisis local, notebooks, pipelines y scripts que combinan SQL con el ecosistema Python.
DuckDB es una base analítica embebida y orientada a columnas. Embebida significa que funciona dentro del proceso Python; analítica indica que favorece lecturas, agregaciones y uniones, no miles de pequeñas transacciones concurrentes. Para ampliar fundamentos, consulta la guía de Python para Data Science y el tutorial de análisis de datos con pandas.
Instalación y primera consulta
Trabaja en un entorno aislado y registra versiones. Nuestra guía de entornos virtuales con venv explica el proceso. Instala DuckDB, pandas y PyArrow:
python -m pip install duckdb pandas pyarrow
Una conexión sin ruta vive en memoria. El gestor de contexto garantiza el cierre aunque se produzca una excepción:
import duckdb
with duckdb.connect() as con:
totales = con.execute("""
SELECT region, sum(importe) AS ingresos
FROM read_csv('datos/ventas.csv', header = true)
GROUP BY region
ORDER BY ingresos DESC
""").fetchall()
print(totales)
fetchall() devuelve tuplas. Usa .df() para obtener pandas o .arrow() para Arrow. No materialices millones de filas sin necesidad: selecciona columnas, filtra y agrega primero dentro del motor.
Consultar CSV con tipos previsibles
read_csv puede detectar encabezado, delimitador y tipos, pero inferir no equivale a definir un contrato. Fechas ambiguas, identificadores con ceros iniciales y columnas mixtas pueden interpretarse mal. En procesos estables, declara las opciones importantes:
consulta = """
SELECT
pedido_id,
CAST(fecha_pedido AS DATE) AS fecha_pedido,
importe
FROM read_csv(
?,
header = true,
delim = ';',
columns = {
'pedido_id': 'VARCHAR',
'fecha_pedido': 'VARCHAR',
'importe': 'DECIMAL(12,2)'
}
)
WHERE importe >= ?
"""
with duckdb.connect() as con:
pedidos = con.execute(consulta, ['datos/pedidos.csv', 100]).df()
Los marcadores ? separan valores del texto SQL y evitan concatenaciones inseguras. Sirven para rutas y escalares, no para cualquier nombre de tabla o columna; limita los identificadores dinámicos a una lista permitida. La guía de manejo de CSV y archivos con Python completa estos conceptos.
Por qué Parquet encaja con DuckDB
Parquet guarda valores por columna e incorpora estadísticas. Una consulta de tres columnas puede evitar decodificar las demás y los filtros pueden descartar grupos completos. La documentación oficial de Parquet en DuckDB describe estas optimizaciones.
with duckdb.connect() as con:
mensual = con.execute("""
SELECT date_trunc('month', fecha) AS mes,
categoria,
avg(importe) AS pedido_medio
FROM read_parquet('lake/ventas/**/*.parquet', hive_partitioning = true)
WHERE fecha >= DATE '2026-01-01'
GROUP BY ALL
ORDER BY mes, categoria
""").df()
El patrón glob reúne archivos con un esquema lógico común. hive_partitioning convierte directorios como anio=2026/mes=07 en columnas. Inspecciona los esquemas antes de unir lotes. union_by_name = true alinea columnas por nombre, pero no resuelve significados de negocio incompatibles.
SQL sobre DataFrames
DuckDB puede localizar una variable pandas en el ámbito de Python. El registro explícito hace más claras las dependencias, especialmente dentro de funciones:
import pandas as pd
import duckdb
clientes = pd.DataFrame({
'cliente_id': [1, 2, 3],
'segmento': ['empresa', 'particular', 'empresa'],
})
with duckdb.connect() as con:
con.register('clientes_df', clientes)
resultado = con.execute("""
SELECT c.segmento, count(*) AS pedidos
FROM read_parquet('datos/pedidos.parquet') p
JOIN clientes_df c USING (cliente_id)
WHERE p.estado = 'pagado'
GROUP BY c.segmento
""").df()
con.unregister('clientes_df')
El patrón une una dimensión pequeña en memoria con hechos Parquet sin exportar el DataFrame. No modifiques el objeto desde otro hilo durante la consulta. Para cálculos que siguen en Python, la guía de NumPy cubre operaciones vectorizadas.
Persistencia, vistas y exportación
Usa duckdb.connect('analitica.duckdb') para conservar objetos entre ejecuciones. Una vista sobre archivos guarda la consulta, no una copia. CREATE TABLE AS sí materializa el resultado.
with duckdb.connect('analitica.duckdb') as con:
con.execute("""
CREATE OR REPLACE VIEW ventas AS
SELECT * FROM read_parquet('datos/ventas/*.parquet')
""")
con.execute("""
COPY (
SELECT categoria, sum(importe) AS total
FROM ventas GROUP BY categoria
) TO 'salida/resumen.parquet' (FORMAT PARQUET)
""")
Utiliza transacciones cuando varias modificaciones deban ser atómicas. No compartas una conexión desordenadamente entre hilos o procesos. La referencia oficial de la API Python de DuckDB explica conexiones y resultados; la página de lectura de CSV enumera sus opciones.
Errores comunes
- Usar siempre
SELECT *: lee datos innecesarios y acopla el código a todos los cambios del esquema. - Confiar ciegamente en la inferencia: valida nulos, codificación, separador, fechas e identificadores.
- Interpolar entradas en SQL: vincula valores y permite solo identificadores previamente validados.
- Pasar a pandas demasiado pronto: filtra, une y agrega en DuckDB antes de materializar.
- Usarlo como servidor OLTP: no reemplaza la base transaccional multiusuario de una aplicación.
- Suponer el directorio actual: resuelve rutas desde una base conocida del proyecto.
Checklist para un análisis fiable
- Crea un entorno virtual y registra las versiones de dependencias.
- Define esquemas, formatos de fecha, precisión decimal y reglas de nulos.
- Selecciona columnas y filtra antes de recuperar objetos Python.
- Parametriza valores externos y valida identificadores SQL dinámicos.
- Comprueba conteos, cardinalidad de uniones, nulos y claves duplicadas.
- Elige memoria para trabajo desechable y archivo para persistencia.
- Prefiere Parquet cuando importan los tipos y las lecturas selectivas.
- Prueba archivos vacíos, entradas dañadas y cambios de esquema.
DuckDB es una opción directa cuando los datos analíticos ya están en archivos u objetos Python. Empieza con una consulta pequeña, declara rutas y tipos, y reduce los datos dentro del motor antes de devolverlos a pandas. Así obtienes un flujo compacto, reproducible y fácil de revisar.