En los últimos años, el día a día de un analista ha tenido una rutina repetitiva: descargar un CSV grande, abrirlo en Excel (esperando que no dé errores), usar pandas en un Jupyter notebook en aquellos casos de mayor volumetría o directamente recurrir al SQL del data warehouse cuando los volúmenes empiezan a ser serios. Tres herramientas con tres lenguajes y tres formas de pensar el mismo problema.
DuckDB aparece para romper esta fragmentación. Es una base de datos analítica embebida —vive dentro del proceso de tu aplicación, no en un servidor— que ejecuta SQL puro a gran velocidad sobre ficheros locales, datos en cloud, DataFrames de pandas y gran variedad de orígenes. Sin instalación de servidor, sin configuración, sin DBA. Solo pip install duckdb y a trabajar.
En este artículo te explicamos qué es, por qué deberías conocerla si haces analítica, y una serie de tareas simples de cara a comenzar su uso.
¿Qué es DuckDB exactamente?
Piensa en DuckDB como el SQLite del mundo analítico con una misma filosofía embebida, pero optimizada para agregaciones y joins en lugar de transacciones.
Ideas clave que la hacen distinta:
- Es columnar y vectorizada: almacena y procesa los datos por columnas, no por filas, lo que la hace órdenes de magnitud más rápida que una BBDD tradicional para queries analíticas. El motor procesa los datos en lotes vectorizados aprovechando las instrucciones SIMD del procesador.
- Es in-process: no hay un servidor al que conectarse ya que la base de datos se ejecuta dentro de Python, R, Node, Java, etc. Cero latencia de red y sin gestión de infraestructura.
- Habla SQL estándar: soporta extensiones de sintaxis muy cómodas para análisis como las cláusulas
QUALIFY,EXCLUDE/REPLACEenSELECT, columnas posicionales, lambdas, lista de funciones de ventana muy completa, etc.
La versión actual a fecha de este artículo es la 1.5 «Variegata». Las versiones recientes han traído mejoras potentes:
- Extensión DuckLake para lakehouse.
- Tipo VARIANT estilo Snowflake.
- Tipo GEOMETRY nativo.
- CLI rediseñada.
Instalación: operativo en 30 segundos
Las tres formas más habituales:
- Python
pip install duckdb
- CLI standalone: Descarga el binario en duckdb.org/docs/installation/
- R, Node.js, Java, Go, Rust y más: todas disponibles desde la web oficial
Antes de empezar: los modos de ejecutar SQL
A lo largo del artículo vas a ver bloques de código en SQL «puro» y otros en Python. Conviene aclarar de entrada los dos modos de trabajar, porque si copias y pegas sin saberlo, los SQL directos no se ejecutan solos desde Python:
Modo 1 — CLI o UI directa: arrancas el binario duckdb desde la terminal y ejecutas SQL tal cual. Todo lo que veas en bloques marcados como sql se ejecuta así.
duckdb
> SELECT 'Hola DuckDB' AS saludo;

Modo 2 — Desde Python: lo más habitual para un analista. Aquí se trabaja con una conexión, y cada SQL se ejecuta a través de ella. El patrón más común:
import duckdb
# Conexión en memoria (no persiste, ideal para análisis ad-hoc)
con = duckdb.connect()
# O persistente en fichero (como SQLite)
con = duckdb.connect('mi_analisis.duckdb')
# Ejecutar SQL y ver resultados
con.execute("SELECT 'Hola DuckDB' AS saludo").fetchall()
# O usar .sql() si quieres un objeto Relation manipulable
con.sql("SELECT 'Hola DuckDB' AS saludo").show()
¿execute() o sql()? Regla práctica:
con.execute(query)→ para DDL/DML (CREATE, INSERT, COPY, ATTACH, INSTALL, LOAD, SET…) y cuando solo quieres lanzar la query sin manipular el resultado. Devuelve la conexión, encadenable con.fetchall(),.fetchdf(),.df(), etc.con.sql(query)→ cuando quieres trabajar con el resultado como objeto Relation (lazy, encadenable, convertible a DataFrame con.df()). Ideal para análisis exploratorio.
A partir de aquí, los bloques en sql son CLI/UI y los bloques en python muestran cómo invocarlo desde una conexión con.
Vamos a los casos prácticos.
Tarea 1: leer CSV, Parquet, JSON o Excel
Este es el primer momento para cualquier analista. DuckDB puede consultar ficheros directamente como si fueran tablas, sin un paso previo de carga:
-- Lee un CSV directamente, infiere tipos y delimitadores
SELECT * FROM 'ventas_2025.csv' LIMIT 10;

