VARIANT
Обзор
Тип VARIANT хранит полуструктурированные данные JSON: объекты, массивы, строки, числа, логические значения и null. При записи Doris определяет тип каждого пути JSON и выполняет Subcolumnization для часто встречающихся путей, сохраняя их как отдельные подстолбцы в колоночном формате. Запрос к такому пути читает только нужный подстолбец.
VARIANT хорошо подходит для случаев, когда структура документов со временем меняется, но запросы по-прежнему обращаются к небольшому набору часто используемых путей.
- Для часто используемых путей выполняется Subcolumnization, что позволяет использовать преимущества колоночного хранения, пропуска файлов и векторизованного выполнения.
- Для ключевых путей доступны индексы на уровне пути, полнотекстовый поиск и пропуск данных с помощью разреженного индекса Doris.
- Оптимизации для широких столбцов позволяют эффективно применять автоматическую Subcolumnization при количестве подстолбцов порядка 10k. Когда число путей, участвующих в Subcolumnization, приближается к 10,000, требования к оборудованию быстро растут, поэтому обычно разумнее начинать с режима DOC.
Если вы ещё выбираете между поведением по умолчанию, разреженными столбцами, режимом DOC и Schema Template, начните с руководства по выбору режима Variant для рабочей нагрузки. Эта страница служит справочником по записи и разбору данных, правилам типов, CAST, семантике NULL и сравнений, ALTER, индексам, ограничениям и настройке.
Эта страница описывает VARIANT в Doris 5.0.0 и более поздних версиях. Отличия от Doris 4.x, которые с наибольшей вероятностью повлияют на существующий SQL:
INSERTсохраняет строку как строковое значение VARIANT, а не разбирает её как JSON. Это относится и кINSERT INTO ... SELECTиз табличных функций, таких какs3()иhdfs(). Для записи JSON-текста используйтеPARSE_TO_VARIANTвINSERT. Задания загрузки, например Stream Load, по-прежнему разбирают JSON.- Для значений VARIANT целиком поддерживаются
GROUP BY,DISTINCTи операции над множествами. Два значения VARIANT можно сравнивать с помощью=,!=и<=>, а также использовать как ключи соединения, ключиORDER BYи ключи оконных операций. VARIANT_TYPEвозвращает одно имя типа, напримерobject, вместо отображения путей в типы.- К элементам массивов VARIANT можно обращаться по целочисленным индексам, начиная с 1.
Для Doris 4.x см. версию этой страницы для 4.x.
Быстрый старт
CREATE TABLE events (
id BIGINT,
v VARIANT
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES ("replication_num" = "1");
-- INSERT keeps a string literal as a VARIANT string, so parse JSON text explicitly.
INSERT INTO events VALUES
(1, PARSE_TO_VARIANT('{"user": {"id": 42, "name": "alice"}, "tags": ["doris", "sql"], "score": 9.5}')),
(2, PARSE_TO_VARIANT('{"user": {"id": 7, "name": "bob"}, "score": 3}'));
SELECT id,
CAST(v['user']['name'] AS STRING) AS name,
v['tags'][1] AS first_tag
FROM events
WHERE CAST(v['score'] AS DOUBLE) > 5;
+------+-------+-----------+
| id | name | first_tag |
+------+-------+-----------+
| 1 | alice | doris |
+------+-------+-----------+
v['user']['name']иv['tags'][1]возвращают значенияVARIANT. Индексы массивов начинаются с 1.- Прежде чем сравнивать значение пути или использовать его в вычислениях, приведите его к конкретному типу с помощью CAST.
- Задания загрузки, например Stream Load, автоматически разбирают JSON-текст. См. Запись данных.
Определение столбца VARIANT
column_name VARIANT
column_name VARIANT< field_definition [, field_definition ...] >
column_name VARIANT< properties('key' = 'value' [, ...]) >
column_name VARIANT< field_definition [, ...], properties('key' = 'value' [, ...]) >
field_definition:
[MATCH_NAME | MATCH_NAME_GLOB] 'path_or_pattern' : data_type [COMMENT 'comment']
- Список
field_definition— это Schema Template. Он фиксирует тип хранения выбранных путей. properties(...)задаёт свойства хранения на уровне столбца. См. Свойства столбца.- Столбец VARIANT может быть
NULLилиNOT NULL. Единственное допустимое значение по умолчанию —NULL. - Имя столбца VARIANT верхнего уровня не может содержать точку (
.), даже если оно заключено в обратные кавычки: например,`v.x` VARIANTнедопустимо. Это ограничение применяется кCREATE TABLEиALTER TABLE; оно не ограничивает имена полей JSON внутри значения VARIANT.
CREATE TABLE IF NOT EXISTS example_tbl (
k BIGINT,
v VARIANT<
'id' : INT, -- path id is stored as INT
'message*' : STRING, -- paths matching message* are stored as STRING
'tags*' : ARRAY<TEXT>, -- paths matching tags* are stored as ARRAY<TEXT>
properties('variant_max_subcolumns_count' = '2048')
> NULL
)
DUPLICATE KEY(k)
DISTRIBUTED BY HASH(k) BUCKETS 1
PROPERTIES ("replication_num" = "1");
Где можно использовать столбец VARIANT в таблице:
| Использование | Поддерживается | Примечания |
|---|---|---|
| Столбец значений в таблицах Duplicate Key, Unique Key и Aggregate Key | Да | В таблице Aggregate Key тип агрегации должен быть REPLACE или REPLACE_IF_NOT_NULL. |
| Ключевой столбец, столбец партиционирования, столбец распределения по бакетам | Нет | |
Вложение в другой тип в схеме таблицы (ARRAY<VARIANT>, MAP, STRUCT) | Нет | Результат запроса при этом может иметь тип ARRAY<VARIANT>, например при использовании COLLECT_LIST(v). |
| Значение по умолчанию | Только NULL | DEFAULT '{}' и другие значения по умолчанию, отличные от NULL, отклоняются. |
Запись данных
INSERT и CAST сохраняют строку как строковое значение VARIANT. PARSE_TO_VARIANT, TRY_PARSE_TO_VARIANT и задания загрузки, например Stream Load, разбирают JSON-текст. Примеры загрузки см. в разделе Загрузка данных VARIANT.
Ошибки разбора
PARSE_TO_VARIANT, TRY_PARSE_TO_VARIANT и задания загрузки используют один и тот же парсер JSON. Задания загрузки обрабатывают ошибки так же, как TRY_PARSE_TO_VARIANT:
| Входные данные | PARSE_TO_VARIANT | TRY_PARSE_TO_VARIANT и задания загрузки |
|---|---|---|
| Корректный JSON | Результат разбора | Результат разбора |
Текст, который не является корректным JSON, например hello или {"id": | Сохраняется как строковое значение VARIANT | Сохраняется как строковое значение VARIANT |
JSON, содержащий целое число вне диапазона [-2^63, 2^64 - 1] или число вне диапазона DOUBLE, например {"a": 1, "n": 100000000000000000000} | Весь текст сохраняется как строковое значение VARIANT, поэтому v['a'] возвращает NULL | Весь текст сохраняется как строковое значение VARIANT |
| Пустая строка | Пустой объект {} | Пустой объект {} |
| Вложенность глубже 128 уровней | Ошибка | SQL NULL |
Ключ объекта длиннее variant_max_json_key_length байт (настройка BE, по умолчанию 255) | Ошибка | SQL NULL |
| Повторяющиеся ключи в одном объекте | Ошибка | SQL NULL |
| Строка с некорректной кодировкой UTF-8 | Ошибка | SQL NULL |
Документ с настолько глубокой вложенностью, что его отклоняет сам парсер JSON (около 1,000 уровней), считается некорректным JSON и поэтому сохраняется как строка.
Эти правила изменяются двумя настройками BE:
variant_throw_exeception_on_invalid_json(по умолчаниюfalse): при значенииtrueтекст, отклонённый парсером, в том числе JSON с числами вне допустимого диапазона, вызывает ошибку вPARSE_TO_VARIANTи возвращает SQLNULLвTRY_PARSE_TO_VARIANTи заданиях загрузки, а не сохраняется как строковое значение VARIANT.variant_enable_duplicate_json_path_check(по умолчаниюfalse): при значенииtrueдля повторяющегося ключа объекта сохраняется первое значение вместо возникновения ошибки. Эта настройка также предотвращает сбой записи, когда ключ с точкой повторяет вложенный путь; сохраняется только одно из значений.
Чтобы сохранить структуру документа с целыми числами вне допустимого диапазона, преобразуйте его через JSON: CAST(CAST(text AS JSON) AS VARIANT). Целые числа длиной до 38 цифр остаются точными числами, более длинные целые числа в пределах диапазона LARGEINT становятся строками, а ещё большие — DOUBLE. Использование PARSE_TO_VARIANT(CAST(text AS JSON)) не помогает, поскольку JSON-текст разбирается повторно.
SELECT VARIANT_TYPE(PARSE_TO_VARIANT('{"id": 1}')) AS valid_json, -- object
VARIANT_TYPE(PARSE_TO_VARIANT('{"id":')) AS invalid_json, -- string
VARIANT_TYPE(CAST('{"id": 1}' AS VARIANT)) AS string_cast; -- string
Что сохраняется при записи
При записи значения в таблицу оно нормализуется. Прочитанное значение может отличаться от значения, вычисленного до записи:
| До записи | При чтении из таблицы |
|---|---|
Поле объекта со значением JSON null, например {"a": null, "b": 1} | Поле удаляется: {"b":1}. v['a'] возвращает SQL NULL. |
Поле объекта, значение которого — пустой объект или массив либо объект, ставший пустым после применения этих правил, например {"a": {}, "b": [], "c": {"d": null}} | Удаляется: {} |
Поле объекта, массив которого содержит только null, например {"p": [null, null], "q": 1} | Может быть удалено: {"q":1} |
Значения внутри массивов, например {"arr": [{"a": null}, {}, [], null]} | Сохраняются без изменений. v['arr'][1]['a'] — это значение VARIANT null. |
Корневое значение JSON null, например PARSE_TO_VARIANT('null') | Пустой объект {} |
Корневой пустой массив [] или пустой объект {} | Сохраняется |
Значения DATE и DATETIME в корне, например CAST(date_col AS VARIANT), или в пути, который также содержит значения других типов | Сохраняются в текстовом виде, поэтому при чтении возвращаются как строки. В пути объекта, который содержит только даты, они сохраняют свой тип. |
| Логические значения и числа в одном пути | Логические значения могут возвращаться как 1 или 0 в зависимости от порядка значений в рамках одной записи и от compaction |
Ключ, содержащий точку, например {"a.b": 1} | Сохраняется как вложенный путь: {"a":{"b":1}}. И v['a.b'], и v['a']['b'] возвращают 1. Документ, содержащий одновременно ключ a.b и ключ b внутри a, приводит к сбою всего INSERT или задания загрузки, если variant_enable_duplicate_json_path_check не имеет значения true. |
| Ключи объектов | Возвращаются в порядке байтов |
Эти правила зависят от других строк, записываемых вместе, и от режима DOC, поэтому не следует рассчитывать на то, что значения null или пустые контейнеры сохранятся при записи. Пути, объявленные в Schema Template, вместо этого преобразуются к объявленному типу; см. Schema Template.
Определение типов и конфликты типов
Без Schema Template Doris определяет тип каждого значения при разборе JSON. VARIANT_TYPE возвращает этот тип:
| Значение JSON | Тип |
|---|---|
Целое число в пределах диапазона BIGINT | tinyint, smallint, int или bigint — наименьший подходящий тип |
Целое число выше диапазона BIGINT, но не больше 18446744073709551615 | decimal |
| Число с дробной частью или экспонентой | double |
Целое число вне диапазона [-2^63, 2^64 - 1] или число вне диапазона DOUBLE | Не считается числом: весь текст считается некорректным JSON (см. Ошибки разбора) |
| Строка | string |
true, false | bool |
null | null |
| Массив, объект | array, object |
При хранении данных каждому пути назначается один тип хранения:
- Целые числа хранятся как
BIGINTили какLARGEINT, если этого требует значение. Числа с плавающей точкой хранятся какDOUBLE, десятичные числа — какDECIMAL, строки — какSTRING, логические значения — какBOOLEAN, а массивы скалярных значений — какARRAY<T>. - Массивы объектов, вложенные массивы и пути со значениями разных типов, например целыми числами и числами с плавающей точкой, числами и строками или скалярными значениями и массивами, хранятся как
JSONB(отображается какjsonв выводеDESC). Массивы с конфликтующими типами элементов становятсяARRAY<JSONB>.
{"a" : 12345678}
{"a" : "HelloWorld"}
-- a is stored as JSONB
Путь с типом JSONB сохраняет все значения, но теряет типизированное хранение: индексы и пропуск данных с учётом типа больше не применяются к этому пути. Логические значения — особый случай: если первое значение пути логическое, а последующие значения — числа, путь может храниться как число, а логические значения будут возвращаться как 1 или 0. Если пути нужен стабильный тип, объявите его в Schema Template.
VARIANT_TYPE сообщает тип каждого значения, поэтому целое число, прочитанное из пути с типом BIGINT, всё ещё может иметь тип tinyint. Чтобы увидеть тип хранения каждого пути, выполните SET describe_extend_variant_column = true;, а затем DESC table_name;. См. Просмотр подстолбцов и типов.
Доступ к путям и вывод
v['key']иv['a']['b']читают поля объекта.v['arr'][1]читает элемент массива: индексы начинаются с 1, а-1обозначает последний элемент. ELEMENT_AT работает аналогично.- Результат имеет тип
VARIANT. Отсутствующий ключ, индекс0, индекс вне допустимого диапазона, строковый ключ для массива, целочисленный индекс для объекта и ключ для скалярного значения возвращают SQLNULL. - В вычисленном значении ключ, содержащий точку, считается единым ключом:
v['a.b']читает ключa.b, аv['a']['b']— ключbвнутриa. При хранении это различие не сохраняется; см. Что сохраняется при записи. - Путь не применяется автоматически к элементам массива. Для
{"a": [{"b": 1}]}выражениеv['a']['b']возвращаетNULL; используйтеv['a'][1]['b'].
SELECT v['user']['id'] AS id, -- 42
v['tags'][-1] AS last_tag, -- sql
v['user']['missing'] AS missing -- NULL
FROM events
WHERE id = 1;
В типичных сценариях путь приводят с помощью CAST к типу, необходимому запросу:
SELECT * FROM tbl WHERE ARRAY_CONTAINS(CAST(v['tags'] AS ARRAY<TEXT>), 'Doris');
SELECT * FROM tbl WHERE CAST(v['date'] AS DATE) = '2021-01-02';
SELECT * FROM tbl WHERE v['bool']; -- implicit CAST to BOOLEAN
SELECT * FROM tbl WHERE v['str'] MATCH 'Doris'; -- uses an inverted index on the path
Чтение значения VARIANT целиком возвращает JSON-текст. Ключи объектов возвращаются в порядке байтов, без пробельных символов, поэтому текст не совпадает с исходным побайтово. Корневое строковое значение возвращается без кавычек.
CREATE TABLE variant_tbl (k INT, v VARIANT)
DUPLICATE KEY(k)
DISTRIBUTED BY HASH(k) BUCKETS 1
PROPERTIES ("replication_num" = "1");
INSERT INTO variant_tbl VALUES (1, PARSE_TO_VARIANT('{ "b": 2, "a": 1, "c": { "y": 20, "x": 10 } }'));
SELECT v FROM variant_tbl WHERE k = 1;
-- {"a":1,"b":2,"c":{"x":10,"y":20}}
CAST
CAST в VARIANT
| Исходный тип | Результат |
|---|---|
CHAR, VARCHAR, STRING | Строковое значение VARIANT. JSON-текст не разбирается. Строка должна иметь корректную кодировку UTF-8, иначе CAST завершается ошибкой. |
BOOLEAN | Логическое значение. |
TINYINT, SMALLINT, INT, BIGINT | Целое число. |
LARGEINT | Десятичное число. Значение, модуль которого превышает 10^38 - 1, становится строкой. |
FLOAT, DOUBLE | Число с плавающей точкой. |
DECIMALV2, DECIMAL(p, s) с p <= 38 | Десятичное число. |
DATE, DATETIME(p), TIMESTAMP_NS | Дата или временная метка без часового пояса. |
IPV4, IPV6 | Строка с текстовым представлением значения. |
JSON / JSONB | Та же структура. Значение, которое VARIANT не может хранить, например число DECIMAL256, или повторяющиеся ключи в объекте приводят к ошибке CAST. |
ARRAY<T> | Массив; каждый элемент преобразуется. T должен быть VARIANT или одним из типов в этой таблице. |
MAP, STRUCT, TIME, TIMESTAMPTZ, VARBINARY и другие типы | Не поддерживаются. Инструкция завершается ошибкой. |
Исходное значение также должно быть корректным для собственного типа; некорректное значение отклоняется, а не исправляется. CAST никогда не разбирает строку: CAST('{"id": 1}' AS VARIANT) — это строка {"id": 1}. Для разбора JSON-текста используйте PARSE_TO_VARIANT.
CAST из VARIANT
| Целевой тип | Результат |
|---|---|
BOOLEAN | Логические значения без изменений. Числа становятся true, если они не равны нулю. Строки преобразуются так же, как при CAST(string AS BOOLEAN). |
TINYINT, SMALLINT, INT, BIGINT, LARGEINT | Целые числа. Дробная часть десятичного числа или числа с плавающей точкой отбрасывается (1.5 становится 1). Логические значения становятся 1 или 0. Числовые строки, например "123", преобразуются. |
FLOAT, DOUBLE, DECIMAL(p, s) | Числа и числовые строки. DECIMAL округляет до масштаба s. Значение, слишком большое для FLOAT, становится Infinity. |
DATE, DATETIME(p), TIMESTAMP_NS | Значения даты и времени, строки в формате даты или времени и числа, например 20240102. |
TIMESTAMPTZ(p) | Значения даты и времени и строки в формате даты или времени. |
IPV4, IPV6 | Строки в формате IP-адреса. |
CHAR, VARCHAR, STRING | Корневое строковое значение возвращается как есть, без кавычек. Объекты и массивы возвращаются как JSON-текст. Другие скалярные значения форматируются так же, как соответствующий тип SQL: корневое логическое значение становится 1 или 0, значение DATETIME имеет шесть знаков дробной части секунд, а значение TIMESTAMP_NS — девять. Значение VARIANT null становится строкой null. |
JSON / JSONB | Та же структура. Значения без аналога в JSON, например даты и временные метки, становятся строками JSON; временная метка с часовым поясом форматируется в часовом поясе сеанса. |
ARRAY<T> | Массив, преобразуемый поэлементно; элемент, который невозможно преобразовать, становится NULL. Строка, содержащая массив JSON, например "[1, 2]", также преобразуется. Другие значения возвращают NULL. |
MAP, STRUCT, TIME и другие типы | Не поддерживаются. Инструкция завершается ошибкой. |
Значение, которое невозможно преобразовать к целевому типу, возвращает SQL NULL, в том числе при включённой настройке enable_strict_cast. Исключение — значение, для типа которого вообще не предусмотрено преобразование к целевому типу, например число в TIMESTAMPTZ, IPV4 или IPV6, либо дата или временная метка в BOOLEAN: в таком случае инструкция завершается ошибкой.
SELECT CAST(PARSE_TO_VARIANT('"123"') AS INT) AS from_string, -- 123
CAST(PARSE_TO_VARIANT('"abc"') AS INT) AS not_a_number, -- NULL
CAST(PARSE_TO_VARIANT('1.5') AS INT) AS truncated, -- 1
CAST(PARSE_TO_VARIANT('300') AS TINYINT) AS overflow, -- NULL
CAST(PARSE_TO_VARIANT('{"a": 1}') AS INT) AS from_object, -- NULL
CAST(PARSE_TO_VARIANT('[1, "2", null, "x"]') AS ARRAY<INT>) AS arr; -- [1, 2, null, null]
SELECT CAST(PARSE_TO_VARIANT('true') AS STRING) AS bool_root, -- 1
CAST(PARSE_TO_VARIANT('{"b": true}') AS STRING) AS object_text, -- {"b":true}
CAST(PARSE_TO_VARIANT('"abc"') AS STRING) AS string_root, -- abc
CAST(PARSE_TO_VARIANT('null') AS STRING) AS variant_null; -- null
Десятичные значения и значения даты и времени
При преобразовании этих типов в VARIANT:
| Тип Doris | Поведение |
|---|---|
Устаревший DECIMALV2 | Точность до 27 и масштаб до 9 сохраняются без потерь. |
DECIMAL(p, s) | При 1 <= p <= 38 и 0 <= s <= p значения сохраняются без потерь. Десятичные числа с точностью больше 38 не поддерживаются. |
DATE | Календарная дата без времени и часового пояса. |
DATETIME(p) | 0 <= p <= 6, без поправки на часовой пояс. |
TIMESTAMP_NS | Наносекундная точность без поправки на часовой пояс; значения должны находиться в диапазоне TIMESTAMP_NS. |
TIMESTAMPTZ(p) | Не поддерживается ни при CAST, ни в качестве типа пути Schema Template. |
TIME | Не поддерживается. |
Семантика NULL
У VARIANT есть два вида null:
- SQL
NULLозначает отсутствие значения. Он возникает из значения столбцаNULL, отсутствующего пути, напримерv['no_such_key'], или неудачного CAST. - VARIANT
null— это значение: литерал JSONnull, напримерPARSE_TO_VARIANT('null')или элемент массиваnull. Для негоVARIANT_TYPEвозвращаетnull.
| Операция | SQL NULL | VARIANT null |
|---|---|---|
v IS NULL | true | false |
COALESCE(v, x), IFNULL(v, x), где x имеет тип VARIANT | Возвращает x | Возвращает значение VARIANT null |
COUNT(v) | Не учитывается | Учитывается |
GROUP BY v, DISTINCT | Все значения SQL NULL образуют одну группу | Все значения VARIANT null образуют другую группу |
v = x, соединение по равенству | Никогда не совпадает | Совпадает с другим значением VARIANT null |
ORDER BY v | Размещается согласно NULLS FIRST или NULLS LAST | При сортировке по возрастанию располагается перед всеми остальными значениями, отличными от NULL |
CAST(v AS STRING) | SQL NULL | Строка null |
После сохранения значения поле объекта со значением null вне массивов удаляется, поэтому его чтение возвращает SQL NULL, а корневое значение null возвращается как {} (см. Что сохраняется при записи). Поэтому для сохранённых данных выражение v['a'] IS NULL не позволяет отличить отсутствующее поле от поля со значением null.
Сравнение, группировка и упорядочивание
Значения VARIANT сравниваются по логическому значению, а не по тексту или физическому представлению. Равенство, хеширование (GROUP BY, DISTINCT, соединения) и упорядочивание (ORDER BY, ключи оконных операций) используют одинаковые правила, поэтому равные значения всегда попадают в одну группу и имеют одинаковую позицию при сортировке.
Поддерживаемые операции
| Операция над значениями VARIANT | Поддержка | Примечания |
|---|---|---|
=, !=, <=> между двумя значениями VARIANT или с литералом NULL | Поддерживается | Включая вложенные пути, например v['a'] = w['a']. |
Соединение по равенству, semi join и anti join, подзапросы IN и NOT IN | Поддерживается | Для ключей соединения VARIANT не создаются runtime filters. |
GROUP BY, DISTINCT, COUNT(DISTINCT ...), UNION, INTERSECT, EXCEPT | Поддерживается | |
ORDER BY, ORDER BY ... LIMIT | Поддерживается | |
PARTITION BY и ORDER BY в оконных операциях | Поддерживается | |
COUNT(v), COLLECT_LIST(v), ARRAY_AGG(v) | Поддерживается | |
IF, CASE, IFNULL, COALESCE | Поддерживается | При смешивании VARIANT с другим типом значения VARIANT преобразуются к этому типу (DECIMAL(38, 9), если это целочисленный тип); значения, которые невозможно преобразовать, становятся NULL. |
CAST(v AS ARRAY<VARIANT>), EXPLODE_VARIANT_ARRAY, EXPLODE и EXPLODE_OUTER для ARRAY<VARIANT> | Поддерживается | |
<, <=, >, >=, BETWEEN между значениями VARIANT | Не поддерживается | Сначала приведите к конкретному типу с помощью CAST. |
Сравнение значения VARIANT целиком со значением другого типа, например v = 1 | Не поддерживается | Вложенный путь преобразуется неявно: v['a'] = 1 работает. |
Список IN, содержащий значения VARIANT, например v IN (PARSE_TO_VARIANT('1')) | Не поддерживается | v IN ('a', 'b') со значениями других типов работает благодаря неявному преобразованию. |
MIN, MAX | Не поддерживается | Сначала приведите вложенный путь с помощью CAST. |
ARRAY(...), MAP(...), NAMED_STRUCT(...) с аргументами VARIANT | Не поддерживается |
Равенство
- Числа. Целое число, десятичное число с нулевой дробной частью и число с плавающей точкой, имеющее целое значение, равны:
1,1.0и1.00— одно значение. Конечные нули десятичного числа не имеют значения, а-0.0равно0. Десятичное число и число с плавающей точкой, имеющие дробную часть, никогда не равны:DECIMAL 1.5не равноDOUBLE 1.5. Числа JSON с дробной частью разбираются какDOUBLE; десятичные числа появляются при CAST, в путях Schema Template и при разборе целых чисел, слишком больших дляBIGINT. - Значения разных видов никогда не равны. Число
1, строка"1"иtrue— три разных значения. ЗначениеDATEне равно строке"2024-01-01", а временная метка без часового пояса не равна временной метке с часовым поясом. - Строки равны только при совпадении их байтов. Сравнение учитывает регистр.
- Объекты равны, если у них одинаковые ключи с равными значениями. Порядок ключей не имеет значения.
- Массивы равны, если у них одинаковая длина и равные элементы в одинаковом порядке.
- Значение VARIANT
nullравно другому значению VARIANTnull. Для SQLNULLдействуют правила SQL.
SELECT PARSE_TO_VARIANT('1') = PARSE_TO_VARIANT('1.0') AS int_double, -- 1
PARSE_TO_VARIANT('1.5') = CAST(CAST(1.5 AS DECIMAL(10, 2)) AS VARIANT) AS dbl_dec, -- 0
PARSE_TO_VARIANT('1') = PARSE_TO_VARIANT('"1"') AS num_str, -- 0
PARSE_TO_VARIANT('{"a": 1, "b": 2}') = PARSE_TO_VARIANT('{"b": 2, "a": 1}') AS obj, -- 1
PARSE_TO_VARIANT('[1, 2]') = PARSE_TO_VARIANT('[2, 1]') AS arr; -- 0
Упорядочивание
ORDER BY для значений VARIANT использует единый полный порядок. Значения разных видов сначала упорядочиваются по виду:
null < boolean < number < string < binary < date < timestamp with time zone
< timestamp without time zone < time < UUID < object < array
Двоичные значения, значения времени и UUID нельзя получить разбором JSON или с помощью CAST в Doris; они могут появиться только в данных VARIANT, записанных другими системами, например в файлах Parquet со столбцами VARIANT.
Внутри одного вида:
- Логические значения:
falseпередtrue. - Числа: целые числа (
INT,BIGINTи т. д.), десятичные числа (DECIMAL) и числа с плавающей точкой (FLOAT,DOUBLE) сортируются по числовому значению.INT 1,FLOAT/DOUBLE 1.0иDECIMAL 1.00равны и занимают одинаковую позицию при сортировке; конечные нули десятичного числа не имеют значения. Для равных нецелых значений десятичное число идёт первым:DECIMAL 1.5предшествуетFLOAT/DOUBLE 1.5, и как значения VARIANT они не равны. Точность чисел с плавающей точкой может влиять на порядок:DOUBLE 0.1немного большеDECIMAL 0.1. Отрицательная бесконечность располагается первой; положительная бесконечность, а затем NaN — последними. - Строки: по байтам UTF-8. Прописные буквы идут перед строчными, а
"10"— перед"9". - Даты и временные метки: в хронологическом порядке. Любая дата идёт перед любой временной меткой.
- Объекты: поэлементно в порядке ключей: сначала сравниваются наименьшие ключи, затем их значения, затем следующие ключи. Объект, элементы которого являются префиксом другого объекта, идёт первым, поэтому
{"a":1}<{"a":1,"b":2}<{"b":0}. - Массивы: поэлементно; префикс идёт первым, поэтому
[]<[null]<[1]<[1,2].
SQL NULL размещается согласно NULLS FIRST или NULLS LAST.
SELECT v, VARIANT_TYPE(v) AS type
FROM (
SELECT PARSE_TO_VARIANT('[1, 2]') AS v UNION ALL
SELECT PARSE_TO_VARIANT('{"a": 1}') UNION ALL
SELECT PARSE_TO_VARIANT('"9"') UNION ALL
SELECT PARSE_TO_VARIANT('"10"') UNION ALL
SELECT PARSE_TO_VARIANT('10') UNION ALL
SELECT PARSE_TO_VARIANT('9.5') UNION ALL
SELECT PARSE_TO_VARIANT('true') UNION ALL
SELECT PARSE_TO_VARIANT('null') UNION ALL
SELECT NULL
) t
ORDER BY v;
+---------+---------+
| v | type |
+---------+---------+
| NULL | NULL |
| null | null |
| true | bool |
| 9.5 | double |
| 10 | tinyint |
| 10 | string |
| 9 | string |
| {"a":1} | object |
| [1,2] | array |
+---------+---------+
Сравнение в конкретном типе и сравнение VARIANT
Операнды определяют, используются ли при сравнении правила VARIANT или правила конкретного типа. Рассмотрим путь a со значениями 1, 1.0 и "1":
| Выражение | Тип при сравнении | Результат |
|---|---|---|
v['a'] = 1 | DECIMAL(38, 9) благодаря неявному CAST для v['a'] | Совпадают все три значения |
v['a'] = CAST(1 AS VARIANT), v['a'] = w['a'] | VARIANT | Совпадают 1 и 1.0, но не "1" |
GROUP BY v['a'] | VARIANT | 1 и 1.0 образуют одну группу, "1" — другую |
GROUP BY CAST(v['a'] AS STRING) | STRING | Одна группа, 1 |
ORDER BY v['a'] | VARIANT | Сначала числа, затем строки |
ORDER BY CAST(v['a'] AS INT) | INT | Числовой порядок; значения, которые невозможно преобразовать, становятся NULL |
Примечания
- Затраты. Ключи VARIANT хешируются и сравниваются по логическому значению. В тесте на 44 миллионах строк
GROUP BY, сортировка и соединения по ключу VARIANT занимали в 1.2–3.7 раза больше времени, чем те же операции надCAST(v['path'] AS <type>). Если у пути один известный тип, используйте CAST. - Результаты определяются сохранёнными значениями. Нормализации из раздела Что сохраняется при записи выполняются до сравнения: поле, имевшее значение
null, отсутствует, корневое значениеDATEсравнивается как строка, а логические значения, смешанные с числами в одном пути, могут возвращаться как1или0. Объявляйте пути, для которых важен тип, в Schema Template. - Смешанные типы сортируются по виду, а не по значению. Если путь содержит и числа, и числовые строки,
ORDER BY v['a']размещает все числа перед всеми строками, а строки упорядочивает по байтам. Для числового или лексикографического порядка приведите значения к одному типу с помощью CAST. - Одинаково отображаемые значения могут различаться. В результате
GROUP BYчисло1и строка"1"отображаются одинаково, но образуют две группы. Чтобы увидеть разницу, используйтеVARIANT_TYPE. - Порядок между видами определяется Doris, чтобы результаты были детерминированными. Он не является частью стандарта JSON и может отличаться от порядка в других системах.
- Внутренний хеш значения VARIANT — деталь реализации, а не стабильная контрольная сумма для пользователя.
Schema Template
Schema Template объявляет тип хранения выбранных путей. Объявляйте только ключевые пути, которым нужен стабильный тип или отдельный индекс; остальная часть документа остаётся динамической.
CREATE TABLE test_var_schema (
id BIGINT NOT NULL,
v1 VARIANT<
'large_int_val': LARGEINT,
'string_val': STRING,
'decimal_val': DECIMAL(38, 9),
'datetime_val': DATETIME,
'ip_val': IPV4
> NULL
)
PROPERTIES ("replication_num" = "1");
Поле шаблона может использовать следующие типы:
- Числа:
TINYINT,SMALLINT,INT,BIGINT,LARGEINT,FLOAT,DOUBLEиDECIMAL(p, s)сp <= 38 STRING(илиTEXT)BOOLEANDATE,DATETIME(p),TIMESTAMP_NSIPV4,IPV6ARRAY<T>, гдеT— один из перечисленных выше типов (только одно измерение)
Запись в пути шаблона
В объявленном пути каждое значение преобразуется к объявленному типу по нестрогим правилам CAST. Значение, которое невозможно преобразовать, сохраняется как NULL, поэтому при чтении путь отсутствует; остальная часть строки при этом записывается. Преобразование может изменить значение:
CREATE TABLE tpl_demo (
k INT,
v VARIANT<'id': INT, 'price': DECIMAL(10, 2), 'ts': DATETIME(3)>
)
DUPLICATE KEY(k)
DISTRIBUTED BY HASH(k) BUCKETS 1
PROPERTIES ("replication_num" = "1");
INSERT INTO tpl_demo VALUES
(1, PARSE_TO_VARIANT('{"id": "123", "price": 9.999, "ts": "2024-01-01 10:00:00.123456"}')),
(2, PARSE_TO_VARIANT('{"id": "abc", "price": "x", "ts": "not a time"}')),
(3, PARSE_TO_VARIANT('{"id": 1.7}'));
SELECT k, v FROM tpl_demo ORDER BY k;
+------+------------------------------------------------------------+
| k | v |
+------+------------------------------------------------------------+
| 1 | {"id":123,"price":10.00,"ts":"2024-01-01 10:00:00.123000"} |
| 2 | {} |
| 3 | {"id":1} |
+------+------------------------------------------------------------+
"123"становится123,1.7усекается до1,9.999округляется до10.00, а временная метка сохраняет объявленную точность (миллисекунды); её текстовое представление всегда содержит шесть знаков дробной части секунд."abc","x"и"not a time"невозможно преобразовать, поэтому они удаляются.- Число JSON с дробной частью перед преобразованием разбирается как
DOUBLE, поэтому путьDECIMALможет потерять точность. Записывайте такие значения как строки JSON, чтобы сохранить каждую цифру (см. Частые вопросы). - Значения дат и IP-адресов должны быть строками JSON:
{"date": 2020-01-01}и{"ip": 127.0.0.1}не являются корректным JSON; записывайте{"date": "2020-01-01"}и{"ip": "127.0.0.1"}.
Значение преобразуется только в том случае, если для его типа JSON предусмотрено преобразование к объявленному типу. Число JSON в пути IPV4 или IPV6 приводит к сбою всей записи. Если в рамках одной записи в пути DATE, DATETIME, IPV4 или IPV6 смешаны строки и числа JSON, даже корректные значения этого пути могут быть удалены. Записывайте значения таких путей как строки JSON.
Чтение путей шаблона
v['path'] сохраняет тип VARIANT даже для объявленного пути; Doris не приводит его к объявленному типу автоматически. Явно применяйте CAST, когда нужен объявленный тип, и приводите к нему значения в фильтрах, чтобы запрос мог использовать zone maps и индексы этого пути:
SELECT CAST(v['ts'] AS DATETIME(3)) AS ts, -- 2024-01-01 10:00:00.123
CAST(v['ts'] AS STRING) AS ts_text, -- 2024-01-01 10:00:00.123000
VARIANT_TYPE(v['price']) AS type -- decimal
FROM tpl_demo
WHERE CAST(v['id'] AS INT) = 123;
Schema Template определяет только способ хранения значений. Тот же JSON, вычисленный в запросе, сохраняет тип, полученный при разборе:
SELECT VARIANT_TYPE(PARSE_TO_VARIANT('{"price": 9.999}')['price']); -- double
Сопоставление с шаблонами
По умолчанию имя поля является glob-шаблоном (MATCH_NAME_GLOB): * соответствует любой последовательности символов, включая . между вложенными ключами, а ? — одному символу. Например, 'm*' соответствует и m1, и m2.x. Если путь соответствует нескольким полям, используется первое поле в порядке определения:
CREATE TABLE test_var_pattern (
id BIGINT NOT NULL,
v1 VARIANT<
'enumString*' : STRING,
'enum*' : ARRAY<TEXT>,
'ip*' : IPV6
> NULL
)
PROPERTIES ("replication_num" = "1");
-- enumString1 matches both enumString* and enum*; the first one (STRING) is used.
Для точного сопоставления с именем, содержащим символ *, используйте MATCH_NAME:
v1 VARIANT<
MATCH_NAME 'enumString*' : STRING
> NULL
По умолчанию для совпавших путей выполняется Subcolumnization. Если совпадает слишком много путей и создаётся слишком много подстолбцов, рассмотрите настройку variant_enable_typed_paths_to_sparse (см. Свойства столбца).
Schema Template нельзя изменить после создания столбца. См. ALTER TABLE.
ALTER TABLE
ADD COLUMN (включая пакетное добавление), MODIFY COLUMN и RENAME COLUMN также должны использовать имя столбца VARIANT верхнего уровня без точек. Например, следующая инструкция отклоняется:
ALTER TABLE example_tbl ADD COLUMN `v.x` VARIANT;
-- ERROR: Cannot use `VARIANT` type column named with a `.` character: v.x
ALTER TABLE поддерживает:
ADD COLUMNдля столбца VARIANT, допускающего NULL, с Schema Template и свойствами или без них.DROP COLUMN,RENAME COLUMNиMODIFY COLUMN ... COMMENT '...'.ADD INDEXиDROP INDEX. Индекс сfield_patternможно определить только вCREATE TABLE.- Для столбца без Schema Template: изменение
NOT NULLнаNULLи изменениеvariant_doc_materialization_min_rows.MODIFY COLUMNпринимает полное определение столбца; пропущенное свойство получает значение соответствующей переменной сеансаdefault_variant_*, поэтому повторно указывайте каждое свойство, значение которого отличается.
Не поддерживается:
ADD COLUMN ... VARIANT NOT NULL: столбцуNOT NULL, добавленному черезALTER, нужно значение по умолчанию, а VARIANT допускает толькоDEFAULT NULL. Определяйте столбцы VARIANT сNOT NULLвCREATE TABLE.- Добавление Schema Template к существующему столбцу. Для столбца со Schema Template через
MODIFY COLUMNможно изменить только комментарий; любое другое изменение завершается ошибкой, даже если оно повторяет тот же шаблон. - Изменение любого свойства, кроме
variant_doc_materialization_min_rows, изменениеNULLнаNOT NULLи преобразование между VARIANT и другими типами. BUILD INDEXдля столбца VARIANT.
Для таких изменений создайте таблицу с новым определением и скопируйте данные через INSERT INTO ... SELECT; исходный столбец STRING оберните в PARSE_TO_VARIANT.
ALTER TABLE t ADD COLUMN v2 VARIANT NOT NULL;
-- ERROR: Field 'v2' doesn't have a default value
-- v is a VARIANT column without a Schema Template.
ALTER TABLE t MODIFY COLUMN v VARIANT<'id': INT>;
-- ERROR: Can not change variant schema templates
Свойства столбца
Свойства столбца задаются в properties(...) внутри типа VARIANT:
CREATE TABLE example_table (
id INT,
data_variant VARIANT<
'path_1' : INT,
'path_2' : STRING,
properties(
'variant_max_subcolumns_count' = '2048',
'variant_enable_typed_paths_to_sparse' = 'true',
'variant_sparse_hash_shard_count' = '64'
)
>
);
| Свойство | По умолчанию | ALTER | Описание |
|---|---|---|---|
variant_max_subcolumns_count | 2048 | Нет | Максимальное число динамических путей, для которых выполняется Subcolumnization в одном файле данных; первыми выбираются пути с большим числом значений, отличных от null, а остальные пути сохраняются в разреженных столбцах. 0 означает отсутствие ограничения. Диапазон от 0 до 100000; рекомендуется не превышать 10000. Значения по умолчанию достаточно для большинства рабочих нагрузок; если нагрузке действительно требуется намного больше подстолбцов, предпочтителен режим DOC. |
variant_enable_typed_paths_to_sparse | false | Нет | По умолчанию для путей Schema Template всегда выполняется Subcolumnization, и они не учитываются в ограничении variant_max_subcolumns_count. При значении true они учитываются в ограничении и могут храниться в разреженных столбцах. |
variant_sparse_hash_shard_count | 1 | Нет | Число физических разреженных столбцов, между которыми разреженные пути распределяются по хешу. Диапазон от 0 до 1024; 0 трактуется как 1. |
variant_max_sparse_column_statistics_size | 10000 | Нет | Максимальное число разреженных путей в одном файле данных, для которых записывается статистика. При превышении этого значения запрос к пути без статистики не может пропустить разреженные столбцы. Диапазон от 1 до 50000. |
variant_enable_doc_mode | false | Нет | Включает режим DOC. Нельзя задать одновременно с variant_max_subcolumns_count, variant_enable_typed_paths_to_sparse, variant_max_sparse_column_statistics_size или variant_sparse_hash_shard_count. |
variant_doc_materialization_min_rows | 0 | Только без Schema Template | Только для режима DOC. При записи меньшего числа строк сохраняется только документ; Subcolumnization выполняется, когда compaction объединит файлы до достижения порога. 0 означает выполнение Subcolumnization при записи. Диапазон от 0 до 1000000000. |
variant_doc_hash_shard_count | 64 | Нет | Только для режима DOC. Число столбцов, на которые разбивается хранимый документ. Диапазон от 0 до 1024; 0 трактуется как 1. |
Столбец ALTER показывает, можно ли изменить свойство через ALTER TABLE ... MODIFY COLUMN; см. ALTER TABLE. О применении свойств разреженных столбцов и режима DOC см. в разделе Широкие столбцы.
Переменные сеанса
| Переменная | По умолчанию | Описание |
|---|---|---|
default_variant_max_subcolumns_count | 2048 | Значение variant_max_subcolumns_count для столбца VARIANT, в определении которого это свойство не указано. |
default_variant_enable_typed_paths_to_sparse | false | Значение variant_enable_typed_paths_to_sparse для столбца, в определении которого это свойство не указано. |
default_variant_sparse_hash_shard_count | 0 | Значение variant_sparse_hash_shard_count для столбца, в определении которого это свойство не указано; 0 трактуется как 1. |
default_variant_max_sparse_column_statistics_size | 10000 | Значение variant_max_sparse_column_statistics_size для столбца, в определении которого это свойство не указано. |
default_variant_enable_doc_mode | false | Значение variant_enable_doc_mode для столбца, в определении которого это свойство не указано. |
default_variant_doc_materialization_min_rows | 0 | Значение variant_doc_materialization_min_rows для столбца, в определении которого это свойство не указано. |
default_variant_doc_hash_shard_count | 64 | Значение variant_doc_hash_shard_count для столбца, в определении которого это свойство не указано. |
describe_extend_variant_column | false | При значении true вывод DESC также включает подстолбцы столбцов VARIANT. |
Переменные default_variant_* применяются при определении столбца через CREATE TABLE или ALTER TABLE. Их изменение не влияет на существующие столбцы, но влияет на последующий MODIFY COLUMN, в котором свойство не указано.
Настройки BE
| Настройка | По умолчанию | Описание |
|---|---|---|
variant_max_json_key_length | 255 | Максимальная длина ключа объекта JSON в байтах. Более длинный ключ вызывает ошибку разбора. Диапазон от 1 до 65535. |
variant_throw_exeception_on_invalid_json | false | При значении false текст, отклонённый парсером JSON, сохраняется как строковое значение VARIANT. При значении true возникает ошибка разбора. |
variant_enable_duplicate_json_path_check | false | При значении false повторяющийся ключ объекта вызывает ошибку разбора. При значении true сохраняется первое значение, а повторы игнорируются. |
Все три настройки можно изменять во время работы. О том, как сообщается ошибка разбора, см. в разделе Ошибки разбора.
Индексы Variant
Выбор индексов
VARIANT поддерживает BloomFilter и Inverted Index для вложенных путей.
- Для фильтров по равенству/IN с высокой кардинальностью предпочтителен BloomFilter (более разреженный индекс, более высокая производительность записи).
- Для поиска с токенизацией, по фразам или диапазонам используйте Inverted Index и задавайте подходящие свойства
parser/analyzer.
Фильтр может использовать индекс или BloomFilter только при сравнении пути в его типе хранения. Явно приводите числовые пути с помощью CAST; строковый путь при сравнении со строковым литералом не требует CAST.
...
PROPERTIES("replication_num" = "1", "bloom_filter_columns" = "v");
-- Use BloomFilter for equality/IN filters
SELECT * FROM tbl WHERE CAST(v['id'] AS BIGINT) = 12345678;
SELECT * FROM tbl WHERE CAST(v['id'] AS BIGINT) IN (1, 2, 3);
После создания инвертированного индекса для столбца VARIANT все вложенные пути наследуют одинаковые свойства индекса (например, парсер):
CREATE TABLE IF NOT EXISTS tbl (
k BIGINT,
v VARIANT,
INDEX idx_v(v) USING INVERTED PROPERTIES("parser" = "english")
);
-- All subpaths inherit the english parser
SELECT * FROM tbl WHERE v['id_1'] MATCH 'Doris';
SELECT * FROM tbl WHERE v['id_2'] MATCH 'Apache';
Индекс для вложенного пути
Можно задать свойства индекса для определённых вложенных путей VARIANT и даже настроить для одного пути инвертированные индексы как с токенизацией, так и без неё. Для индексов отдельных путей тип пути должен быть объявлен через Schema Template.
-- Common properties: field_pattern (target path), analyzer, parser, support_phrase, norms, etc.
CREATE TABLE IF NOT EXISTS tbl (
k BIGINT,
v VARIANT<'content' : STRING>,
INDEX idx_tokenized(v) USING INVERTED PROPERTIES("parser" = "english", "field_pattern" = "content"),
INDEX idx_v(v) USING INVERTED PROPERTIES("field_pattern" = "content")
);
-- v.content has both tokenized and non-tokenized inverted indexes
SELECT * FROM tbl WHERE v['content'] MATCH 'Doris';
SELECT * FROM tbl WHERE v['content'] = 'Doris';
Индексирование путей по шаблону:
CREATE TABLE IF NOT EXISTS tbl (
k BIGINT,
v VARIANT<'pattern_*' : STRING>,
INDEX idx_tokenized(v) USING INVERTED PROPERTIES("parser" = "english", "field_pattern" = "pattern_*"),
INDEX idx_v(v) USING INVERTED -- global non-tokenized inverted index
);
SELECT * FROM tbl WHERE v['pattern_1'] MATCH 'Doris';
SELECT * FROM tbl WHERE v['pattern_1'] = 'Doris';
Начиная с Doris 4.1.5, norms можно отключить с помощью свойства индекса или настройки BE, приведённых ниже.
Индексы с токенизацией для путей VARIANT по умолчанию записывают norms для BM25, что требует одного байта на строку для каждого пути, включая строки без значения этого пути. Для путей, используемых только при фильтрации, отключите norms с помощью "norms" = "false". Без field_pattern это свойство применяется ко всем вложенным путям, охваченным индексом:
CREATE TABLE IF NOT EXISTS tbl (
k BIGINT,
v VARIANT<'body_*' : STRING>,
INDEX idx_body(v) USING INVERTED PROPERTIES("parser" = "english", "field_pattern" = "body_*", "norms" = "false")
);
Отключение norms не влияет на фильтрацию MATCH, но score() возвращает ошибку при чтении сегмента без norms. Настройка BE inverted_index_skip_norms_for_variant отключает norms для всех путей VARIANT. Подробности см. в разделе Длина записи и свойство norms.
Когда индексы не работают
- Изменение типа приводит к потере индекса: если тип вложенного пути меняется на несовместимый (например, INT → JSONB), индекс теряется. Чтобы это исправить, зафиксируйте типы и индексы через Schema Template.
- Фильтр сравнивает путь не в его типе хранения:
Если
-- An implicit numeric comparison is evaluated row by row and uses no index
SELECT * FROM tbl WHERE v['id'] = 123456;
-- CAST to the stored type instead
SELECT * FROM tbl WHERE CAST(v['id'] AS BIGINT) = 123456;v['id']хранится как строка, сравнивайте его со строкой:v['id'] = '123456'. - Неправильно настроенный индекс: индексы применяются к вложенным путям, а не ко всему столбцу VARIANT.
-- VARIANT itself cannot be indexed as a whole
SELECT * FROM tbl WHERE v MATCH 'Doris';
-- If whole-JSON search is needed, store a duplicate STRING column and index it
CREATE TABLE IF NOT EXISTS tbl (
k BIGINT,
v VARIANT,
v_str STRING,
INDEX idx_v_str(v_str) USING INVERTED PROPERTIES("parser" = "english")
);
SELECT * FROM tbl WHERE v_str MATCH 'Doris';
Широкие столбцы
Если загружаемые данные содержат много различных ключей JSON, число подстолбцов, создаваемых при Subcolumnization, может быстро расти; при больших объёмах это приводит к разрастанию метаданных, увеличению затрат на запись и слияние, а также замедлению запросов. Для работы с «широкими столбцами» (слишком большим числом подстолбцов) VARIANT предоставляет два механизма: разреженные столбцы и кодирование DOC.
Рекомендации по выбору режима для рабочей нагрузки см. в руководстве по выбору режима Variant для рабочей нагрузки. Этот раздел описывает только сами механизмы и связанные с ними свойства.
Примечание: эти два механизма взаимоисключающие — включение кодирования DOC отключает разреженные столбцы, и наоборот.
Разреженные столбцы
Как это работает
- Система ранжирует пути по доле значений, отличных от null, и степени разреженности: для часто встречающихся (менее разреженных) путей выполняется Subcolumnization, и они хранятся как отдельные подстолбцы; остальные редкие (разреженные) пути объединяются и хранятся в разреженных столбцах. Максимальное число выделенных подстолбцов регулируется свойством
variant_max_subcolumns_count. - Если путь объявлен в Schema Template, по умолчанию он не переносится в разреженные столбцы; задайте
variant_enable_typed_paths_to_sparse, чтобы разрешить перенос типизированных путей в разреженные столбцы. - Разреженные столбцы поддерживают шардирование: разреженные вложенные пути распределяются между несколькими разреженными столбцами, чтобы снизить затраты на чтение каждого столбца и повысить эффективность чтения. Число физически хранимых разреженных столбцов задаётся через
variant_sparse_hash_shard_count.
Справочные замечания
- Если у большинства ключей примерно одинаковая доля значений, отличных от null (небольшие различия в разреженности), выявить действительно разреженные пути трудно, и польза от разреженных столбцов снижается.
- Значение
variant_max_subcolumns_countпо умолчанию —2048, чего уже достаточно для большинства рабочих нагрузок. Не стоит резко увеличивать его только ради предварительного выделения большего числа подстолбцов; если нагрузке действительно требуется масштабная Subcolumnization, предпочтителен режим DOC. Рекомендуется по-прежнему удерживать практический верхний предел на уровне ≤10000. variant_sparse_hash_shard_countможно приблизительно оценить как «число разреженных путей / 128». Например: всего ключей JSON ≈ 10,000,variant_max_subcolumns_count = 2000, тогда разреженных путей ≈ 8000, поэтому дляvariant_sparse_hash_shard_countможно начать со значения около8000/128.
Кодирование DOC (режим DOC)
Как это работает
- Для путей по-прежнему может выполняться Subcolumnization, чтобы обслуживать запросы к отдельным путям, а исходный JSON дополнительно сохраняется как stored field для эффективного возврата полного документа JSON.
- Кодирование DOC поддерживает шардирование: исходный JSON разбивается на несколько столбцов для хранения и собирается заново при запросе полного JSON. Число шардов DOC задаётся через
variant_doc_hash_shard_count. - При записи небольшими пакетами Subcolumnization можно пропустить и отложить до последующих слияний. Это регулируется свойством
variant_doc_materialization_min_rows. Например, еслиvariant_doc_materialization_min_rows = 10000, при записи менее 10,000 строк сохраняется только исходный JSON, а Subcolumnization для этого пакета не запускается. - Для нагрузок с очень широкими столбцами режим DOC также более стабилен, когда масштаб Subcolumnization приближается к десяти тысячам подстолбцов. По сравнению с немедленной Subcolumnization по умолчанию потребление памяти при compaction может снизиться примерно на две трети, а пропускная способность загрузки разреженных широких столбцов — вырасти примерно в 5-10 раз.
- Когда столбец
VARIANTочень широкий, а запросы часто читают документ целиком, режим DOC может повысить производительностьSELECT variant_colна порядки по сравнению с восстановлением документа из множества подстолбцов.
CREATE TABLE example_table (
id INT,
data_variant VARIANT<
'path_1' : INT,
'path_2' : STRING,
properties(
'variant_enable_doc_mode' = 'true',
'variant_doc_materialization_min_rows' = '10000',
'variant_doc_hash_shard_count' = '64'
)
>
);
Справочные замечания
- Для режима DOC требуется
variant_enable_doc_mode = true. - В режиме DOC поля Schema Template ограничены строками, целыми числами,
FLOAT,DOUBLE,BOOLEANи массивами этих типов. variant_doc_hash_shard_countможно приблизительно оценить как «общее число ключей JSON / 128».
Поведение при достижении пределов и рекомендации по настройке
- После превышения порога новые пути записываются в разреженные столбцы; слияния Rowset также могут переносить некоторые пути в разреженные столбцы.
- Система предпочитает сохранять в Subcolumnization пути с большей долей значений, отличных от null, и более высокой частотой обращений.
- При количестве путей в Subcolumnization около 10,000 требуется мощное оборудование (рекомендуется ≥128G RAM и ≥32C на узел). Если нагрузка уже близка к этому диапазону, сначала оцените режим DOC.
- Настройка загрузки: увеличьте клиентский
batch_sizeдо подходящего значения или используйте Group Commit (при необходимости увеличьтеgroup_commit_interval_ms/group_commit_data_bytes). - Если пропуск бакетов не нужен, рассмотрите распределение по бакетам RANDOM и включение загрузки в один tablet, чтобы уменьшить усиление записи при compaction.
- Параметры настройки BE:
max_cumu_compaction_threads(≥8),vertical_compaction_num_columns_per_group=500(улучшает vertical compaction, но увеличивает потребление памяти),segment_cache_memory_percentage=20(повышает эффективность кеша метаданных). - Следите за Compaction Score; если показатель постоянно растёт, compaction не успевает за загрузкой — снизьте интенсивность загрузки.
- Избегайте объёмных
SELECT *для VARIANT; выбирайте конкретные проекции, напримерSELECT v['path'].
Просмотр подстолбцов и типов
Способ 1: используйте VARIANT_TYPE, чтобы получить тип значения или отдельного пути для каждой строки. Функция читает каждую проверяемую строку:
SELECT VARIANT_TYPE(v), VARIANT_TYPE(v['a']) FROM variant_tbl LIMIT 10;
Способ 2: расширенный вывод DESC показывает вложенные пути, выделенные при Subcolumnization, и их типы хранения:
SET describe_extend_variant_column = true;
DESC variant_tbl;
DESCRIBE ${table_name} PARTITION ($partition_name);
Способ 1 показывает тип каждого значения; способ 2 — то, как хранится каждый путь.
Ограничения
- Оптимизация широких таблиц: для широких таблиц с большим числом динамических подстолбцов (например, более 2000 столбцов), создаваемых типом
VARIANT, настоятельно рекомендуется включить Storage Format V3, указав"storage_format" = "V3"вPROPERTIESтаблицы. Это отделяет метаданные столбцов от Segment Footer, ускоряя открытие файлов и снижая затраты памяти. - По умолчанию длина ключа JSON не может превышать 255 байт (
variant_max_json_key_length). - Столбец VARIANT не может быть ключевым столбцом, столбцом партиционирования или распределения по бакетам, а также не может быть вложен в другой тип в схеме таблицы (см. Определение столбца VARIANT).
- Вне режима DOC чтение всего столбца VARIANT сканирует все вложенные пути. Для очень широких столбцов прямой
SELECT variant_colобычно не рекомендуется, если режим DOC не включён. Если у столбца много вложенных путей, рассмотрите хранение исходной строки JSON в дополнительном столбце STRING/JSONB для поиска по объекту целиком, например черезLIKE:
CREATE TABLE example_table (
id INT,
data_variant VARIANT
);
SELECT * FROM example_table WHERE data_variant LIKE '%doris%';
-- Better: keep the original JSON string for whole-object matching
CREATE TABLE example_table (
id INT,
data_string STRING,
data_variant VARIANT
);
SELECT * FROM example_table WHERE data_string LIKE '%doris%';
Сравнение с типом JSON
- Хранение: JSON хранится как JSONB (в строковом формате). VARIANT использует Subcolumnization при записи (более высокая степень сжатия, меньший объём).
- Запросы: JSON требует разбора. VARIANT сканирует столбцы напрямую и обычно работает намного быстрее.
Модифицированный ClickBench (43 запроса):
- Хранение: VARIANT экономит ~65% по сравнению с JSON.
- Запросы: VARIANT быстрее JSON в 8 раз и более, приближаясь по скорости к заранее определённым статическим столбцам.
Объём хранения
| Тип | Размер |
|---|---|
| Заранее определённые столбцы | 12.618 GB |
| VARIANT | 12.718 GB |
| JSON | 35.711 GB |
Экономия места ~65%
| Запуск | Заранее определённые столбцы | VARIANT | JSON |
|---|---|---|---|
| Первый (холодный) | 233.79s | 248.66s | Большинство запросов превысили время ожидания |
| Второй (прогретый) | 86.02s | 94.82s | 789.24s |
| Третий (прогретый) | 83.03s | 92.29s | 743.69s |
Частые вопросы
- Одинаковы ли
nullв VARIANT и SQLNULL?- Нет. Значение JSON
null, вычисленное в запросе, является значением VARIANTnull, тогда как отсутствующий путь — это SQLNULL. После сохранения значения поля объектов со значениемnullвне массивов удаляются и при чтении возвращаются как SQLNULL. См. Семантика NULL.
- Нет. Значение JSON
- Почему
v['a']возвращаетNULLпослеINSERT INTO t VALUES (1, '{"a": 1}')?INSERTсохраняет строку как строковое значение VARIANT без разбора; так же работаетINSERT INTO ... SELECTизs3(),hdfs()или другого строкового столбца. ИспользуйтеPARSE_TO_VARIANT('{"a": 1}')или загрузите данные заданием загрузки, например Stream Load. См. Запись данных.
- Почему весь документ JSON сохраняется как строка, хотя это корректный JSON?
- Вероятно, документ содержит целое число вне диапазона [-2^63, 2^64 - 1] или число вне диапазона
DOUBLE. Парсер отклоняет такой документ, и по умолчанию он сохраняется как строка. См. Ошибки разбора.
- Вероятно, документ содержит целое число вне диапазона [-2^63, 2^64 - 1] или число вне диапазона
- Почему мой запрос или индекс не работает?
- Проверьте, приводите ли вы пути с помощью CAST к их типам хранения (неявные числовые сравнения не могут использовать индексы), не был ли тип изменён на JSONB из-за конфликтов и не ожидаете ли вы ошибочно индексирования всего VARIANT вместо вложенных путей.
- Почему
ORDER BY v['a']размещает"10"после9, аGROUP BY v['a']разделяет1и"1"?- При упорядочивании и проверке равенства VARIANT сначала учитывается вид значения: числа идут перед строками, а число никогда не равно строке. Приведите путь к одному типу с помощью CAST, если нужна числовая или лексикографическая семантика. См. Сравнение, группировка и упорядочивание.
- Почему
COALESCE(v['a'], 0)возвращает0.000000000, когдаv['a']— строка, аIF(..., v['a'], 1)возвращаетNULL?- При смешивании VARIANT с другим типом значения VARIANT преобразуются к этому типу; для целого числа это
DECIMAL(38, 9). Строку вроде"abc"невозможно преобразовать, и она становитсяNULL, поэтомуCOALESCEвозвращает0как0.000000000. НапишитеCOALESCE(v['a'], CAST(0 AS VARIANT)), чтобы сохранить результат VARIANT, либо приведитеv['a']к нужному типу с помощью CAST.
- При смешивании VARIANT с другим типом значения VARIANT преобразуются к этому типу; для целого числа это
- Почему DECIMAL теряет точность при записи в столбец VARIANT?
- Тип чисел JSON с дробной частью определяется как
DOUBLE, а неDECIMAL, поэтому последние цифры могут теряться. Объявление пути какDECIMALв Schema Template, напримерv VARIANT<'num': DECIMAL(9, 3)>, не решает проблему полностью, поскольку сначала значение разбирается какDOUBLE. Запишите значение внутри документа как строку JSON, напримерPARSE_TO_VARIANT('{"num": "12.345"}'); тогда оно будет преобразовано напрямую вDECIMAL(9, 3)без потерь.
- Тип чисел JSON с дробной частью определяется как