04 · CSV y JSON¶
Cuaderno asociado
cuadernos/03_csv_json.ipynb. Volver al 00 - Índice de la demo.
El 80 % de los datos «sucios» que llegan a un equipo de datos son CSV mal formados y/o JSON anidado. DuckDB ataca ambos sin cargar nada en memoria de Python.
flowchart LR
subgraph origen["Orígenes"]
C1["CSV / TSV<br/>delimitadores raros"]
C2["JSON array"]
C3["NDJSON<br/>una línea = un objeto"]
end
subgraph duck["DuckDB"]
R1["read_csv / read_csv_auto"]
R2["read_json_auto"]
S["sniff_csv"]
V["::VARIANT"]
end
C1 --> R1
C2 --> R2
C3 --> R2
C1 -.->|"Inferir esquema"| S
R2 -.->|"tipado opcional"| V
R1 --> T[("Tabla / Parquet")]
R2 --> T
V --> T
linkStyle 3 stroke:red,stroke-width:3px;
CSV: inferencia de metadatos -> control¶
-- 1. Explorar: ¿qué infiere el motor?
FROM sniff_csv('datos/transacciones.csv');
-- 2. Consultar directamente
SELECT * FROM read_csv_auto('datos/transacciones.csv') LIMIT 5;
-- 3. Fijar el esquema (lo que harás en producción)
SELECT *
FROM read_csv('datos/transacciones.csv',
header = true,
delim = ',',
columns = {'fecha': 'DATE', 'monto': 'DOUBLE', 'categoria': 'VARCHAR'});
read_csv_auto para explorar, read_csv con esquema explícito para producción. La inferencia falla con columnas vacías, ceros iniciales y fechas en formatos mixtos; el esquema explícito no.
Unir ficheros con columnas distintas¶
SELECT * FROM read_csv(['datos/2024/*.csv', 'datos/2025/*.csv'],
header = true,
union_by_name = true);
union_by_name alinea por nombre de columna en vez de por posición: imprescindible cuando el proveedor añade una columna a mitad de año.
Cuando el CSV está roto 🤯🤯¶
| Síntoma | Opción |
|---|---|
| Líneas con más/menos columnas | ignore_errors = true (descarta) o null_padding = true (rellena) |
| Codificación no UTF-8 | Volver a codificar antes; encoding cubre latin-1 |
| Decimales con coma | delim = ';' y decimal_separator = ',' |
| Comentarios y cabeceras repetidas | comment = '#', skip = N |
JSON: dos formatos, dos estrategias¶
-- NDJSON (una línea = un documento) y arrays: el mismo lector los detecta
SELECT * FROM read_json_auto('datos/usuarios.json');
Acceso a campos anidados¶
SELECT
id,
direccion.ciudad AS ciudad, -- acceso por punto
habilidades[1] AS primera, -- LIST 1-indexado
len(habilidades) AS n_habilidades
FROM read_json_auto('datos/usuarios.json');
De JSON-texto a VARIANT¶
Si el esquema varía entre filas, no forzar columnas: convertir a tipo VARIANT y consultar de forma flexible.
SELECT payload.action::VARCHAR AS accion,
variant_typeof(payload) AS tipo
FROM read_json_auto('datos/eventos.jsonl');
→ Más en 08 - VARIANT.
Errores en el cuaderno
read_json_auto(..., auto_expand=false)no existe en 2.0.0: ese parámetro fue eliminado. El acceso por punto sobre el STRUCT ya resuelve el caso.read_json(..., format='array')falla si el fichero es NDJSON: «Expected top-level JSON array». Usaformat='auto'oread_json_auto.
JSON grande: evitar leer múltiples veces!!¶
-- Materializa una vez y explota el coste
CREATE TABLE eventos AS SELECT * FROM read_json_auto('datos/eventos.jsonl');
-- A partir de aquí, comprime y vuela
COPY eventos TO 'datos/eventos.parquet' (FORMAT PARQUET, COMPRESSION zstd);
Este es el patrón que conecta con 05 - Parquet y Hive partitioning: JSON para ingerir, Parquet para analizar.
Ejercicios¶
- Generar un CSV con una fila corrupta (una coma de más) y consigue leerlo sin que falle la consulta. ¿Qué filas pierdes?
- Leer un NDJSON donde unas filas traen
"precio": 10y otras"precio": "10.5". ¿Qué infiereread_json_auto? Fuerza el tipo. - Extraer la ciudad y el número de habilidades de
usuarios.jsonen una sola consulta. - Convertir
eventos.jsonla Parquet comprimido y compara el tamaño en disco.
Soluciones en cuadernos/99_soluciones.ipynb.