Этот тип позволяет хранить значения любых типов, не зная заранее полный их набор.
Чтобы объявить столбец типа Dynamic, используйте следующий синтаксис:
<column_name> Dynamic(max_types=N)
Где N — необязательный параметр в диапазоне от 0 до 254, который указывает, сколько различных типов данных может храниться в виде отдельных подстолбцов внутри столбца типа Dynamic в рамках одного отдельно хранимого блока данных (например, в пределах одной части данных для таблицы семейства MergeTree). Если этот предел превышен, все значения новых типов будут храниться вместе в специальной общей структуре данных в бинарной форме. Значение max_types по умолчанию — 32.
Использование типа Dynamic в определении столбца таблицы:
CREATE TABLE test (d Dynamic) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT d, dynamicType(d) FROM test;
┌─d─────────────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ 42 │ Int64 │
│ Hello, World! │ String │
│ [1,2,3] │ Array(Int64) │
└───────────────┴────────────────┘
Использование CAST с обычным столбцом:
SELECT 'Hello, World!'::Dynamic AS d, dynamicType(d);
┌─d─────────────┬─dynamicType(d)─┐
│ Hello, World! │ String │
└───────────────┴────────────────┘
Использование CAST со столбцом Variant:
SET use_variant_as_common_type = 1;
SELECT multiIf((number % 3) = 0, number, (number % 3) = 1, range(number + 1), NULL)::Dynamic AS d, dynamicType(d) FROM numbers(3)
┌─d─────┬─dynamicType(d)─┐
│ 0 │ UInt64 │
│ [0,1] │ Array(UInt64) │
│ ᴺᵁᴸᴸ │ None │
└───────┴────────────────┘
Чтение вложенных типов Dynamic как подстолбцов
Тип Dynamic поддерживает чтение отдельного вложенного типа из столбца Dynamic, используя имя типа в качестве подстолбца.
Так, если у вас есть столбец d Dynamic, вы можете прочитать подстолбец любого допустимого типа T, используя синтаксис d.T.
Этот подстолбец будет иметь тип Nullable(T), если T может находиться внутри Nullable, и T в противном случае. Размер этого подстолбца
будет таким же, как у исходного столбца Dynamic, и он будет содержать значения NULL (или пустые значения, если T не может находиться внутри Nullable)
во всех строках, где исходный столбец Dynamic не имеет типа T.
Подстолбцы Dynamic также можно читать с помощью функции dynamicElement(dynamic_column, type_name).
Примеры:
CREATE TABLE test (d Dynamic) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT d, dynamicType(d), d.String, d.Int64, d.`Array(Int64)`, d.Date, d.`Array(String)` FROM test;
┌─d─────────────┬─dynamicType(d)─┬─d.String──────┬─d.Int64─┬─d.Array(Int64)─┬─d.Date─┬─d.Array(String)─┐
│ ᴺᵁᴸᴸ │ None │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [] │ ᴺᵁᴸᴸ │ [] │
│ 42 │ Int64 │ ᴺᵁᴸᴸ │ 42 │ [] │ ᴺᵁᴸᴸ │ [] │
│ Hello, World! │ String │ Hello, World! │ ᴺᵁᴸᴸ │ [] │ ᴺᵁᴸᴸ │ [] │
│ [1,2,3] │ Array(Int64) │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [1,2,3] │ ᴺᵁᴸᴸ │ [] │
└───────────────┴────────────────┴───────────────┴─────────┴────────────────┴────────┴─────────────────┘
SELECT toTypeName(d.String), toTypeName(d.Int64), toTypeName(d.`Array(Int64)`), toTypeName(d.Date), toTypeName(d.`Array(String)`) FROM test LIMIT 1;
┌─toTypeName(d.String)─┬─toTypeName(d.Int64)─┬─toTypeName(d.Array(Int64))─┬─toTypeName(d.Date)─┬─toTypeName(d.Array(String))─┐
│ Nullable(String) │ Nullable(Int64) │ Array(Int64) │ Nullable(Date) │ Array(String) │
└──────────────────────┴─────────────────────┴────────────────────────────┴────────────────────┴─────────────────────────────┘
SELECT d, dynamicType(d), dynamicElement(d, 'String'), dynamicElement(d, 'Int64'), dynamicElement(d, 'Array(Int64)'), dynamicElement(d, 'Date'), dynamicElement(d, 'Array(String)') FROM test;```
┌─d─────────────┬─dynamicType(d)─┬─dynamicElement(d, 'String')─┬─dynamicElement(d, 'Int64')─┬─dynamicElement(d, 'Array(Int64)')─┬─dynamicElement(d, 'Date')─┬─dynamicElement(d, 'Array(String)')─┐
│ ᴺᵁᴸᴸ │ None │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [] │ ᴺᵁᴸᴸ │ [] │
│ 42 │ Int64 │ ᴺᵁᴸᴸ │ 42 │ [] │ ᴺᵁᴸᴸ │ [] │
│ Hello, World! │ String │ Hello, World! │ ᴺᵁᴸᴸ │ [] │ ᴺᵁᴸᴸ │ [] │
│ [1,2,3] │ Array(Int64) │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [1,2,3] │ ᴺᵁᴸᴸ │ [] │
└───────────────┴────────────────┴─────────────────────────────┴────────────────────────────┴───────────────────────────────────┴───────────────────────────┴────────────────────────────────────┘
Чтобы узнать, какой вариант хранится в каждой строке, можно использовать функцию dynamicType(dynamic_column). Она возвращает значение типа String с именем типа значения для каждой строки (или 'None', если строка — NULL).
Пример:
CREATE TABLE test (d Dynamic) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('Hello, World!'), ([1, 2, 3]);
SELECT dynamicType(d) FROM test;
┌─dynamicType(d)─┐
│ None │
│ Int64 │
│ String │
│ Array(Int64) │
└────────────────┘
Преобразования между столбцом типа Dynamic и другими столбцами
Для столбца Dynamic возможны 4 преобразования.
Преобразование обычного столбца в столбец типа Dynamic
SELECT 'Hello, World!'::Dynamic AS d, dynamicType(d);
┌─d─────────────┬─dynamicType(d)─┐
│ Hello, World! │ String │
└───────────────┴────────────────┘
Преобразование столбца String в столбец типа Dynamic с помощью разбора
Чтобы разобрать значения типа Dynamic из столбца String, можно включить настройку cast_string_to_dynamic_use_inference:
SET cast_string_to_dynamic_use_inference = 1;
SELECT CAST(materialize(map('key1', '42', 'key2', 'true', 'key3', '2020-01-01')), 'Map(String, Dynamic)') as map_of_dynamic, mapApply((k, v) -> (k, dynamicType(v)), map_of_dynamic) as map_of_dynamic_types;
┌─map_of_dynamic──────────────────────────────┬─map_of_dynamic_types─────────────────────────┐
│ {'key1':42,'key2':true,'key3':'2020-01-01'} │ {'key1':'Int64','key2':'Bool','key3':'Date'} │
└─────────────────────────────────────────────┴──────────────────────────────────────────────┘
Преобразование столбца типа Dynamic в обычный столбец
Столбец Dynamic можно преобразовать в обычный столбец. В этом случае все вложенные типы будут преобразованы в целевой тип:
CREATE TABLE test (d Dynamic) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('42.42'), (true), ('e10');
SELECT d::Nullable(Float64) FROM test;
┌─CAST(d, 'Nullable(Float64)')─┐
│ ᴺᵁᴸᴸ │
│ 42 │
│ 42.42 │
│ 1 │
│ 0 │
└──────────────────────────────┘
Преобразование столбца типа Variant в столбец типа Dynamic
CREATE TABLE test (v Variant(UInt64, String, Array(UInt64))) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), ('String'), ([1, 2, 3]);
SELECT v::Dynamic AS d, dynamicType(d) FROM test;
┌─d───────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ 42 │ UInt64 │
│ String │ String │
│ [1,2,3] │ Array(UInt64) │
└─────────┴────────────────┘
Преобразование столбца Dynamic(max_types=N) в столбец Dynamic(max_types=K)
Если K >= N, то при преобразовании данные не меняются:
CREATE TABLE test (d Dynamic(max_types=3)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), (43), ('42.42'), (true);
SELECT d::Dynamic(max_types=5) as d2, dynamicType(d2) FROM test;
┌─d─────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ 42 │ Int64 │
│ 43 │ Int64 │
│ 42.42 │ String │
│ true │ Bool │
└───────┴────────────────┘
Если K < N, то значения с самыми редкими типами будут вставлены в один специальный подстолбец, но по-прежнему останутся доступными:
CREATE TABLE test (d Dynamic(max_types=4)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), (43), ('42.42'), (true), ([1, 2, 3]);
SELECT d, dynamicType(d), d::Dynamic(max_types=2) as d2, dynamicType(d2), isDynamicElementInSharedData(d2) FROM test;
┌─d───────┬─dynamicType(d)─┬─d2──────┬─dynamicType(d2)─┬─isDynamicElementInSharedData(d2)─┐
│ ᴺᵁᴸᴸ │ None │ ᴺᵁᴸᴸ │ None │ false │
│ 42 │ Int64 │ 42 │ Int64 │ false │
│ 43 │ Int64 │ 43 │ Int64 │ false │
│ 42.42 │ String │ 42.42 │ String │ false │
│ true │ Bool │ true │ Bool │ true │
│ [1,2,3] │ Array(Int64) │ [1,2,3] │ Array(Int64) │ true │
└─────────┴────────────────┴─────────┴─────────────────┴──────────────────────────────────┘
Функция isDynamicElementInSharedData возвращает true для строк, которые хранятся в специальной общей структуре данных внутри Dynamic, и, как видно, результирующий столбец содержит только 2 типа, которые не хранятся в общей структуре данных.
Если K=0, все типы будут вставляться в один специальный подстолбец:
CREATE TABLE test (d Dynamic(max_types=4)) ENGINE = Memory;
INSERT INTO test VALUES (NULL), (42), (43), ('42.42'), (true), ([1, 2, 3]);
SELECT d, dynamicType(d), d::Dynamic(max_types=0) as d2, dynamicType(d2), isDynamicElementInSharedData(d2) FROM test;
┌─d───────┬─dynamicType(d)─┬─d2──────┬─dynamicType(d2)─┬─isDynamicElementInSharedData(d2)─┐
│ ᴺᵁᴸᴸ │ None │ ᴺᵁᴸᴸ │ None │ false │
│ 42 │ Int64 │ 42 │ Int64 │ true │
│ 43 │ Int64 │ 43 │ Int64 │ true │
│ 42.42 │ String │ 42.42 │ String │ true │
│ true │ Bool │ true │ Bool │ true │
│ [1,2,3] │ Array(Int64) │ [1,2,3] │ Array(Int64) │ true │
└─────────┴────────────────┴─────────┴─────────────────┴──────────────────────────────────┘
Чтение типа Dynamic из данных
Все текстовые форматы (TSV, CSV, CustomSeparated, Values, JSONEachRow и т. д.) поддерживают чтение типа Dynamic. При разборе данных ClickHouse пытается определить тип каждого значения и использовать его при вставке в столбец Dynamic.
Пример:
SELECT
d,
dynamicType(d),
dynamicElement(d, 'String') AS str,
dynamicElement(d, 'Int64') AS num,
dynamicElement(d, 'Float64') AS float,
dynamicElement(d, 'Date') AS date,
dynamicElement(d, 'Array(Int64)') AS arr
FROM format(JSONEachRow, 'd Dynamic', $$
{"d" : "Hello, World!"},
{"d" : 42},
{"d" : 42.42},
{"d" : "2020-01-01"},
{"d" : [1, 2, 3]}
$$)
┌─d─────────────┬─dynamicType(d)─┬─str───────────┬──num─┬─float─┬───────date─┬─arr─────┐
│ Hello, World! │ String │ Hello, World! │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [] │
│ 42 │ Int64 │ ᴺᵁᴸᴸ │ 42 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [] │
│ 42.42 │ Float64 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ 42.42 │ ᴺᵁᴸᴸ │ [] │
│ 2020-01-01 │ Date │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ 2020-01-01 │ [] │
│ [1,2,3] │ Array(Int64) │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ [1,2,3] │
└───────────────┴────────────────┴───────────────┴──────┴───────┴────────────┴─────────┘
Использование типа Dynamic в функциях
Большинство функций поддерживают аргументы типа Dynamic. В этом случае функция выполняется отдельно для каждого внутреннего типа данных, хранящегося в столбце Dynamic.
Если тип результата функции зависит от типов аргументов, то результат такой функции, вызванной с аргументами типа Dynamic, будет иметь тип Dynamic. Если тип результата функции не зависит от типов аргументов — результат будет иметь тип Nullable(T), где T — обычный тип результата этой функции.
Примеры:
CREATE TABLE test (d Dynamic) ENGINE=Memory;
INSERT INTO test VALUES (NULL), (1::Int8), (2::Int16), (3::Int32), (4::Int64);
SELECT d, dynamicType(d) FROM test;
┌─d────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ 1 │ Int8 │
│ 2 │ Int16 │
│ 3 │ Int32 │
│ 4 │ Int64 │
└──────┴────────────────┘
SELECT d, d + 1 AS res, toTypeName(res), dynamicType(res) FROM test;
┌─d────┬─res──┬─toTypeName(res)─┬─dynamicType(res)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Dynamic │ None │
│ 1 │ 2 │ Dynamic │ Int16 │
│ 2 │ 3 │ Dynamic │ Int32 │
│ 3 │ 4 │ Dynamic │ Int64 │
│ 4 │ 5 │ Dynamic │ Int64 │
└──────┴──────┴─────────────────┴──────────────────┘
SELECT d, d + d AS res, toTypeName(res), dynamicType(res) FROM test;
┌─d────┬─res──┬─toTypeName(res)─┬─dynamicType(res)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Dynamic │ None │
│ 1 │ 2 │ Dynamic │ Int16 │
│ 2 │ 4 │ Dynamic │ Int32 │
│ 3 │ 6 │ Dynamic │ Int64 │
│ 4 │ 8 │ Dynamic │ Int64 │
└──────┴──────┴─────────────────┴──────────────────┘
SELECT d, d < 3 AS res, toTypeName(res) FROM test;
┌─d────┬──res─┬─toTypeName(res)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Nullable(UInt8) │
│ 1 │ 1 │ Nullable(UInt8) │
│ 2 │ 1 │ Nullable(UInt8) │
│ 3 │ 0 │ Nullable(UInt8) │
│ 4 │ 0 │ Nullable(UInt8) │
└──────┴──────┴─────────────────┘
SELECT d, exp2(d) AS res, toTypeName(res) FROM test;
┌─d────┬──res─┬─toTypeName(res)───┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Nullable(Float64) │
│ 1 │ 2 │ Nullable(Float64) │
│ 2 │ 4 │ Nullable(Float64) │
│ 3 │ 8 │ Nullable(Float64) │
│ 4 │ 16 │ Nullable(Float64) │
└──────┴──────┴───────────────────┘
TRUNCATE TABLE test;
INSERT INTO test VALUES (NULL), ('str_1'), ('str_2');
SELECT d, dynamicType(d) FROM test;
┌─d─────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ str_1 │ String │
│ str_2 │ String │
└───────┴────────────────┘
SELECT d, upper(d) AS res, toTypeName(res) FROM test;
┌─d─────┬─res───┬─toTypeName(res)──┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Nullable(String) │
│ str_1 │ STR_1 │ Nullable(String) │
│ str_2 │ STR_2 │ Nullable(String) │
└───────┴───────┴──────────────────┘
SELECT d, extract(d, '([0-3])') AS res, toTypeName(res) FROM test;
┌─d─────┬─res──┬─toTypeName(res)──┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Nullable(String) │
│ str_1 │ 1 │ Nullable(String) │
│ str_2 │ 2 │ Nullable(String) │
└───────┴──────┴──────────────────┘
TRUNCATE TABLE test;
INSERT INTO test VALUES (NULL), ([1, 2]), ([3, 4]);
SELECT d, dynamicType(d) FROM test;
┌─d─────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ [1,2] │ Array(Int64) │
│ [3,4] │ Array(Int64) │
└───────┴────────────────┘
SELECT d, d[1] AS res, toTypeName(res), dynamicType(res) FROM test;
┌─d─────┬─res──┬─toTypeName(res)─┬─dynamicType(res)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Dynamic │ None │
│ [1,2] │ 1 │ Dynamic │ Int64 │
│ [3,4] │ 3 │ Dynamic │ Int64 │
└───────┴──────┴─────────────────┴──────────────────┘
Если функцию нельзя выполнить для какого-либо типа в столбце Dynamic, будет сгенерировано исключение:
INSERT INTO test VALUES (42), (43), ('str_1');
SELECT d, dynamicType(d) FROM test;
┌─d─────┬─dynamicType(d)─┐
│ 42 │ Int64 │
│ 43 │ Int64 │
│ str_1 │ String │
└───────┴────────────────┘
┌─d─────┬─dynamicType(d)─┐
│ ᴺᵁᴸᴸ │ None │
│ [1,2] │ Array(Int64) │
│ [3,4] │ Array(Int64) │
└───────┴────────────────┘
SELECT d, d + 1 AS res, toTypeName(res), dynamicType(d) FROM test;
Получено исключение:
Code: 43. DB::Exception: Illegal types Array(Int64) and UInt8 of arguments of function plus: while executing 'FUNCTION plus(__table1.d : 3, 1_UInt8 :: 1) -> plus(__table1.d, 1_UInt8) Dynamic : 0'. (ILLEGAL_TYPE_OF_ARGUMENT)
Можно отфильтровать ненужные типы:
SELECT d, d + 1 AS res, toTypeName(res), dynamicType(res) FROM test WHERE dynamicType(d) NOT IN ('String', 'Array(Int64)', 'None')
┌─d──┬─res─┬─toTypeName(res)─┬─dynamicType(res)─┐
│ 42 │ 43 │ Dynamic │ Int64 │
│ 43 │ 44 │ Dynamic │ Int64 │
└────┴─────┴─────────────────┴──────────────────┘
Или извлеките нужный тип в виде подстолбца:
SELECT d, d.Int64 + 1 AS res, toTypeName(res) FROM test;
┌─d─────┬──res─┬─toTypeName(res)─┐
│ 42 │ 43 │ Nullable(Int64) │
│ 43 │ 44 │ Nullable(Int64) │
│ str_1 │ ᴺᵁᴸᴸ │ Nullable(Int64) │
└───────┴──────┴─────────────────┘
┌─d─────┬──res─┬─toTypeName(res)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ Nullable(Int64) │
│ [1,2] │ ᴺᵁᴸᴸ │ Nullable(Int64) │
│ [3,4] │ ᴺᵁᴸᴸ │ Nullable(Int64) │
└───────┴──────┴─────────────────┘
Поведение при несовпадении типов
Параметр dynamic_throw_on_type_mismatch определяет, что происходит, когда функция применяется к столбцу Dynamic, а фактический тип данных в строке несовместим с этой функцией:
true (по умолчанию) — сгенерировать исключение (ILLEGAL_TYPE_OF_ARGUMENT) при первой несовместимой строке.
false — возвращать NULL для несовместимых строк, сохраняя результат для совместимых.
Пример:
CREATE TABLE test (d Dynamic) ENGINE = Memory;
INSERT INTO test VALUES ('world'), (123), (456);
-- По умолчанию (исключение при несоответствии типов): length() не принимает целые числа, поэтому запрос завершается ошибкой.
SELECT length(d) FROM test; -- выбрасывает ILLEGAL_TYPE_OF_ARGUMENT
-- При отключённом выбрасывании исключений: несовместимые строки возвращают NULL.
SET dynamic_throw_on_type_mismatch = false;
SELECT d, length(d) FROM test ORDER BY d::String NULLS LAST;
┌─d─────┬─length(d)─┐
│ world │ 5 │
│ 123 │ ᴺᵁᴸᴸ │
│ 456 │ ᴺᵁᴸᴸ │
└───────┴───────────┘
Использование типа Dynamic в ORDER BY и GROUP BY
При ORDER BY и GROUP BY значения типа Dynamic сравниваются аналогично значениям типа Variant:
Результат оператора < для значений d1 с внутренним типом T1 и d2 с внутренним типом T2 типа Dynamic определяется следующим образом:
- Если
T1 = T2 = T, результатом будет d1.T < d2.T (будут сравниваться внутренние значения).
- Если
T1 != T2, результатом будет T1 < T2 (будут сравниваться имена типов).
По умолчанию тип Dynamic нельзя использовать в ключах GROUP BY/ORDER BY; если вы хотите его использовать, учитывайте его особое правило сравнения и включите настройки allow_suspicious_types_in_group_by/allow_suspicious_types_in_order_by.
Примеры:
CREATE TABLE test (d Dynamic) ENGINE=Memory;
INSERT INTO test VALUES (42), (43), ('abc'), ('abd'), ([1, 2, 3]), ([]), (NULL);
SELECT d, dynamicType(d) FROM test;
┌─d───────┬─dynamicType(d)─┐
│ 42 │ Int64 │
│ 43 │ Int64 │
│ abc │ String │
│ abd │ String │
│ [1,2,3] │ Array(Int64) │
│ [] │ Array(Int64) │
│ ᴺᵁᴸᴸ │ None │
└─────────┴────────────────┘
SELECT d, dynamicType(d) FROM test ORDER BY d SETTINGS allow_suspicious_types_in_order_by=1;
┌─d───────┬─dynamicType(d)─┐
│ [] │ Array(Int64) │
│ [1,2,3] │ Array(Int64) │
│ 42 │ Int64 │
│ 43 │ Int64 │
│ abc │ String │
│ abd │ String │
│ ᴺᵁᴸᴸ │ None │
└─────────┴────────────────┘
Примечание: значения типа Dynamic с разными числовыми типами считаются разными и не сравниваются между собой; вместо этого сравниваются имена их типов.
Пример:
CREATE TABLE test (d Dynamic) ENGINE=Memory;
INSERT INTO test VALUES (1::UInt32), (1::Int64), (100::UInt32), (100::Int64);
SELECT d, dynamicType(d) FROM test ORDER BY d SETTINGS allow_suspicious_types_in_order_by=1;
┌─v───┬─dynamicType(v)─┐
│ 1 │ Int64 │
│ 100 │ Int64 │
│ 1 │ UInt32 │
│ 100 │ UInt32 │
└─────┴────────────────┘
SELECT d, dynamicType(d) FROM test GROUP BY d SETTINGS allow_suspicious_types_in_group_by=1;
┌─d───┬─dynamicType(d)─┐
│ 1 │ Int64 │
│ 100 │ UInt32 │
│ 1 │ UInt32 │
│ 100 │ Int64 │
└─────┴────────────────┘
Примечание: описанное правило сравнения не применяется при выполнении функций сравнения, таких как </>/= и других, из-за особенностей работы функций с типом Dynamic
Достижение предела количества различных типов данных, хранимых в Dynamic
Тип данных Dynamic может хранить лишь ограниченное число различных типов данных в виде отдельных подстолбцов. По умолчанию этот предел равен 32, но его можно изменить в объявлении типа, используя синтаксис Dynamic(max_types=N), где N — число от 0 до 254 (из-за подробностей реализации невозможно иметь более 254 различных типов данных, которые могут храниться в Dynamic как отдельные подстолбцы).
Когда этот предел достигнут, все новые типы данных, вставляемые в столбец Dynamic, будут помещаться в единую общую структуру данных, которая хранит значения разных типов данных в двоичной форме.
Посмотрим, что происходит при достижении этого предела в разных сценариях.
Достижение лимита при разборе данных
При разборе значений Dynamic из данных, когда для текущего блока данных достигается лимит, все новые значения будут помещаться в общую структуру данных:
SELECT d, dynamicType(d), isDynamicElementInSharedData(d) FROM format(JSONEachRow, 'd Dynamic(max_types=3)', '
{"d" : 42}
{"d" : [1, 2, 3]}
{"d" : "Hello, World!"}
{"d" : "2020-01-01"}
{"d" : ["str1", "str2", "str3"]}
{"d" : {"a" : 1, "b" : [1, 2, 3]}}
')
┌─d──────────────────────┬─dynamicType(d)─────────────────┬─isDynamicElementInSharedData(d)─┐
│ 42 │ Int64 │ false │
│ [1,2,3] │ Array(Int64) │ false │
│ Hello, World! │ String │ false │
│ 2020-01-01 │ Date │ true │
│ ['str1','str2','str3'] │ Array(String) │ true │
│ (1,[1,2,3]) │ Tuple(a Int64, b Array(Int64)) │ true │
└────────────────────────┴────────────────────────────────┴─────────────────────────────────┘
Как мы видим, после вставки 3 различных типов данных — Int64, Array(Int64) и String — все новые типы были помещены в специальную общую структуру данных.
Во время слияния частей данных в движках таблиц MergeTree
При слиянии нескольких частей данных в таблице семейства MergeTree столбец Dynamic в результирующей части данных может достичь ограничения на число различных типов данных, которые могут храниться в отдельных подстолбцах, и тогда не сможет хранить все типы из исходных частей в виде подстолбцов.
В этом случае ClickHouse выбирает, какие типы останутся в виде отдельных подстолбцов после слияния, а какие будут помещены в общую структуру данных. В большинстве случаев ClickHouse старается сохранить наиболее часто встречающиеся типы, а самые редкие хранить в общей структуре данных, но это зависит от реализации.
Рассмотрим пример такого слияния. Сначала создадим таблицу со столбцом Dynamic, зададим ограничение на число различных типов данных равным 3 и вставим значения 5 разных типов:
CREATE TABLE test (id UInt64, d Dynamic(max_types=3)) ENGINE=MergeTree ORDER BY id;
SYSTEM STOP MERGES test;
INSERT INTO test SELECT number, number FROM numbers(5);
INSERT INTO test SELECT number, range(number) FROM numbers(4);
INSERT INTO test SELECT number, toDate(number) FROM numbers(3);
INSERT INTO test SELECT number, map(number, number) FROM numbers(2);
INSERT INTO test SELECT number, 'str_' || toString(number) FROM numbers(1);
Каждая вставка создаст отдельную часть данных со столбцом Dynamic, содержащим значения только одного типа:
SELECT count(), dynamicType(d), isDynamicElementInSharedData(d), _part FROM test GROUP BY _part, dynamicType(d), isDynamicElementInSharedData(d) ORDER BY _part, count();
┌─count()─┬─dynamicType(d)──────┬─isDynamicElementInSharedData(d)─┬─_part─────┐
│ 5 │ UInt64 │ false │ all_1_1_0 │
│ 4 │ Array(UInt64) │ false │ all_2_2_0 │
│ 3 │ Date │ false │ all_3_3_0 │
│ 2 │ Map(UInt64, UInt64) │ false │ all_4_4_0 │
│ 1 │ String │ false │ all_5_5_0 │
└─────────┴─────────────────────┴─────────────────────────────────┴───────────┘
Теперь давайте объединим все части в одну часть и посмотрим, что произойдёт:
SYSTEM START MERGES test;
OPTIMIZE TABLE test FINAL;
SELECT count(), dynamicType(d), isDynamicElementInSharedData(d), _part FROM test GROUP BY _part, dynamicType(d), isDynamicElementInSharedData(d) ORDER BY _part, count() desc;
┌─count()─┬─dynamicType(d)──────┬─isDynamicElementInSharedData(d)─┬─_part─────┐
│ 5 │ UInt64 │ false │ all_1_5_2 │
│ 4 │ Array(UInt64) │ false │ all_1_5_2 │
│ 3 │ Date │ false │ all_1_5_2 │
│ 2 │ Map(UInt64, UInt64) │ true │ all_1_5_2 │
│ 1 │ String │ true │ all_1_5_2 │
└─────────┴─────────────────────┴─────────────────────────────────┴───────────┘
Как мы видим, ClickHouse сохранил наиболее часто встречающиеся типы UInt64 и Array(UInt64) в виде подстолбцов, а все остальные типы поместил в общие данные.
Все функции JSONExtract* поддерживают тип Dynamic:
SELECT JSONExtract('{"a" : [1, 2, 3]}', 'a', 'Dynamic') AS dynamic, dynamicType(dynamic) AS dynamic_type;
┌─dynamic─┬─dynamic_type───────────┐
│ [1,2,3] │ Array(Nullable(Int64)) │
└─────────┴────────────────────────┘
SELECT JSONExtract('{"obj" : {"a" : 42, "b" : "Hello", "c" : [1,2,3]}}', 'obj', 'Map(String, Dynamic)') AS map_of_dynamics, mapApply((k, v) -> (k, dynamicType(v)), map_of_dynamics) AS map_of_dynamic_types
┌─map_of_dynamics──────────────────┬─map_of_dynamic_types────────────────────────────────────┐
│ {'a':42,'b':'Hello','c':[1,2,3]} │ {'a':'Int64','b':'String','c':'Array(Nullable(Int64))'} │
└──────────────────────────────────┴─────────────────────────────────────────────────────────┘
SELECT JSONExtractKeysAndValues('{"a" : 42, "b" : "Hello", "c" : [1,2,3]}', 'Dynamic') AS dynamics, arrayMap(x -> (x.1, dynamicType(x.2)), dynamics) AS dynamic_types```
┌─dynamics───────────────────────────────┬─dynamic_types─────────────────────────────────────────────────┐
│ [('a',42),('b','Hello'),('c',[1,2,3])] │ [('a','Int64'),('b','String'),('c','Array(Nullable(Int64))')] │
└────────────────────────────────────────┴───────────────────────────────────────────────────────────────┘
В формате RowBinary значения типа Dynamic сериализуются следующим образом:
<binary_encoded_data_type><value_in_binary_format_according_to_the_data_type>
Последнее изменение 10 июня 2026 г.