-- Parquet, igual
SELECT cliente, SUM(importe) AS total
FROM 'transacciones.parquet'
GROUP BY cliente
ORDER BY total DESC;

-- Múltiples ficheros a la vez con wildcards
SELECT * FROM 'datos/ventas_*.parquet';

-- JSON anidado
SELECT * FROM 'eventos.json';

-- Excel (necesitas cargar previamente la extensión)
INSTALL excel; LOAD excel;
SELECT * FROM read_xlsx('cuadro_mando.xlsx', sheet='Resumen');

Para un analista que recibe ficheros sueltos a diario ahorramos el paso de ingesta, ya que las consultas son SQL directo sobre los ficheros.
Desde Python, recuerda envolver cada consulta con la conexión:
import duckdb
con = duckdb.connect()
# Resultado como DataFrame listo para usar
df = con.execute("""
SELECT cliente, SUM(importe) AS total
FROM 'transacciones.parquet'
GROUP BY cliente
ORDER BY total DESC
""").fetchdf()
Apunte extra: si necesitas controlar la lectura del CSV (separadores raros, encoding ISO-8859-1 típico del español, fechas en formato europeo):
SELECT * FROM read_csv(
'datos_raros.csv',
delim=';',
decimal_separator=',',
dateformat='%d/%m/%Y',
encoding='latin-1'
);
Tarea 2: Pandas, Polars y DuckDB hablándose entre sí
Aquí está el punto de entrada para quien viene del mundo Python. DuckDB puede consultar DataFrames directamente como si fueran tablas, sin copiar datos:
import duckdb
import pandas as pd
con = duckdb.connect()
ventas = pd.read_csv('ventas.csv')
clientes = pd.read_csv('clientes.csv')
# Consulta SQL sobre los DataFrames como si fueran tablas
resultado = con.execute("""
SELECT c.segmento, SUM(v.importe) AS total
FROM ventas v
JOIN clientes c ON v.cliente_id = c.id
GROUP BY c.segmento
ORDER BY total DESC
""").fetchdf()
# .fetchdf() devuelve pandas; .fetch_arrow_table() devuelve Arrow
# Si prefieres trabajar con .sql() (relación lazy): con.sql("...").df() / .pl() / .arrow()

