Saltar al contenido principal
Este tipo representa una unión de otros tipos de datos. El tipo Variant(T1, T2, ..., TN) significa que cada fila de este tipo tiene un valor de tipo T1, T2, … TN o ninguno de ellos (valor NULL). El orden de los tipos anidados no importa: Variant(T1, T2) = Variant(T2, T1). Los tipos anidados pueden ser tipos arbitrarios, excepto los tipos Nullable(…), LowCardinality(Nullable(…)) y Variant(…).
No se recomienda usar tipos similares como variantes (por ejemplo, distintos tipos numéricos como Variant(UInt32, Int64) o distintos tipos de fecha como Variant(Date, DateTime)), porque trabajar con valores de esos tipos puede dar lugar a ambigüedades. De forma predeterminada, crear ese tipo Variant generará una excepción, pero puede habilitarse mediante la configuración allow_suspicious_variant_types

Creación de Variant

Uso del tipo Variant en la definición de una columna de una tabla:
CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT v FROM test;
┌─v─────────────┐
│ ᴺᵁᴸᴸ          │
│ 42            │
│ Hello, World! │
│ [1,2,3]       │
└───────────────┘
Uso de CAST con columnas normales:
SELECT toTypeName(variant) AS type_name, 'Hello, World!'::Variant(UInt64, String, Array(UInt64)) as variant;
┌─type_name──────────────────────────────┬─variant───────┐
│ Variant(Array(UInt64), String, UInt64) │ Hello, World! │
└────────────────────────────────────────┴───────────────┘
Uso de las funciones if/multiIf cuando los argumentos no tienen un tipo común (para ello, debe estar habilitada la configuración use_variant_as_common_type):
SET use_variant_as_common_type = 1;
SELECT if(number % 2, number, range(number)) as variant FROM numbers(5);
┌─variant───┐
│ []        │
│ 1         │
│ [0,1]     │
│ 3         │
│ [0,1,2,3] │
└───────────┘
SET use_variant_as_common_type = 1;
SELECT multiIf((number % 4) = 0, 42, (number % 4) = 1, [1, 2, 3], (number % 4) = 2, 'Hello, World!', NULL) AS variant FROM numbers(4);
┌─variant───────┐
│ 42            │
│ [1,2,3]       │
│ Hello, World! │
│ ᴺᵁᴸᴸ          │
└───────────────┘
Uso de las funciones ‘array/map’ cuando los elementos del array/los valores del map no tienen un tipo común (para ello, la configuración use_variant_as_common_type debe estar habilitada):
SET use_variant_as_common_type = 1;
SELECT array(range(number), number, 'str_' || toString(number)) as array_of_variants FROM numbers(3);
┌─array_of_variants─┐
│ [[],0,'str_0']    │
│ [[0],1,'str_1']   │
│ [[0,1],2,'str_2'] │
└───────────────────┘
SET use_variant_as_common_type = 1;
SELECT map('a', range(number), 'b', number, 'c', 'str_' || toString(number)) as map_of_variants FROM numbers(3);
┌─map_of_variants───────────────┐
│ {'a':[],'b':0,'c':'str_0'}    │
│ {'a':[0],'b':1,'c':'str_1'}   │
│ {'a':[0,1],'b':2,'c':'str_2'} │
└───────────────────────────────┘

Lectura de tipos Variant anidados como subcolumnas

