Этот тип представляет собой объединение других типов данных. Тип Variant(T1, T2, ..., TN) означает, что каждая строка этого типа
имеет значение либо типа T1, либо T2, либо … либо TN, либо не имеет ни одного из них (значение NULL).
Порядок вложенных типов не имеет значения: Variant(T1, T2) = Variant(T2, T1).
Вложенные типы могут быть любыми, кроме Nullable(…), LowCardinality(Nullable(…)) и Variant(…).
Не рекомендуется использовать в качестве вариантов схожие типы (например, разные числовые типы, такие как Variant(UInt32, Int64), или разные типы дат, такие как Variant(Date, DateTime)),
поскольку работа со значениями таких типов может приводить к неоднозначности. По умолчанию создание такого типа Variant приводит к исключению, но это поведение можно включить с помощью настройки allow_suspicious_variant_types
Использование типа Variant при определении столбца таблицы:
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] │
└───────────────┘
Использование CAST для обычных столбцов:
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! │
└────────────────────────────────────────┴───────────────┘
Использование функций if/multiIf, если у аргументов нет общего типа (для этого должен быть включен параметр 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! │
│ ᴺᵁᴸᴸ │
└───────────────┘
Использование функций ‘array/map’, если у элементов массива/значений map нет общего типа (для этого должен быть включен параметр use_variant_as_common_type):
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'} │
└───────────────────────────────┘
Чтение вложенных типов Variant как подстолбцов
Тип Variant поддерживает чтение отдельного вложенного типа из столбца Variant, используя имя типа в качестве подстолбца.
Так, если у вас есть столбец variant Variant(T1, T2, T3), вы можете прочитать подстолбец типа T2 с помощью синтаксиса variant.T2.
Этот подстолбец будет иметь тип Nullable(T2), если T2 может находиться внутри Nullable, и тип T2 в противном случае. Размер этого подстолбца будет
таким же, как у исходного столбца Variant, и он будет содержать значения NULL (или пустые значения, если T2 не может находиться внутри Nullable)
во всех строках, в которых исходный столбец Variant не имеет тип T2.
Подстолбцы Variant также можно читать с помощью функции variantElement(variant_column, type_name).
Примеры:
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] │
└───────────────┴─────────────────────────────┴─────────────────────────────┴────────────────────────────────────┘
Чтобы узнать, какой вариант хранится в каждой строке, можно использовать функцию variantType(variant_column). Она возвращает Enum с именем типа варианта для каждой строки (или 'None', если строка имеет значение NULL).
Пример:
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) │
└─────────────────────────────────────────────────────────────────────┘
Преобразование между столбцом Variant и другими столбцами
Возможны 4 преобразования для столбца типа Variant.
Преобразование столбца String в столбец Variant
Преобразование из String в Variant выполняется путём синтаксического разбора строкового значения как значения типа Variant:
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'} │
└─────────────────────────────────────────────┴───────────────────────────────────────────────┘
Чтобы отключить разбор при преобразовании из String в Variant, можно отключить настройку 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 │
└───────────┴──────────────┘
Преобразование обычного столбца в столбец типа Variant
Обычный столбец типа T можно преобразовать в столбец Variant, содержащий этот тип:
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) │
└────────────────────────────────────────┴─────────┴───────────────┘
Примечание: преобразование из типа String всегда выполняется через разбор; если вам нужно преобразовать столбец String в вариант String типа Variant без разбора, можно сделать следующее:
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 │
└───────────┴──────────────┘
Преобразование столбца Variant в обычный столбец
Столбец Variant можно преобразовать в обычный столбец. В этом случае все вложенные варианты будут преобразованы в целевой тип:
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 │
└──────────────────────────────┘
Преобразование Variant в другой Variant
Можно преобразовать столбец Variant в другой столбец Variant, но только если целевой столбец Variant содержит все вложенные типы исходного Variant:
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 │
└───────────────────────────────────────────────────┘
Чтение типа Variant из данных
Все текстовые форматы (TSV, CSV, CustomSeparated, Values, JSONEachRow и т. д.) поддерживают чтение типа Variant. При разборе данных ClickHouse пытается вставить значение в наиболее подходящий тип варианта.
Пример:
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] │
└─────────────────────┴───────────────┴──────┴───────┴─────────────────────┴─────────┘
Сравнение значений типа Variant
Значения типа Variant можно сравнивать только со значениями того же типа Variant.
По умолчанию операторы сравнения используют реализацию по умолчанию для Variant,
выполняя сравнение отдельно для каждого типа варианта. Это поведение можно отключить настройкой use_variant_default_implementation_for_comparisons = 0,
чтобы использовать нативные правила сравнения Variant, описанные ниже. Обратите внимание, что ORDER BY всегда использует нативное сравнение.
Нативные правила сравнения Variant:
Результат оператора < для значений v1 с базовым типом T1 и v2 с базовым типом T2 типа Variant(..., T1, ... T2, ...) определяется следующим образом:
- Если
T1 = T2 = T, результатом будет v1.T < v2.T (будут сравниваться внутренние значения).
- Если
T1 != T2, результатом будет T1 < T2 (будут сравниваться имена типов).
Примеры:
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 │
└────┴─────────┴─────────┴───────────────┴────────────────┴──────────────┴─────────────────┘
Если вам нужно найти строку с определённым значением Variant, вы можете сделать одно из следующего:
- Привести значение к соответствующему типу
Variant:
SELECT * FROM test WHERE v2 == [1,2,3]::Array(UInt32)::Variant(String, UInt64, Array(UInt32));
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘
- Сравните подстолбец
Variant с нужным типом:
SELECT * FROM test WHERE v2.`Array(UInt32)` == [1,2,3] -- или используя variantElement(v2, 'Array(UInt32)')
┌─v1─┬─v2──────┐
│ 42 │ [1,2,3] │
└────┴─────────┘
Иногда полезно дополнительно проверять тип варианта, поскольку подстолбцы со сложными типами, такими как Array/Map/Tuple, не могут находиться внутри Nullable и в строках с другими типами будут иметь значения по умолчанию вместо NULL:
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) │
└────┴──────────────────┴─────────────────┘
Примечание: значения Variant с разными числовыми типами считаются различными вариантами и между собой не сравниваются; вместо этого сравниваются их имена типов.
Пример:
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 │
└─────┴────────────────┘
Примечание: по умолчанию тип Variant нельзя использовать в ключах GROUP BY/ORDER BY; если вы хотите его использовать, учитывайте особые правила его сравнения и включите настройки allow_suspicious_types_in_group_by/allow_suspicious_types_in_order_by.
Все функции JSONExtract* поддерживают тип 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)')] │
└────────────────────────────────────────┴───────────────────────────────────────────────────────┘
Функции с аргументами Variant
Большинство функций в ClickHouse автоматически поддерживают аргументы типа Variant благодаря реализации по умолчанию для Variant.
Начиная с версии 26.1, если функция, которая не обрабатывает типы Variant явно, получает столбец Variant, ClickHouse:
- Извлекает каждый тип варианта из столбца Variant
- Выполняет функцию отдельно для каждого типа варианта
- Объединяет результаты с учётом их типов
Это позволяет использовать обычные функции со столбцами Variant без какой-либо специальной обработки.
Пример:
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) │
└────┴─────────────────────────┘
Оператор сравнения автоматически применяется отдельно к каждому типу варианта, что позволяет фильтровать по столбцам Variant.
Поведение результирующего типа:
Результирующий тип зависит от того, что функция возвращает для каждого варианта:
-
Разные результирующие типы:
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) │
└────────┴─────────────────────────┘
-
Несовместимость типов:
NULL для несовместимых вариантов
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) │
└────────┴─────────────────────────┘
Обработка ошибок: Если функция не может обработать тип варианта, перехватываются только ошибки, связанные с типами (ILLEGAL_TYPE_OF_ARGUMENT,
TYPE_MISMATCH, CANNOT_CONVERT_TYPE, NO_COMMON_TYPE), и для таких строк результатом становится NULL. Другие ошибки, например
деление на ноль или нехватка памяти, вызываются как обычно, чтобы не скрывать реальные проблемы.
Поведение при несоответствии типов
Параметр variant_throw_on_type_mismatch определяет, что происходит, когда функция применяется к столбцу Variant, а фактический тип значения в строке несовместим с этой функцией:
true (по умолчанию) — сгенерировать исключение (ILLEGAL_TYPE_OF_ARGUMENT) при первой несовместимой строке.
false — возвращать NULL для несовместимых строк и сохранять результат для совместимых строк.
Пример:
CREATE TABLE test (v Variant(String, UInt64)) ENGINE = Memory;
INSERT INTO test VALUES ('hello'), (42), ('foo');
-- По умолчанию (сгенерировать исключение при несоответствии типов): length() не принимает UInt64, поэтому запрос завершается с ошибкой.
SELECT length(v) FROM test; -- генерирует ILLEGAL_TYPE_OF_ARGUMENT
-- При отключённом исключении: несовместимые строки возвращают 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 │ ᴺᵁᴸᴸ │
└───────┴───────────┘
Последнее изменение 10 июня 2026 г.