JSON-индексы
JSON-индексы — это разновидность вторичного индекса, реализованная поверх инвертированного индекса, которая ускоряет фильтрацию строк таблицы по условиям, накладываемым на содержимое колонок типа Json и JsonDocument. Индекс задействуется, если в предикате WHERE используются функции JSON_EXISTS и JSON_VALUE с выражениями JsonPath. В отличие от традиционных вторичных индексов, оптимизированных для поиска по равенству или диапазону отдельных колонок таблицы, JSON-индекс работает с произвольными путями внутри JSON-документа.
Общее описание поиска по JSON и устройства инвертированного индекса по путям JSON-документа см. в разделе Поиск по содержимому JSON-документов.
Характеристики JSON-индексов
JSON-индексы в YDB позволяют:
- быстро фильтровать строки по JSON_EXISTS и JSON_VALUE с выражениями JsonPath;
- комбинировать индексируемые условия операторами
ANDиOR; - использовать значения параметров запроса, переданные приложением, при обработке проверяемых предикатов.
JSON-индекс является глобальным синхронным индексом — его данные всегда согласованы с основной таблицей.
При выполнении запроса JSON-индекс может быть применён:
- явно — через оператор
<имя_таблицы> VIEW <имя_индекса>; - автоматически — оптимизатором, если предикат подходит под формальные правила.
Синтаксис JSON-индексов
Создание JSON-индекса:
- при создании таблицы: INDEX (CREATE TABLE);
- добавление к существующей таблице: ALTER TABLE.
Удаление JSON-индексов выполняется через ALTER TABLE:
ALTER TABLE documents DROP INDEX json_idx
Синтаксис запроса с явным указанием JSON-индекса:
Функции и выражения для работы с JSON в предикатах:
- Функции для работы с JSON —
JSON_EXISTSиJSON_VALUE; - JsonPath — язык запросов для обращения к значениям внутри JSON.
Готовые сценарии использования собраны в разделе рецептов поиска по JSON-документам.
Обновление JSON-индексов
JSON-индексы автоматически поддерживаются при модификации данных и обновляются синхронно вместе с основной таблицей. Таблицы с JSON-индексами поддерживают:
INSERTUPSERTREPLACEUPDATEDELETE
Пакетные операции (BATCH UPDATE и BATCH DELETE) для таблиц с JSON-индексами не поддерживаются. При попытке выполнения такого запроса над таблицей, для которой создан JSON-индекс, этот запрос будет отклонен, в приложение будет возвращена соответствующая ошибка, а данные останутся в неизменном состоянии.
Кроме того, для таблиц с JSON-индексами не поддерживается:
- массовая загрузка данных через вызов
BulkUpsert— запрошенная операция будет отклонена с соответствующим сообщением об ошибке; - автоматическое удаление строк по TTL — будут возвращены ошибки при попытке создать таблицу одновременно с TTL-политикой и JSON-индексом, а также при попытке получить такое сочетание свойств через команды
ALTER TABLE.
Поддерживаемые предикаты
Для выполнения через JSON-индексы поддерживаются только выражения на основе функций JSON_EXISTS и JSON_VALUE в блоке WHERE, объединённые операторами AND / OR по правилам ниже.
JSON_EXISTS
Проверка существования пути или значения внутри фильтра JsonPath.
Разрешено:
-- Корень документа (значение не NULL)
WHERE JSON_EXISTS(doc, '$')
-- Цепочка ключей; индексы массивов «прозрачны»
WHERE JSON_EXISTS(doc, '$.user.name')
WHERE JSON_EXISTS(doc, '$.items[*].sku')
WHERE JSON_EXISTS(doc, '$.items[0 to last].active')
-- Фильтр ? (...) — предикаты внутри фильтра допустимы
WHERE JSON_EXISTS(doc, '$.items ? (@.price == 100)')
WHERE JSON_EXISTS(doc, '$.items ? (@.qty >= 1 && @.qty <= 10)')
WHERE JSON_EXISTS(doc, '$.items ? (@.tag == $t)' PASSING "sale" AS t)
-- Методы JsonPath (путь индексируется до метода; точную проверку выполняет пост-фильтр)
WHERE JSON_EXISTS(doc, '$.value.type()')
WHERE JSON_EXISTS(doc, '$.arr.size()')
-- Комбинации на одной колонке
WHERE JSON_EXISTS(doc, '$.a') AND JSON_EXISTS(doc, '$.b')
WHERE JSON_EXISTS(doc, '$.a') OR JSON_EXISTS(doc, '$.b')
Запрещено (ошибка при использовании оператора VIEW или отказ автовыбора индекса):
-- Предикаты сравнения на верхнем уровне пути (вне ? (...))
WHERE JSON_EXISTS(doc, '$.key == 10')
WHERE JSON_EXISTS(doc, 'exists($.key)')
WHERE JSON_EXISTS(doc, '$.key starts with "a"')
-- Отрицание в JsonPath
WHERE JSON_EXISTS(doc, '!($.key == 10)')
-- ON ERROR TRUE
WHERE JSON_EXISTS(doc, '$.key' TRUE ON ERROR)
-- Путь без оператора контекста ($) — переданный документ не используется
WHERE JSON_EXISTS(doc, '1')
Примечание
Функция JSON_EXISTS возвращает true для любого непустого результата JsonPath. Предикат $.key == 10, указанный на верхнем уровне, дал бы «существование пути» даже когда сравнение ложно, что не соответствует ожидаемой семантике. Сравнения нужно выносить в вызовы JSON_VALUE или в фильтр вида ? (...).
JSON_VALUE
Извлечение скалярного значения с обязательным RETURNING <тип>.
Для сравнения значения через JSON_VALUE всегда нужно указывать RETURNING с нужным типом. По умолчанию JSON_VALUE возвращает тип Utf8, что во время выполнения запроса приводит к некорректному сравнению — значения разных типов сравниваются как строки:
$tmp = Json(@@["1", 1]@@);
SELECT JSON_VALUE($tmp, '$[0]') == "1"; -- true: корректно, строка сравнивается со строкой
SELECT JSON_VALUE($tmp, '$[1]') == "1"; -- true: некорректно, число сравнивается со строкой
Поддерживаемые типы для секции RETURNING: Int8 … Int64, Uint8 … Uint64, Float, Double, Bytes (String), Text (Utf8), Bool.
Примеры предикатов, применяемых через JSON-индекс:
-- Равенство (путь + значение попадают в индекс)
WHERE JSON_VALUE(doc, '$.user.age' RETURNING Int32) = 25
WHERE JSON_VALUE(doc, '$.flag' RETURNING Bool) = true
WHERE JSON_VALUE(doc, '$.name' RETURNING Utf8) = "Alice"u
-- Неявное сравнение с true для Bool
WHERE JSON_VALUE(doc, '$.active' RETURNING Bool)
-- Параметры
WHERE JSON_VALUE(doc, '$.user.id' RETURNING Int64) = $id
WHERE JSON_VALUE(doc, '$.tag' RETURNING Utf8) = $tag
-- Сравнения (в индексе задействован только путь, сравнение выполняет пост-фильтр)
WHERE JSON_VALUE(doc, '$.score' RETURNING Int64) > 0
WHERE JSON_VALUE(doc, '$.score' RETURNING Int64) != 100
WHERE JSON_VALUE(doc, '$.score' RETURNING Int64) BETWEEN 1 AND 10
WHERE JSON_VALUE(doc, '$.score' RETURNING Int64) NOT BETWEEN 0 AND 5
-- IN: список литералов
WHERE JSON_VALUE(doc, '$.status' RETURNING Utf8) IN ("open"u, "pending"u)
-- IN: заданный параметр типа List<Utf8>
WHERE JSON_VALUE(doc, '$.status' RETURNING Utf8) IN $status_list
-- PASSING для переменных JsonPath
WHERE JSON_VALUE(doc, '$.x ? (@.y == $v)' RETURNING Int64 PASSING 42 AS v) = 10
-- Предикаты JsonPath внутри пути.
-- В отличие от JSON_EXISTS, разрешены предикаты на верхнем уровне.
WHERE JSON_VALUE(doc, '$.user ? (@.role == "admin")' RETURNING Utf8) = "ok"u
WHERE JSON_VALUE(doc, '$.code starts with "A"' RETURNING String) != ""
WHERE JSON_VALUE(doc, 'exists($.meta)' RETURNING Bool)
-- Комбинации AND / OR на одной колонке
WHERE JSON_VALUE(doc, '$.a' RETURNING Int32) = 1
OR JSON_VALUE(doc, '$.b' RETURNING Int32) = 2
WHERE JSON_EXISTS(doc, '$.a') AND JSON_VALUE(doc, '$.a' RETURNING Int32) = 10
Примеры предикатов, которые не могут быть применены через JSON-индекс:
-- Вызов JSON_VALUE без RETURNING
WHERE JSON_VALUE(doc, '$.key') = "x"
-- DEFAULT при ON EMPTY / ON ERROR (кроме NULL)
WHERE JSON_VALUE(doc, '$.k' RETURNING Utf8 DEFAULT "x" ON ERROR) = "y"
-- Неподдерживаемый тип данных в RETURNING
WHERE JSON_VALUE(doc, '$.ts' RETURNING Timestamp) = ...
-- RETURNING Bool с операторами сравнения
WHERE JSON_VALUE(doc, '$.flag' RETURNING Bool) >= true
-- IS NULL / IS NOT NULL — семантически противоречат индексу «существования пути»
WHERE JSON_VALUE(doc, '$.k' RETURNING Utf8) IS NULL
-- Сравнение двух JSON_VALUE с разных колонок
WHERE JSON_VALUE(doc1, '$.k' RETURNING Utf8) = JSON_VALUE(doc2, '$.k' RETURNING Utf8)
-- Вложенные JSON_* в аргументах
WHERE JSON_VALUE(JSON_QUERY(doc, '$.a'), '$.b' RETURNING Utf8) = "x"
Примечание
Для проверки «значение равно false» или «значение равно null» используйте фильтр JsonPath внутри JSON_EXISTS, например JSON_EXISTS(doc, '$.k ? (@ == false)') или JSON_EXISTS(doc, '$.k ? (@ == null)'), а не JSON_VALUE(...) IS NULL.
Ограничения
- JSON-индексы поддерживаются только для строковых таблиц.
- Первичный ключ таблицы должен состоять из единственной колонки целочисленного типа (
Uint64,Uint32,Int64илиInt32). Это временное ограничение, которое будет снято в ходе дальнейшего развития. - В одном JSON-индексе индексируется ровно одна колонка типа
JsonилиJsonDocument. - Выражение
COVERдля JSON-индексов не поддерживается. - Для таблиц с JSON-индексами не поддерживается ряд операций и механизмов модификации данных.
- Тип параметра запроса чтения из индекса не может быть обёрнут в
Optional<T>— опциональные параметры не поддерживаются. - Сравнение по равенству с целочисленным литералом, абсолютное значение которого превышает 2⁵³, не ускоряется индексом по значению (такие числа не помещаются в численный тип, используемый в
JsonиJsonDocument) и сводится к проверке существования пути. - Приведение вещественных литералов (
Float,Double) к целочисленным типам при сравнении не выполняется — такое сравнение не ускоряется индексом.
Рецепты
Готовые сценарии работы с JSON-индексом:
- JSON-индекс — быстрый старт — быстрый старт.
- Каталог со вложенными атрибутами — каталог товаров со вложенными атрибутами.
- Параметризованные запросы и переменные JsonPath — параметризованные запросы и переменные JsonPath.
- Проверка типа поля и наличия пути — проверка типа поля и наличия пути.
Связанные материалы
- Функции для работы с JSON —
JSON_EXISTS,JSON_VALUE,JSON_QUERY, синтаксис JsonPath. - Вторичные индексы — общие сведения о глобальных индексах и
VIEW. - Полнотекстовые индексы — соседний механизм, построенный поверх инвертированного индекса по словам и фразам.
- INDEX (CREATE TABLE) и VIEW (JSON-индекс) — справка по синтаксису.