Este tipo representa uma união de outros tipos de dados. O tipo Variant(T1, T2, ..., TN) significa que cada linha desse tipo
tem um valor do tipo T1, T2, … ou TN, ou de nenhum deles (valor NULL).
A ordem dos tipos aninhados não importa: Variant(T1, T2) = Variant(T2, T1). Os tipos aninhados podem ser quaisquer tipos, exceto Nullable(…), LowCardinality(Nullable(…)) e Variant(…).
Criando Variant
Usando o tipo Variant na definição de uma coluna da tabela:
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] │
└───────────────┘Usando CAST de colunas comuns:
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! │
└────────────────────────────────────────┴───────────────┘Usando as funções if/multiIf quando os argumentos não têm um tipo em comum (a configuração use_variant_as_common_type deve estar habilitada para isso):
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! │
│ ᴺᵁᴸᴸ │
└───────────────┘Ao usar as funções 'array/map' se os elementos do array/valores do map não tiverem um tipo em comum (a configuração use_variant_as_common_type deve estar habilitada para isso):
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'} │
└───────────────────────────────┘Lendo tipos aninhados de Variant como subcolunas
O tipo Variant oferece suporte à leitura de um único tipo aninhado de uma coluna Variant usando o nome do tipo como subcoluna.
Assim, se você tiver a coluna variant Variant(T1, T2, T3), poderá ler uma subcoluna do tipo T2 usando a sintaxe variant.T2;
essa subcoluna terá o tipo Nullable(T2) se T2 puder estar dentro de Nullable, e T2 caso contrário. Essa subcoluna
terá o mesmo tamanho da coluna Variant original e conterá valores NULL (ou valores vazios se T2 não puder estar dentro de Nullable)
em todas as linhas em que a coluna Variant original não tiver o tipo T2.
As subcolunas de Variant também podem ser lidas usando a função variantElement(variant_column, type_name).
Exemplos:
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 qual variante está armazenado em cada linha, pode-se usar a função variantType(variant_column). Ela retorna um Enum com o nome do tipo variante de cada linha (ou 'None' se a linha for NULL).
Exemplo:
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) │
└─────────────────────────────────────────────────────────────────────┘Conversão entre uma coluna Variant e outras colunas
Há 4 conversões possíveis que podem ser feitas com uma coluna do tipo Variant.
Convertendo uma coluna String em uma coluna Variant
A conversão de String para Variant é feita fazendo o parsing de um valor do tipo Variant a partir do valor em string:
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 desativar o parsing durante a conversão de String para Variant, desative a configuração 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 │
└───────────┴──────────────┘Convertendo uma coluna comum em uma coluna Variant
É possível converter uma coluna comum do tipo T em uma coluna Variant que contenha esse 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) │
└────────────────────────────────────────┴─────────┴───────────────┘Observação: a conversão a partir do tipo String sempre é feita por meio de parsing; se você precisar converter uma coluna String para a variante String de um Variant sem fazer parsing, faça o seguinte:
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 │
└───────────┴──────────────┘Convertendo uma coluna Variant em uma coluna comum
É possível converter uma coluna Variant em uma coluna comum. Nesse caso, todas as variantes aninhadas serão convertidas para um 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 │
└──────────────────────────────┘Convertendo um Variant em outro Variant
É possível converter uma coluna Variant em outra coluna Variant, mas apenas se a coluna Variant de destino contiver todos os tipos aninhados da coluna 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 │
└───────────────────────────────────────────────────┘Lendo o tipo Variant a partir dos dados
Todos os formatos de texto (TSV, CSV, CustomSeparated, Values, JSONEachRow etc.) oferecem suporte à leitura do tipo Variant. Durante o parsing dos dados, o ClickHouse tenta inserir o valor no tipo Variant mais apropriado.
Exemplo:
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] │
└─────────────────────┴───────────────┴──────┴───────┴─────────────────────┴─────────┘Comparando valores do tipo Variant
Valores de um tipo Variant só podem ser comparados com valores do mesmo tipo Variant.
Por padrão, os operadores de comparação usam a implementação padrão para Variant,
aplicando a comparação a cada tipo variante separadamente. Isso pode ser desativado usando a configuração use_variant_default_implementation_for_comparisons = 0
para usar as regras nativas de comparação de Variant descritas abaixo. Observação: ORDER BY sempre usa comparação nativa.
Regras nativas de comparação de Variant:
O resultado do operador < para os valores v1, com tipo subjacente T1, e v2, com tipo subjacente T2, de um tipo Variant(..., T1, ... T2, ...) é definido da seguinte forma:
- Se
T1 = T2 = T, o resultado seráv1.T < v2.T(os valores subjacentes serão comparados). - Se
T1 != T2, o resultado seráT1 < T2(os nomes dos tipos serão comparados).
Exemplos:
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);SET allow_suspicious_types_in_order_by = 1;
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 │
└─────────┴───────────────┘SET use_variant_default_implementation_for_comparisons = 0;
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 │
└────┴─────────┴─────────┴───────────────┴────────────────┴──────────────┴─────────────────┘Se você precisar encontrar a linha com um valor Variant específico, pode usar uma das opções a seguir:
- Fazer cast do valor para o tipo
Variantcorrespondente:
SET use_variant_default_implementation_for_comparisons = 0;
SELECT * FROM test WHERE v2 == [1,2,3]::Array(UInt32)::Variant(String, UInt64, Array(UInt32));┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘- Compare a subcoluna
Variantcom o tipo necessário:
SELECT * FROM test WHERE v2.`Array(UInt32)` == [1,2,3] -- or using variantElement(v2, 'Array(UInt32)')┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘Às vezes, pode ser útil fazer uma verificação adicional do tipo Variant, pois subcolunas com tipos complexos como Array/Map/Tuple não podem estar dentro de Nullable e terão valores padrão em vez de NULL em linhas com tipos diferentes:
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) │
└────┴──────────────────┴─────────────────┘Observação: valores de variantes com tipos numéricos diferentes são considerados variantes distintas e não são comparados entre si; em vez disso, seus nomes de tipos são comparados.
Exemplo:
SET allow_suspicious_variant_types = 1;
SET allow_suspicious_types_in_order_by = 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 │
└─────┴────────────────┘Observação: por padrão, o tipo Variant não é permitido em chaves GROUP BY/ORDER BY; se quiser usá-lo, considere sua regra especial de comparação e habilite as configurações allow_suspicious_types_in_group_by/allow_suspicious_types_in_order_by.
Funções JSONExtract com Variant
Todas as funções JSONExtract* oferecem suporte ao 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)')] │
└────────────────────────────────────────┴───────────────────────────────────────────────────────┘Funções com argumentos variante
A maioria das funções no ClickHouse oferece suporte automático a argumentos do tipo Variant por meio de uma implementação padrão para Variant.
A partir da versão 26.1, quando uma função que não trata explicitamente tipos Variant recebe uma coluna Variant, o ClickHouse:
- Extrai cada tipo variante da coluna Variant
- Executa a função separadamente para cada tipo variante
- Combina os resultados adequadamente com base nos tipos de resultado
Isso permite usar funções comuns com colunas Variant sem precisar de tratamento especial.
Exemplo:
SET variant_throw_on_type_mismatch = 0;
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)───────────┐
│ 42 │ Variant(String, UInt32) │
└────┴─────────────────────────┘O operador de comparação é aplicado automaticamente a cada tipo Variant separadamente, permitindo filtrar colunas Variant.
Comportamento do tipo de resultado:
O tipo de resultado depende do que a função retorna para cada variante:
- Tipos de resultado diferentes:
Variant(T1, T2, ...)
SET variant_throw_on_type_mismatch = 0;
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) │
└────────┴─────────────────────────┘- Incompatibilidade de tipos:
NULLpara variantes incompatíveis
SET variant_throw_on_type_mismatch = 0;
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) │
└────────┴─────────────────────────┘Comportamento em caso de incompatibilidade de tipo
A configuração variant_throw_on_type_mismatch controla o que acontece quando uma função é aplicada a uma coluna Variant e o tipo efetivamente armazenado em uma linha é incompatível com a função:
true(padrão) — gera uma exceção (ILLEGAL_TYPE_OF_ARGUMENT) na primeira linha incompatível.false— retornaNULLpara as linhas incompatíveis e mantém o resultado para as linhas compatíveis.
Exemplo:
CREATE TABLE test (v Variant(String, UInt64)) ENGINE = Memory;
INSERT INTO test VALUES ('hello'), (42), ('foo');
-- Default (throw on mismatch): length() does not accept UInt64, so the query throws.
SELECT length(v) FROM test; -- throws ILLEGAL_TYPE_OF_ARGUMENT
-- With throw disabled: incompatible rows return 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 │ ᴺᵁᴸᴸ │
└───────┴───────────┘