08 · VARIANT¶
Cuaderno asociado
cuadernos/08_variant.ipynb. Volver al 00 - Índice de la demo.
JSON es texto; VARIANT es dato¶
flowchart TB
subgraph json["JSON (texto)"]
J1["user.id, action: todo VARCHAR"]
J2["Se parsea en CADA consulta"]
J3["Sin tipado"]
J1 --> J2 --> J3
end
subgraph variant["VARIANT (binario auto-descriptivo)"]
V1["Cada valor lleva su tipo"]
V2["Shredding: campos comunes<br/>a columnas tipadas"]
V3["Compresión alta + pushdown"]
V1 --> V2 --> V3
end
json -.->|"cast ::VARIANT"| variant
VARIANT guarda valores cuyo tipo cambia entre filas sin renunciar a rendimiento. Es el tipo que evita el antipatrón de «todo es un string y ya lo parseo yo 🙃🙃».
Crear y consultar¶
CREATE TABLE eventos (id INTEGER, payload VARIANT);
INSERT INTO eventos VALUES
(1, '{"user":{"id":7,"name":"ana"},"action":"login"}'),
(2, '{"user":{"id":9,"name":"luis"},"action":"logout","ms":12}');
SELECT id,
payload.user.name::VARCHAR AS usuario,
payload.action::VARCHAR AS accion,
variant_typeof(payload) AS tipo
FROM eventos;
| id | usuario | accion | tipo |
|---|---|---|---|
| 1 | ana | login | OBJECT(action, user) |
| 2 | luis | logout | OBJECT(action, ms, user) |
Atención a la columna tipo: cada fila conserva su estructura. Eso es exactamente lo que un esquema rígido no puede hacer.
Funciones disponibles¶
| Función | Para qué |
|---|---|
variant_typeof(v) |
Tipo real del valor (OBJECT, ARRAY(3), INT32, VARCHAR…) |
variant_extract(v, 'campo') |
Extraer un campo de forma explícita |
variant_extract_string(v, 'campo') |
Igual, pero devolviendo texto |
variant_exists(v, 'campo') |
¿Existe la clave? |
variant_keys(v) |
Claves de un objeto |
variant_array_length(v) |
Longitud de un array |
Notación por punto (payload.user.name) si el tipo es conocido; variant_extract cuando no lo es.
Shredding: la razón de que sea rápido¶
Novedad de la 2.0
Al guardar VARIANT en un fichero DuckDB, el motor detecta la estructura común entre filas y la desmenuza en columnas tipadas. Eso comprime mejor y permite leer solo lo necesario.
- Se aplica a row groups de al menos
variant_minimum_shredding_sizefilas (30 000 por defecto). SET variant_minimum_shredding_size = -1;lo desactiva (útil para comparar en clase).
flowchart LR
R1["fila 1: user + action"] --> SH["Detección de<br/>estructura común"]
R2["fila 2: user + action + ms"] --> SH
R3["fila N: user + action"] --> SH
SH --> C1["user.id INT<br/>(columna tipada)"]
SH --> C2["user.name VARCHAR<br/>(columna tipada)"]
SH --> C3["action VARCHAR<br/>(columna tipada)"]
SH --> C4["resto VARIANT<br/>(campos no comunes)"]
VARIANT en Parquet¶
Se lee y se escribe, con shredding automático o declarado:
COPY eventos TO 'datos/eventos.parquet' (
FORMAT parquet,
SHREDDING {'payload': 'STRUCT(name VARCHAR, ms INTEGER)'}
);
Y DuckDB lee VARIANT shredded generado por Snowflake, reconstruyendo el valor original. Puerta abierta a mover datos semiestructurados entre plataformas sin convertirlos a texto.
VARIANT vs JSON vs columnas¶
| Criterio | JSON | VARIANT | Columnas tipadas |
|---|---|---|---|
| Esquema variable entre filas | ✅ | ✅ | ❌ |
| Compresión | ❌ baja | ✅ alta | ✅ alta |
| Consulta sin parsear | ❌ | ✅ | ✅ |
| Pushdown de campos | ❌ | ✅ | ✅ |
| Coste de escritura | bajo | medio | bajo |
Regla práctica: JSON para aterrizar, VARIANT para explorar, columnas para producir.
Ejercicios¶
- Inserta en una tabla
VARIANTtres filas con estructuras distintas (objeto, array, escalar) y muestravariant_typeofde cada una. - Extrae
user.iddeeventoscon notación por punto y convariant_extract. ¿Cuál devuelveVARIANTy cuál texto? - Escribe el mismo dataset como JSON y como VARIANT-Parquet y compara tamaños.
- Con 50 000 filas sintéticas, mide una consulta con
variant_minimum_shredding_sizepor defecto y con-1. ¿Cuánto cambia?
Soluciones en cuadernos/99_soluciones.ipynb.