VARIANT и GEOMETRY: новые типы DuckDB 1.5
Версия DuckDB 1.5 (релиз «Variegata», март 2026) добавила в систему типов два важных пополнения. VARIANT — тип для полуструктурированных данных, чья форма заранее неизвестна. GEOMETRY — тип для пространственных данных, и важно, что в 1.5 он стал core-типом: он встроен в ядро, тогда как раньше был доступен только через расширение spatial.
Оба типа закрывают пробелы, которые до 1.5 приходилось обходить. VARIANT решает то, с чем не справляются ни STRUCT, ни MAP, ни даже JSON. GEOMETRY делает пространственную аналитику доступной без явной установки расширения. Этот урок — про обе новинки: что они дают, как устроены и когда применять. Курс ориентируется на актуальные DuckDB 1.5.2 stable и LTS-линию 1.4.x «Andium», и VARIANT с GEOMETRY — часть этой актуальной картины.
Проблема, которую решает VARIANT
В уроках про STRUCT, LIST и MAP мы видели: каждый из них требует какой-то предсказуемости. STRUCT требует фиксированную схему. MAP требует единый тип ключей и единый тип значений. Но бывают данные, у которых нет ни того, ни другого: вложенный объект, где у одного события поле payload — это число, у другого — строка, у третьего — вложенный массив объектов. Структура открыта и непредсказуема.
До 1.5 такие данные хранили в типе JSON. JSON гибок, но у него два недостатка.
ClickHouse: JSON type, Variant и Dynamic типы Первый — JSON в DuckDB по сути хранится как текст: каждый раз при доступе к полю строку нужно разбирать (парсить). Второй — система типов JSON беднее, чем у DuckDB: в JSON нет настоящих DATE, TIMESTAMP, DECIMAL — только строки, числа, булевы, объекты, массивы. Дата в JSON — это строка, и её типизированность теряется.
VARIANT — новый тип, спроектированный именно для полуструктурированных данных. Он хранит данные в типизированном бинарном формате, а не как текст. И он поддерживает больше типов, чем JSON, — в частности, темпоральные типы вроде DATE и TIMESTAMP сохраняются как настоящие типы, а не как строки.
Как работать с VARIANT
VARIANT принимает значение почти любой формы и сохраняет его вместе с информацией о типе. Узнать, какой тип лежит внутри конкретного VARIANT-значения, можно функцией variant_typeof, а достать вложенное поле — функцией variant_extract:
-- Привести значение к VARIANT
SELECT
{'user': 'anna', 'age': 30, 'tags': ['a', 'b']}::VARIANT AS v;
-- Узнать тип, лежащий внутри VARIANT-значения
SELECT variant_typeof( 42::VARIANT ) AS t_num,
variant_typeof( 'hello'::VARIANT ) AS t_str,
variant_typeof( [1,2,3]::VARIANT ) AS t_list;
-- Достать вложенное поле из VARIANT
SELECT variant_extract(v, 'user') AS user_field
FROM events;
Принципиальное отличие VARIANT от STRUCT: у STRUCT тип фиксирован и известен компилятору запроса, а у VARIANT — нет. Каждое VARIANT-значение само несёт описание своего типа, и узнать форму можно только во время выполнения, через variant_typeof. Это та же идея, что у UNION с его тегом, но UNION ограничен заранее перечисленным набором вариантов, а VARIANT открыт — он принимает произвольную форму.
Ещё одно практически важное свойство VARIANT — поддержка Parquet shredding. «Shredding» — это разбор полуструктурированного значения на отдельные типизированные колонки при записи в Parquet. Колонка VARIANT, у которой часть полей встречается стабильно, может быть «расшита» в обычные колонки Parquet, и тогда чтение этих полей становится колоночным и быстрым. Это снимает главный минус хранения полуструктурированных данных — потерю колоночности.
Иерархия выбора типа для данных с переменной формой такая. Структура фиксирована и известна заранее — STRUCT. Структура — это словарь с едиными типами ключей и значений — MAP. Структура полностью произвольна и непредсказуема — VARIANT. JSON остаётся для совместимости и случаев, когда нужен именно текстовый JSON на вводе-выводе, но для хранения и обработки полуструктурированных данных внутри DuckDB 1.5 VARIANT — более типизированный и эффективный выбор.
GEOMETRY: пространственный тип в ядре
GEOMETRY — тип для пространственных данных: точек, линий, полигонов на плоскости. Точка — координата объекта, линия — маршрут или граница, полигон — область, регион, контур здания. Пространственная аналитика отвечает на вопросы «какие объекты внутри этой области», «какое расстояние между точками», «пересекаются ли эти границы».
Главная новость 1.5 — GEOMETRY стал core-типом. До 1.5 пространственные возможности жили в расширении spatial, которое нужно было явно установить и загрузить (INSTALL spatial; LOAD spatial;) — и, в отличие от многих расширений, spatial не подгружается автоматически. В 1.5 сам тип GEOMETRY встроен в ядро DuckDB. Это значит, что хранить геометрию, читать её, держать в колонке таблицы можно без расширения. Расширение spatial по-прежнему нужно для богатого набора пространственных функций и форматов, но базовый тип теперь часть ядра.
-- GEOMETRY-колонка и литералы из WKT-текста
CREATE TABLE places (
id INTEGER,
name VARCHAR,
location GEOMETRY
);
INSERT INTO places VALUES
(1, 'Москва', ST_Point(37.6173, 55.7558)),
(2, 'Казань', ST_Point(49.1221, 55.7887));
Как хранится GEOMETRY
GEOMETRY внутри хранится в формате WKB (Well-Known Binary) — это стандартное компактное бинарное представление геометрических объектов, принятое в геоинформатике. DuckDB использует little-endian вариант WKB. WKB кодирует тип фигуры (точка, линия, полигон) и её координаты в виде байтов.
У GEOMETRY-колонки в DuckDB есть две оптимизации, которые стоит знать.
Первая — статистика ограничивающих прямоугольников (bounding box) на уровне row group. Для каждой группы строк DuckDB хранит прямоугольник, охватывающий все геометрии группы. Когда запрос фильтрует по области («объекты в этом прямоугольнике»), движок по bounding-box-статистике сразу пропускает группы, чьи прямоугольники не пересекаются с областью запроса. Это пространственный аналог zonemap — статистики min/max, по которой DuckDB пропускает блоки данных при обычной фильтрации.
Вторая — shredding для сжатия. Если колонка содержит однородные геометрии (например, все значения — точки), DuckDB может «расшить» их в более плотное представление. Для однородных геометрических колонок shredding даёт примерно трёхкратное сжатие.
GEOMETRY поддерживает опциональную привязку к системе координат (CRS, coordinate reference system) — указание, в какой системе заданы координаты (географические широта/долгота, проекционная система в метрах и т. д.). CRS важен для корректности пространственных расчётов: расстояние между точками считается по-разному в зависимости от системы координат.
Для реальной пространственной аналитики — вычисления расстояний, проверки пересечений, буферов, пространственных join — нужны функции из расширения spatial (ST_Distance, ST_Intersects, ST_Within и десятки других). То, что GEOMETRY стал core-типом, означает только, что сам тип и его хранение встроены в ядро; богатую пространственную алгебру по-прежнему даёт расширение. Установите spatial, когда задача выходит за рамки просто хранения геометрии.
Две новинки в общей картине типов
VARIANT и GEOMETRY расширяют систему типов DuckDB в двух разных направлениях, и оба — про то, чтобы не выходить за пределы DuckDB ради специализированной задачи.
VARIANT закрывает направление «данные без фиксированной формы»: раньше для них был только текстовый JSON с бедной типизацией, теперь есть типизированный бинарный тип, понимающий темпоральные типы и поддерживающий Parquet shredding.
GEOMETRY закрывает направление «пространственные данные»: раньше пространственность требовала явной установки расширения даже для хранения, теперь базовый тип — часть ядра, с bounding-box-статистикой для пропуска групп и shredding для сжатия.
| Тип | Направление | Что было до 1.5 | Что даёт 1.5 |
|---|---|---|---|
VARIANT | Полуструктурированные данные | Текстовый JSON, бедная типизация | Типизированный бинарный формат, темпоральные типы, shredding |
GEOMETRY | Пространственные данные | Только через расширение spatial | Core-тип в ядре, WKB-хранение, bounding-box-статистика |
Оба типа подтверждают общую линию DuckDB: быть полноценной аналитической СУБД, в которой специализированные задачи — полуструктурированные и геоданные — решаются нативно, без обходных путей и без вынужденного экспорта данных в другой инструмент.
Попробуй сам
Запустите DuckDB CLI (версия 1.5 или новее):
-- VARIANT
SELECT variant_typeof( 42::VARIANT ) AS t1,
variant_typeof( 'text'::VARIANT ) AS t2,
variant_typeof( DATE '2026-01-01'::VARIANT ) AS t3;
Задания:
- Выполните запрос выше. Обратите внимание, как
variant_typeofразличает число, строку и дату — попробуйте мысленно сделать то же с типомJSONи объясните, почему дата там была бы строкой. - Создайте
VARIANTиз вложенного объекта ({'a': 1, 'b': [2, 3]}::VARIANT) и достаньте полеaчерезvariant_extract. - Объясните своими словами разницу между
STRUCTиVARIANTс точки зрения того, когда становится известна форма данных. - Создайте таблицу с
GEOMETRY-колонкой и вставьте несколько точек черезST_Point. Убедитесь, что для этого не понадобилось устанавливать расширениеspatial. - Объясните, как bounding-box-статистика на уровне row group ускоряет запрос «найди объекты в заданной прямоугольной области», и в чём её аналогия с обычным zonemap.