Lo bueno aquí es que no hay conversión real de los datos, DuckDB lee directamente la memoria del DataFrame gracias al formato Arrow. El resultado es un join que en pandas puro tardaría segundos o minutos sobre millones de filas, y en DuckDB termina en milisegundos.
Para análisis exploratorio, este patrón es el perfecto ejemplo:
df_final = con.execute("""
SELECT
DATE_TRUNC('month', fecha) AS mes,
producto,
SUM(unidades) AS uds,
SUM(importe) / SUM(unidades) AS ticket_medio
FROM 'ventas_*.parquet'
WHERE fecha >= '2025-01-01'
GROUP BY 1, 2
QUALIFY uds > 100
""").fetchdf()
Tarea 3: lectura directa desde S3, Azure Blob o GCS
DuckDB puede leer ficheros directamente desde almacenamiento en cloud sin descargarlos antes:
-- Configurar credenciales (solo la primera vez)
CREATE SECRET azure_secret (
TYPE AZURE,
CONNECTION_STRING 'DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...'
);
-- Consulta de los datos
SELECT COUNT(*)
FROM 'azure://contenedor/raw/eventos_2025*.parquet';
-- S3 funciona igual
CREATE SECRET s3_secret (
TYPE S3,
KEY_ID 'AKIA...',
SECRET 'xyz...',
REGION 'eu-west-1'
);
SELECT * FROM 's3://bucket/datos/*.parquet' LIMIT 100;
Esto es especialmente útil cuando trabajas con un Data Lake en Azure y quieres explorar un Parquet de 10 GB sin descargarlo entero a tu portátil. DuckDB descarga solo los bloques que necesita gracias al pushdown de proyecciones y filtros sobre Parquet.
Desde Python: los
CREATE SECRET,INSTALLyLOADtambién se lanzan concon.execute("..."). Una vez ejecutados sobre la conexión, losSELECTposteriores ya tienen acceso al storage configurado.
Tarea 4: crear datasets de prueba en segundos
Una de las operativas más útiles para un analista: generar datos sintéticos para probar dashboards, validar lógica de negocio o demos. Con DuckDB puedes realizarlo de forma rápida:
-- Generar una serie temporal de 1 millón de filas
CREATE TABLE eventos AS
SELECT
range AS evento_id,
DATE '2024-01-01' + INTERVAL (range % 365) DAY AS fecha,
['BK', 'MCD', 'KFC'][1 + (range % 3)] AS marca,
['ES', 'PT', 'FR'][1 + (range % 3)] AS pais,
ROUND(RANDOM() * 100, 2) AS importe
FROM range(1000000);
-- Y a explorar
SELECT marca, pais, COUNT(*) AS n, AVG(importe) AS ticket_medio
FROM eventos
GROUP BY marca, pais
ORDER BY n DESC;

Otra opción es usar generate_series para series temporales o unnest para expandir arrays. Para datasets más realistas, la extensión tpch genera el dataset estándar TPC-H con un solo comando:
INSTALL tpch; LOAD tpch;
CALL dbgen(sf = 1); -- Genera ~1GB de datos realistas de comercio
SELECT * FROM lineitem LIMIT 10;
Tarea 5: sintaxis SQL pensada para analistas
DuckDB ha tomado prestadas (y mejorado) varias sintaxis modernas que hacen el SQL analítico mucho más limpio:
SELECT * EXCLUDE y REPLACE, para cuando quieres todas las columnas menos un par, o transformar alguna sobre la marcha:
SELECT * EXCLUDE (password, token) FROM usuarios;
SELECT * REPLACE (importe / 100 AS importe) FROM ventas;
QUALIFY filtra sobre el resultado de funciones de ventana sin tener que envolver en una subconsulta:
-- Top 3 productos por categoría
SELECT categoria, producto, ventas
FROM ranking
QUALIFY ROW_NUMBER() OVER (PARTITION BY categoria ORDER BY ventas DESC) <= 3;
GROUP BY ALL y ORDER BY ALL, agrupa por todas las columnas no agregadas. Así evitamos repetir 8 columnas en el GROUP BY:
SELECT pais, region, marca, mes, SUM(importe) AS total
FROM ventas
GROUP BY ALL;
Columnas posicionales en GROUP BY/ORDER BY funcionan bien, pero las anteriores son más legibles.
Pivots nativos:
PIVOT ventas
ON marca
USING SUM(importe)
GROUP BY mes;
Tarea 6: persistencia y consulta cruzada de bases de datos
DuckDB puede tener varias bases de datos abiertas a la vez y hacer joins entre ellas, incluso si una es PostgreSQL o SQLite:
-- Adjuntar una BBDD PostgreSQL como si fuera local
INSTALL postgres; LOAD postgres;
ATTACH 'host=localhost user=usuario dbname=produccion' AS pg (TYPE POSTGRES);
-- Y otra SQLite
ATTACH 'app.sqlite' AS app (TYPE SQLITE);
-- Join entre Postgres, SQLite y un Parquet local en la misma query
SELECT
p.cliente_id,
p.nombre,
a.ultima_sesion,
e.eventos_mes
FROM pg.public.clientes p
JOIN app.sesiones a ON a.cliente_id = p.cliente_id
JOIN 'eventos.parquet' e ON e.cliente_id = p.cliente_id;
Si tienes datos repartidos entre Postgres, SQLite y ficheros sueltos, te ahorras montar un ETL solo para juntarlos.
Tarea 7: exportar resultados a cualquier formato
DuckDB exporta a casi cualquier formato que uses después:
-- A Parquet con compresión
COPY (SELECT * FROM resultado) TO 'salida.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);
-- A CSV
COPY (SELECT * FROM resultado) TO 'salida.csv' (HEADER, DELIMITER ';');
-- A JSON
COPY (SELECT * FROM resultado) TO 'salida.json';
-- Particionado por columna (genera estructura tipo Hive)
COPY ventas TO 'export/' (FORMAT PARQUET, PARTITION_BY (year, mes));


