Saltar a contenido

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_size filas (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

  1. Inserta en una tabla VARIANT tres filas con estructuras distintas (objeto, array, escalar) y muestra variant_typeof de cada una.
  2. Extrae user.id de eventos con notación por punto y con variant_extract. ¿Cuál devuelve VARIANT y cuál texto?
  3. Escribe el mismo dataset como JSON y como VARIANT-Parquet y compara tamaños.
  4. Con 50 000 filas sintéticas, mide una consulta con variant_minimum_shredding_size por defecto y con -1. ¿Cuánto cambia?

Soluciones en cuadernos/99_soluciones.ipynb.