JSON indexes
JSON indexes are a type of secondary index implemented on top of an inverted index that speeds up filtering table rows by conditions imposed on the contents of columns of type Json and JsonDocument. The index is used if the WHERE predicate uses the functions JSON_EXISTS and JSON_VALUE with JsonPath expressions. Unlike traditional secondary indexes optimized for equality or range searches on individual table columns, a JSON index works with arbitrary paths within a JSON document.
For a general description of JSON search and the structure of an inverted index on JSON document paths, see the Searching JSON document contents section.
Characteristics of JSON indexes
JSON indexes in YDB allow:
- Quickly filter rows by JSON_EXISTS and JSON_VALUE with JsonPath expressions
- combine indexed conditions with
ANDandORoperators - use query parameter values passed by the application when processing checked predicates.
A JSON index is a global synchronous index — its data is always consistent with the base table.
When executing a query, a JSON index can be applied:
- explicitly — via the
<table_name> VIEW <index_name>operator - automatically by the optimizer, if the predicate matches the formal rules.
Syntax of JSON indexes
Creating a JSON index:
- when creating a table: INDEX (CREATE TABLE)
- Adding to an existing table: ALTER TABLE.
Deleting JSON indexes is done via ALTER TABLE:
ALTER TABLE documents DROP INDEX json_idx
Query syntax with explicit JSON index specification:
Functions and expressions for working with JSON in predicates:
- JSON functions —
JSON_EXISTSandJSON_VALUE - JsonPath — a query language for accessing values inside JSON.
Ready-made use cases are collected in the JSON document search recipes section.
Updating JSON indexes
JSON indexes are automatically maintained when data is modified and are updated synchronously together with the main table. Tables with JSON indexes support:
INSERTUPSERTREPLACEUPDATEDELETE
Batch operations (BATCH UPDATE and BATCH DELETE) are not supported for tables with JSON indexes. When attempting to execute such a query on a table for which a JSON index has been created, the query will be rejected, a corresponding error will be returned to the application, and the data will remain unchanged.
Additionally, tables with JSON indexes do not support:
- Bulk data loading via a
BulkUpsertcall — the requested operation will be rejected with a corresponding error message. - automatic deletion of rows by TTL — errors are returned when attempting to create a table with both a TTL policy and a JSON index, as well as when trying to retrieve such a combination of properties using the
ALTER TABLEcommands.
Supported predicates
For execution via JSON indexes, only expressions based on the JSON_EXISTS and JSON_VALUE functions in the WHERE block, combined by the AND / OR operators according to the rules below, are supported.
JSON_EXISTS
Checking the existence of a path or value inside a JsonPath filter.
Allowed:
-- Document root (value not NULL)
WHERE JSON_EXISTS(doc, '$')
-- Key chain; array indexes are 'transparent'
WHERE JSON_EXISTS(doc, '$.user.name')
WHERE JSON_EXISTS(doc, '$.items[*].sku')
WHERE JSON_EXISTS(doc, '$.items[0 to last].active')
-- Filter ? (...) — predicates inside the filter are allowed
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 methods (path is indexed up to the method; exact check is performed by post-filter)
WHERE JSON_EXISTS(doc, '$.value.type()')
WHERE JSON_EXISTS(doc, '$.arr.size()')
-- Combinations on one column
WHERE JSON_EXISTS(doc, '$.a') AND JSON_EXISTS(doc, '$.b')
WHERE JSON_EXISTS(doc, '$.a') OR JSON_EXISTS(doc, '$.b')
Prohibited (error when using the VIEW statement or index auto-selection failure):
-- Comparison predicates at the top level of the path (outside ? (...))
WHERE JSON_EXISTS(doc, '$.key == 10')
WHERE JSON_EXISTS(doc, 'exists($.key)')
WHERE JSON_EXISTS(doc, '$.key starts with "a"')
-- Negation in JsonPath
WHERE JSON_EXISTS(doc, '!($.key == 10)')
-- ON ERROR TRUE
WHERE JSON_EXISTS(doc, '$.key' TRUE ON ERROR)
-- Path without context operator ($) — the passed document is not used
WHERE JSON_EXISTS(doc, '1')
Note
The JSON_EXISTS function returns true for any non-empty JsonPath result. The $.key == 10 predicate specified at the top level would give "path existence" even when the comparison is false, which does not match the expected semantics. Comparisons should be moved into JSON_VALUE calls or into a filter of the form ? (...).
JSON_VALUE
Extracting a scalar value with a required RETURNING <type>.
To compare a value using JSON_VALUE, you must always specify RETURNING with the required type. By default, JSON_VALUE returns type Utf8, which leads to incorrect comparison during query execution — values of different types are compared as strings:
$tmp = Json(@@["1", 1]@@);
SELECT JSON_VALUE($tmp, '$[0]') == "1"; -- true: correct, string compared with string
SELECT JSON_VALUE($tmp, '$[1]') == "1"; -- true: incorrect, number compared with string
Supported types for the RETURNING section: Int8 … Int64, Uint8 … Uint64, Float, Double, Bytes (String), Text (Utf8), Bool.
Examples of predicates applied via a JSON index:
-- Equality (path + value are included in the index)
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
-- Implicit comparison with true for Bool
WHERE JSON_VALUE(doc, '$.active' RETURNING Bool)
-- Parameters
WHERE JSON_VALUE(doc, '$.user.id' RETURNING Int64) = $id
WHERE JSON_VALUE(doc, '$.tag' RETURNING Utf8) = $tag
-- Comparisons (only the path is used in the index, comparison is performed by post-filter)
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: list of literals
WHERE JSON_VALUE(doc, '$.status' RETURNING Utf8) IN ("open"u, "pending"u)
-- IN: specified parameter of type List<Utf8>
WHERE JSON_VALUE(doc, '$.status' RETURNING Utf8) IN $status_list
-- PASSING for JsonPath variables
WHERE JSON_VALUE(doc, '$.x ? (@.y == $v)' RETURNING Int64 PASSING 42 AS v) = 10
-- JsonPath predicates inside the path.
-- Unlike JSON_EXISTS, predicates at the top level are allowed.
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 combinations on one column
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
Examples of predicates that cannot be applied via a JSON index:
-- JSON_VALUE call without RETURNING
WHERE JSON_VALUE(doc, '$.key') = "x"
-- DEFAULT with ON EMPTY / ON ERROR (except NULL)
WHERE JSON_VALUE(doc, '$.k' RETURNING Utf8 DEFAULT "x" ON ERROR) = "y"
-- Unsupported data type in RETURNING
WHERE JSON_VALUE(doc, '$.ts' RETURNING Timestamp) = ...
-- RETURNING Bool with comparison operators
WHERE JSON_VALUE(doc, '$.flag' RETURNING Bool) >= true
-- IS NULL / IS NOT NULL — semantically contradict the 'path existence' index
WHERE JSON_VALUE(doc, '$.k' RETURNING Utf8) IS NULL
-- Comparison of two JSON_VALUE from different columns
WHERE JSON_VALUE(doc1, '$.k' RETURNING Utf8) = JSON_VALUE(doc2, '$.k' RETURNING Utf8)
-- Nested JSON_* in arguments
WHERE JSON_VALUE(JSON_QUERY(doc, '$.a'), '$.b' RETURNING Utf8) = "x"
Note
To check 'value equals false' or 'value equals null', use a JsonPath filter inside JSON_EXISTS, for example JSON_EXISTS(doc, '$.k ? (@ == false)') or JSON_EXISTS(doc, '$.k ? (@ == null)'), not JSON_VALUE(...) IS NULL.
Limitations
- JSON indexes are supported only for row tables.
- The table's primary key must consist of a single column of an integer type (
Uint64,Uint32,Int64, orInt32). This is a temporary limitation that will be removed in future development. - A single JSON index indexes exactly one column of type
JsonorJsonDocument. - The
COVERexpression is not supported for JSON indexes. - A number of data modification operations and mechanisms are not supported for tables with JSON indexes.
- The parameter type of a read query from the index cannot be wrapped in
Optional<T>— optional parameters are not supported. - Equality comparison with an integer literal whose absolute value exceeds 2⁵³ is not accelerated by the value index (such numbers do not fit into the numeric type used in
JsonandJsonDocument) and is reduced to a path existence check. - Casting floating-point literals (
Float,Double) to integer types during comparison is not performed — such comparison is not accelerated by the index.
Recipes
Ready-made scenarios for working with a JSON index:
- JSON index – quick start — quick start.
- Catalog with nested attributes — product catalog with nested attributes.
- Parameterized queries and JsonPath variables — parameterized queries and JsonPath variables.
- Checking field type and path existence — field type check and path existence.
Related materials
- JSON functions —
JSON_EXISTS,JSON_VALUE,JSON_QUERY, JsonPath syntax. - Secondary indexes — general information about global indexes and
VIEW. - Full-text indexes — a related mechanism built on top of an inverted index of words and phrases.
- INDEX (CREATE TABLE) and VIEW (JSON index) — syntax reference.