El último es especialmente útil para preparar datos para un Data Lake: particiones por fecha y todo listo para que Databricks o Spark las lean.
¿Cuándo NO usar DuckDB?
Conviene saber dónde no es recomendable usar DuckDB:
- Cargas transaccionales (OLTP) con muchos usuarios concurrentes escribiendo: DuckDB es analítica, no transaccional. Para este tipo de operativas es mejor Postgres, MySQL o SQLite.
- Como base de datos compartida en producción para múltiples aplicaciones: al ser embebida, está pensada para un proceso a la vez. Si necesitas un servidor centralizado al que ataquen muchas apps, mejor un warehouse tradicional.
- Volúmenes que no caben en disco local y necesitan distribución: aunque DuckDB es sorprendentemente capaz con datasets que exceden la RAM (usa spilling a disco), si hablas de petabytes y necesitas escalado horizontal, tu sitio sigue siendo Databricks, Snowflake o similar.
Donde brilla DuckDB son datasets que van desde unos pocos MB hasta cientos de GB, en un único proceso, con foco analítico. Y en ese terreno, es difícil que otra herramienta le gane en relación esfuerzo/resultado.
El ecosistema alrededor de DuckDB
DuckDB ya no es solo la base de datos, alrededor ha crecido un ecosistema bastante serio:
- MotherDuck: la versión cloud/SaaS de DuckDB, con sincronización local-cloud, para casos donde quieres mantener el flujo DuckDB pero con datos compartidos en equipo.
- DuckLake: una especificación de lakehouse propia, alternativa a Delta Lake e Iceberg, que guarda los metadatos en una base de datos en lugar de en ficheros sueltos en object storage.
- Extensiones de la comunidad: lectura de Excel, conexión a Iceberg, soporte espacial con índices R-tree, ejecución de modelos ONNX desde SQL, integración con Kaggle, búsqueda vectorial con Lance, etc.
- Integración con dbt, Airflow, Dagster y prácticamente cualquier orquestador moderno.
Por qué deberías probarla
Si haces analítica y trabajas a diario con CSVs, Parquets o DataFrames, lo más probable es que DuckDB acabe siendo tu herramienta de referencia. La curva de aprendizaje es corta, desde el primer dataset notas la diferencia, y los casos de uso van desde lo exploratorio hasta pipelines de transformación serios.
Nuestra recomendación es que la próxima vez que recibas un CSV grande, en lugar de abrir Excel o usar pandas por defecto, prueba el siguiente comando en tu cuaderno Jupyter:
import duckdb
con = duckdb.connect()
con.execute("SELECT * FROM 'tu_fichero.csv' LIMIT 10").fetchdf()
Si después de eso quieres ya hacer una agregación o un join con otro fichero, descubrirás que casi todo lo que necesitas es SQL puro sobre los propios ficheros.
Próximo artículo: la UI de DuckDB
En un articulo futuro vamos a ver la interfaz de usuario (UI) de DuckDB que permite a los analistas realizar todas las operaciones aquí indicadas y muchas más desde un punto de vista más amigable para el usuario.
En DECIDE | Linkroad ayudamos a equipos de datos a modernizar sus flujos analíticos, desde el prisma del analista hasta las implementaciones más complejas en cualquier herramienta. Si DuckDB encaja en tu stack y quieres explorar cómo integrarlo con el resto de tu plataforma, contáctanos.