El tipo Variant admite leer un único tipo anidado de una columna Variant usando el nombre del tipo como subcolumna. Por lo tanto, si tiene la columna variant Variant(T1, T2, T3), puede leer una subcolumna de tipo T2 con la sintaxis variant.T2; esta subcolumna tendrá el tipo Nullable(T2) si T2 puede estar dentro de Nullable, y T2 en caso contrario. Esta subcolumna tendrá el mismo tamaño que la columna Variant original y contendrá valores NULL (o valores vacíos si T2 no puede estar dentro de Nullable) en todas las filas en las que la columna Variant original no tenga el tipo T2. Las subcolumnas de Variant también se pueden leer usando la función variantElement(variant_column, type_name). Ejemplos:
CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT v, v.String, v.UInt64, v.`Array(UInt64)` FROM test;
┌─v─────────────┬─v.String──────┬─v.UInt64─┬─v.Array(UInt64)─┐
│ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ          │     ᴺᵁᴸᴸ │ []              │
│ 42            │ ᴺᵁᴸᴸ          │       42 │ []              │
│ Hello, World! │ Hello, World! │     ᴺᵁᴸᴸ │ []              │
│ [1,2,3]       │ ᴺᵁᴸᴸ          │     ᴺᵁᴸᴸ │ [1,2,3]         │
└───────────────┴───────────────┴──────────┴─────────────────┘
SELECT toTypeName(v.String), toTypeName(v.UInt64), toTypeName(v.`Array(UInt64)`) FROM test LIMIT 1;
┌─toTypeName(v.String)─┬─toTypeName(v.UInt64)─┬─toTypeName(v.Array(UInt64))─┐
│ Nullable(String)     │ Nullable(UInt64)     │ Array(UInt64)               │
└──────────────────────┴──────────────────────┴─────────────────────────────┘
SELECT v, variantElement(v, 'String'), variantElement(v, 'UInt64'), variantElement(v, 'Array(UInt64)') FROM test;
┌─v─────────────┬─variantElement(v, 'String')─┬─variantElement(v, 'UInt64')─┬─variantElement(v, 'Array(UInt64)')─┐
│ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ                        │                        ᴺᵁᴸᴸ │ []                                 │
│ 42            │ ᴺᵁᴸᴸ                        │                          42 │ []                                 │
│ Hello, World! │ Hello, World!               │                        ᴺᵁᴸᴸ │ []                                 │
│ [1,2,3]       │ ᴺᵁᴸᴸ                        │                        ᴺᵁᴸᴸ │ [1,2,3]                            │
└───────────────┴─────────────────────────────┴─────────────────────────────┴────────────────────────────────────┘
Para saber qué variante se almacena en cada fila, se puede usar la función variantType(variant_column). Devuelve un Enum con el nombre del tipo de variante para cada fila (o 'None' si la fila es NULL). Ejemplo:
CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT variantType(v) FROM test;
┌─variantType(v)─┐
│ None           │
│ UInt64         │
│ String         │
│ Array(UInt64)  │
└────────────────┘
SELECT toTypeName(variantType(v)) FROM test LIMIT 1;
┌─toTypeName(variantType(v))──────────────────────────────────────────┐
│ Enum8('None' = -1, 'Array(UInt64)' = 0, 'String' = 1, 'UInt64' = 2) │
└─────────────────────────────────────────────────────────────────────┘

Conversión entre una columna Variant y otras columnas

Hay 4 conversiones posibles que pueden realizarse con una columna Variant.

Conversión de una columna String a una columna Variant

La conversión de String a Variant se realiza analizando un valor de tipo Variant a partir de la cadena:
SELECT '42'::Variant(String, UInt64) AS variant, variantType(variant) AS variant_type
┌─variant─┬─variant_type─┐
│ 42      │ UInt64       │
└─────────┴──────────────┘
SELECT '[1, 2, 3]'::Variant(String, Array(UInt64)) as variant, variantType(variant) as variant_type
┌─variant─┬─variant_type──┐
│ [1,2,3] │ Array(UInt64) │
└─────────┴───────────────┘
SELECT CAST(map('key1', '42', 'key2', 'true', 'key3', '2020-01-01'), 'Map(String, Variant(UInt64, Bool, Date))') AS map_of_variants, mapApply((k, v) -> (k, variantType(v)), map_of_variants) AS map_of_variant_types```
┌─map_of_variants─────────────────────────────┬─map_of_variant_types──────────────────────────┐
│ {'key1':42,'key2':true,'key3':'2020-01-01'} │ {'key1':'UInt64','key2':'Bool','key3':'Date'} │
└─────────────────────────────────────────────┴───────────────────────────────────────────────┘
Para desactivar el análisis sintáctico durante la conversión de String a Variant, puede desactivar la opción cast_string_to_dynamic_use_inference:
SET cast_string_to_variant_use_inference = 0;
SELECT '[1, 2, 3]'::Variant(String, Array(UInt64)) as variant, variantType(variant) as variant_type
┌─variant───┬─variant_type─┐
│ [1, 2, 3] │ String       │
└───────────┴──────────────┘

Convertir una columna ordinaria en una columna Variant

Es posible convertir una columna ordinaria de tipo T en una columna Variant que contenga ese tipo:
SELECT toTypeName(variant) AS type_name, [1,2,3]::Array(UInt64)::Variant(UInt64, String, Array(UInt64)) as variant, variantType(variant) as variant_name
┌─type_name──────────────────────────────┬─variant─┬─variant_name──┐
│ Variant(Array(UInt64), String, UInt64) │ [1,2,3] │ Array(UInt64) │
└────────────────────────────────────────┴─────────┴───────────────┘
Nota: la conversión desde el tipo String siempre se realiza mediante análisis sintáctico; si necesita convertir una columna String a la variante String de un Variant sin realizar ese análisis, puede hacer lo siguiente:
SELECT '[1, 2, 3]'::Variant(String)::Variant(String, Array(UInt64), UInt64) as variant, variantType(variant) as variant_type
┌─variant───┬─variant_type─┐
│ [1, 2, 3] │ String       │
└───────────┴──────────────┘

Conversión de una columna Variant en una columna ordinaria

Es posible convertir una columna Variant en una columna ordinaria. En este caso, todas las variantes anidadas se convertirán al tipo de destino:
CREATE TABLE test (v Variant(UInt64, String)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('42.42');
SELECT v::Nullable(Float64) FROM test;
┌─CAST(v, 'Nullable(Float64)')─┐
│                         ᴺᵁᴸᴸ │
│                           42 │
│                        42.42 │
└──────────────────────────────┘

Conversión de un Variant a otro Variant

Es posible convertir una columna Variant en otra columna Variant, pero solo si la columna Variant de destino contiene todos los tipos anidados de la columna Variant original:
CREATE TABLE test (v Variant(UInt64, String)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('String');
SELECT v::Variant(UInt64, String, Array(UInt64)) FROM test;
┌─CAST(v, 'Variant(UInt64, String, Array(UInt64))')─┐
│ ᴺᵁᴸᴸ                                              │
│ 42                                                │
│ String                                            │
└───────────────────────────────────────────────────┘

Lectura del tipo Variant a partir de los datos

Todos los formatos de texto (TSV, CSV, CustomSeparated, Values, JSONEachRow, etc.) admiten la lectura del tipo Variant. Durante el análisis de los datos, ClickHouse intenta insertar el valor en el tipo de variante más adecuado. Ejemplo:
SELECT
    v,
    variantElement(v, 'String') AS str,
    variantElement(v, 'UInt64') AS num,
    variantElement(v, 'Float64') AS float,
    variantElement(v, 'DateTime') AS date,
    variantElement(v, 'Array(UInt64)') AS arr
FROM format(JSONEachRow, 'v Variant(String, UInt64, Float64, DateTime, Array(UInt64))', $$
{"v" : "Hello, World!"},
{"v" : 42},
{"v" : 42.42},
{"v" : "2020-01-01 00:00:00"},
{"v" : [1, 2, 3]}
$$)
┌─v───────────────────┬─str───────────┬──num─┬─float─┬────────────────date─┬─arr─────┐
│ Hello, World!       │ Hello, World! │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ []      │
│ 42                  │ ᴺᵁᴸᴸ          │   42 │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ []      │
│ 42.42               │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │ 42.42 │                ᴺᵁᴸᴸ │ []      │
│ 2020-01-01 00:00:00 │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │ 2020-01-01 00:00:00 │ []      │
│ [1,2,3]             │ ᴺᵁᴸᴸ          │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │                ᴺᵁᴸᴸ │ [1,2,3] │
└─────────────────────┴───────────────┴──────┴───────┴─────────────────────┴─────────┘

Comparación de valores del tipo Variant

Los valores de tipo Variant solo pueden compararse con valores del mismo tipo Variant. De forma predeterminada, los operadores de comparación usan la implementación predeterminada para Variant, aplicando la comparación a cada tipo variante por separado. Esto puede deshabilitarse mediante la configuración use_variant_default_implementation_for_comparisons = 0 para usar las reglas nativas de comparación de Variant que se describen a continuación. Nota: ORDER BY siempre usa la comparación nativa. Reglas nativas de comparación de Variant: El resultado del operador < para los valores v1 con tipo subyacente T1 y v2 con tipo subyacente T2 de un tipo Variant(..., T1, ... T2, ...) se define de la siguiente manera:
  • Si T1 = T2 = T, el resultado será v1.T < v2.T (se compararán los valores subyacentes).
  • Si T1 != T2, el resultado será T1 < T2 (se compararán los nombres de tipo).
Ejemplos:
SET allow_suspicious_types_in_order_by = 1;
CREATE TABLE test (v1 Variant(String, UInt64, Array(UInt32)), v2 Variant(String, UInt64, Array(UInt32))) ENGINE=Memory;
INSERT INTO test VALUES (42, 42), (42, 43), (42, 'abc'), (42, [1, 2, 3]), (42, []), (42, NULL);
SELECT v2, variantType(v2) AS v2_type FROM test ORDER BY v2;
┌─v2──────┬─v2_type───────┐
│ []      │ Array(UInt32) │
│ [1,2,3] │ Array(UInt32) │
│ abc     │ String        │
│ 42      │ UInt64        │
│ 43      │ UInt64        │
│ ᴺᵁᴸᴸ    │ None          │
└─────────┴───────────────┘
SELECT v1, variantType(v1) AS v1_type, v2, variantType(v2) AS v2_type, v1 = v2, v1 < v2, v1 > v2 FROM test;
┌─v1─┬─v1_type─┬─v2──────┬─v2_type───────┬─equals(v1, v2)─┬─less(v1, v2)─┬─greater(v1, v2)─┐
│ 42 │ UInt64  │ 42      │ UInt64        │              1 │            0 │               0 │
│ 42 │ UInt64  │ 43      │ UInt64        │              0 │            1 │               0 │
│ 42 │ UInt64  │ abc     │ String        │              0 │            0 │               1 │
│ 42 │ UInt64  │ [1,2,3] │ Array(UInt32) │              0 │            0 │               1 │
│ 42 │ UInt64  │ []      │ Array(UInt32) │              0 │            0 │               1 │
│ 42 │ UInt64  │ ᴺᵁᴸᴸ    │ None          │              0 │            1 │               0 │
└────┴─────────┴─────────┴───────────────┴────────────────┴──────────────┴─────────────────┘

Si necesita encontrar la fila con un valor Variant específico, puede hacer una de las siguientes opciones:
  • Convertir el valor al tipo Variant correspondiente:
SELECT * FROM test WHERE v2 == [1,2,3]::Array(UInt32)::Variant(String, UInt64, Array(UInt32));
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘
  • Compare la subcolumna Variant con el tipo requerido:
SELECT * FROM test WHERE v2.`Array(UInt32)` == [1,2,3] -- o usando variantElement(v2, 'Array(UInt32)')
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘
A veces puede ser útil hacer una comprobación adicional del tipo Variant, ya que las subcolumnas de tipos complejos como Array/Map/Tuple no pueden estar dentro de Nullable y tendrán valores predeterminados en lugar de NULL en las filas con tipos distintos:
SELECT v2, v2.`Array(UInt32)`, variantType(v2) FROM test WHERE v2.`Array(UInt32)` == [];
┌─v2───┬─v2.Array(UInt32)─┬─variantType(v2)─┐
│ 42   │ []               │ UInt64          │
│ 43   │ []               │ UInt64          │
│ abc  │ []               │ String          │
│ []   │ []               │ Array(UInt32)   │
│ ᴺᵁᴸᴸ │ []               │ None            │
└──────┴──────────────────┴─────────────────┘
SELECT v2, v2.`Array(UInt32)`, variantType(v2) FROM test WHERE variantType(v2) == 'Array(UInt32)' AND v2.`Array(UInt32)` == [];
┌─v2─┬─v2.Array(UInt32)─┬─variantType(v2)─┐
│ [] │ []               │ Array(UInt32)   │
└────┴──────────────────┴─────────────────┘
Nota: los valores de variantes con distintos tipos numéricos se consideran variantes diferentes y no se comparan entre sí; en su lugar, se comparan sus nombres de tipo. Ejemplo:
SET allow_suspicious_variant_types = 1;
CREATE TABLE test (v Variant(UInt32, Int64)) ENGINE=Memory;
INSERT INTO test VALUES (1::UInt32), (1::Int64), (100::UInt32), (100::Int64);
SELECT v, variantType(v) FROM test ORDER by v;
┌─v───┬─variantType(v)─┐
│ 1   │ Int64          │
│ 100 │ Int64          │
│ 1   │ UInt32         │
│ 100 │ UInt32         │
└─────┴────────────────┘
Nota: de forma predeterminada, el tipo Variant no está permitido en las claves GROUP BY/ORDER BY; si desea usarlo, tenga en cuenta su regla especial de comparación y habilite las opciones allow_suspicious_types_in_group_by/allow_suspicious_types_in_order_by.

Funciones JSONExtract con Variant

Todas las funciones JSONExtract* admiten el tipo Variant:
SELECT JSONExtract('{"a" : [1, 2, 3]}', 'a', 'Variant(UInt32, String, Array(UInt32))') AS variant, variantType(variant) AS variant_type;
┌─variant─┬─variant_type──┐
│ [1,2,3] │ Array(UInt32) │
└─────────┴───────────────┘
SELECT JSONExtract('{"obj" : {"a" : 42, "b" : "Hello", "c" : [1,2,3]}}', 'obj', 'Map(String, Variant(UInt32, String, Array(UInt32)))') AS map_of_variants, mapApply((k, v) -> (k, variantType(v)), map_of_variants) AS map_of_variant_types
┌─map_of_variants──────────────────┬─map_of_variant_types────────────────────────────┐
│ {'a':42,'b':'Hello','c':[1,2,3]} │ {'a':'UInt32','b':'String','c':'Array(UInt32)'} │
└──────────────────────────────────┴─────────────────────────────────────────────────┘
SELECT JSONExtractKeysAndValues('{"a" : 42, "b" : "Hello", "c" : [1,2,3]}', 'Variant(UInt32, String, Array(UInt32))') AS variants, arrayMap(x -> (x.1, variantType(x.2)), variants) AS variant_types
┌─variants───────────────────────────────┬─variant_types─────────────────────────────────────────┐
│ [('a',42),('b','Hello'),('c',[1,2,3])] │ [('a','UInt32'),('b','String'),('c','Array(UInt32)')] │
└────────────────────────────────────────┴───────────────────────────────────────────────────────┘

Funciones con argumentos de tipo Variant

La mayoría de las funciones de ClickHouse admiten automáticamente argumentos del tipo Variant mediante una implementación predeterminada para Variant. A partir de la versión 26.1, cuando una función que no maneja explícitamente tipos Variant recibe una columna Variant, ClickHouse:
  1. Extrae cada tipo Variant de la columna Variant
  2. Ejecuta la función por separado para cada tipo Variant
  3. Combina los resultados de forma adecuada según los tipos de resultado
Esto permite usar funciones normales con columnas Variant sin necesidad de un manejo especial. Ejemplo:
CREATE TABLE test (v Variant(UInt32, String)) ENGINE = Memory;
INSERT INTO test VALUES (42), ('hello'), (NULL);
SELECT *, toTypeName(v) FROM test WHERE v = 42;
   ┌─v──┬─toTypeName(v)───────────┐
1. │ 42 │ Variant(String, UInt32) │
   └────┴─────────────────────────┘
El operador de comparación se aplica automáticamente a cada tipo Variant por separado, lo que permite filtrar por columnas Variant. Comportamiento del tipo de resultado: El tipo de resultado depende de lo que la función devuelva para cada Variant:
  • Tipos de resultado diferentes: Variant(T1, T2, ...)
    CREATE TABLE test2 (v Variant(UInt64, Float64)) ENGINE = Memory;
    INSERT INTO test2 VALUES (42::UInt64), (42.42);
    SELECT v + 1 AS result, toTypeName(result) FROM test2;
    
    ┌─result─┬─toTypeName(plus(v, 1))──┐
    │     43 │ Variant(Float64, UInt64) │
    │  43.42 │ Variant(Float64, UInt64) │
    └────────┴─────────────────────────┘
    
  • Incompatibilidad de tipos: NULL para variantes incompatibles
    CREATE TABLE test3 (v Variant(Array(UInt32), UInt32)) ENGINE = Memory;
    INSERT INTO test3 VALUES ([1,2,3]), (42);
    SELECT v + 10 AS result, toTypeName(result) FROM test3;
    
    ┌─result─┬─toTypeName(plus(v, 10))─┐
    │   ᴺᵁᴸᴸ │ Nullable(UInt64)        │
    │     52 │ Nullable(UInt64)        │
    └────────┴─────────────────────────┘
    
Manejo de errores: Cuando una función no puede procesar un tipo Variant, solo se capturan los errores relacionados con tipos (ILLEGAL_TYPE_OF_ARGUMENT, TYPE_MISMATCH, CANNOT_CONVERT_TYPE, NO_COMMON_TYPE) y el resultado es NULL para esas filas. Otros errores, como la división por cero o la falta de memoria, se generan normalmente para evitar ocultar silenciosamente problemas reales.

Comportamiento ante incompatibilidades de tipos

La configuración variant_throw_on_type_mismatch controla lo que ocurre cuando se aplica una función a una columna Variant y el tipo real almacenado en una fila es incompatible con la función:
  • true (predeterminado) — genera una excepción (ILLEGAL_TYPE_OF_ARGUMENT) en la primera fila incompatible.
  • false — devuelve NULL para las filas incompatibles y conserva el resultado para las filas compatibles.
Ejemplo:
CREATE TABLE test (v Variant(String, UInt64)) ENGINE = Memory;
INSERT INTO test VALUES ('hello'), (42), ('foo');

-- Por defecto (lanza excepción en caso de incompatibilidad): length() no acepta UInt64, por lo que la consulta lanza una excepción.
SELECT length(v) FROM test;  -- lanza ILLEGAL_TYPE_OF_ARGUMENT

-- Con la excepción deshabilitada: las filas incompatibles devuelven NULL.
SET variant_throw_on_type_mismatch = false;
SELECT v, length(v) FROM test ORDER BY v::String NULLS LAST;
┌─v─────┬─length(v)─┐
│ foo   │         3 │
│ hello │         5 │
│ 42    │      ᴺᵁᴸᴸ │
└───────┴───────────┘
Última modificación el 10 de junio de 2026