Типы JSON
Типы данных JSON предназначены для хранения данных JSON (JavaScript Object Notation, запись объектов JavaScript), в соответствии со стандартом RFC 7159. Подобные данные можно хранить и в виде текста, но преимущество типов JSON в том, что они проверяют каждое сохраненное значение на соответствие правилам JSON. Также для работы с данными, хранящимися в этих типах, существуют различные JSON-специфические функции и операторы; см. раздел Функции и операторы JSON.
QHB предлагает два типа для хранения данных JSON: json и jsonb. Для реализации эффективных механизмов запросов к этим типам данных QHB также предоставляет тип данных jsonpath, описанный в подразделе Тип jsonpath.
Типы данных json и jsonb принимают на ввод почти одинаковые наборы значений. Основное практическое отличие заключается в эффективности. Тип данных json хранит точную копию введенного текста, которую функции обработки должны заново анализировать при каждом выполнении; тогда как данные jsonb хранятся в разобранном двоичном формате, что немного замедляет его ввод из-за дополнительных затрат на преобразование, но значительно ускоряет обработку, так как не требуется повторный анализ. Помимо этого, тип jsonb поддерживает индексирование, что может быть существенным преимуществом.
Поскольку тип json хранит точную копию введенного текста, он сохраняет семантически незначимые пробелы между синтаксическими единицами, а также порядок ключей в объектах JSON. Кроме того, если объект JSON внутри значения содержит повторяющиеся ключи, то сохранятся все пары ключ/значение. (Функции обработки считают действительным последнее значение.) Тип jsonb, напротив, не сохраняет ни пробел, ни порядок ключей объекта, ни их дубликаты. Если во входных данных указаны дублирующиеся ключи, сохраняется только последнее значение.
В целом, в большинстве приложений предпочтительнее хранить данные JSON в формате jsonb, если нет специализированных требований, таких как устаревшие предположения о порядке расположения ключей объектов.
В RFC 7159 говорится, что строки JSON должны быть представлены в кодировке UTF8. Поэтому типы JSON не могут жестко соответствовать спецификации, если кодировка базы данных не UTF8. Напрямую вставить символы, непредставимые в кодировке базы данных, не получится; и наоборот, символы, представимые в кодировке базы данных, но не в UTF8, будут допустимыми.
RFC 7159 позволяет строкам JSON содержать экранированные последовательности Unicode, обозначенные через \u__XXXX__. В функции ввода для типа json экранирование Unicode разрешено независимо от кодировки базы данных и проверяется только на синтаксическую корректность (то есть, после \u должны идти четыре шестнадцатеричные цифры). Однако функция ввода для jsonb более строгая: она запрещает экранирование Unicode для символов, не относящихся к ASCII (т. е. символов выше U+007F), если кодировка базы данных не UTF8. Тип jsonb также не принимает \u0000 (поскольку это значение не может быть представлено в типе text QHB) и требует, чтобы суррогатные пары Unicode для обозначения символов вне основной многоязычной плоскости (BMP) Unicode использовались правильно. Допустимые управляющие символы Unicode преобразуются для хранения в эквивалентные символы ASCII или UTF8 (сюда относится и сворачивание суррогатных пар в один символ).
Примечание
Многие из функций обработки JSON, описанные в разделе Функции и операторы JSON, преобразуют управляющие символы Unicode в обычные и поэтому будут выдавать ошибки того же типа, что и вышеописанные, даже если им ввести данные типа json, а не jsonb. То, что функция ввода в тип json не выполняет эти проверки, можно рассматривать как исторический артефакт, хотя это и позволяет просто сохранять (без обработки) управляющие символы JSON Unicode в кодировке базы данных, не поддерживающей представленные символы.
При преобразовании вводимого текста JSON в jsonb примитивные типы, описанные в RFC 7159, в сущности отображаются в собственные типы QHB, как показано в Таблице 23. Следовательно, к допустимым данным типа jsonb предъявляются некоторые незначительные дополнительные требования, относящиеся к ограничениям представления нижележащего типа данных, которые не применимы ни к типу json, ни к формату JSON в целом. В частности, jsonb будет отклонять числа, выходящие за пределы типа numeric QHB, а json — нет. Подобные ограничения, накладываемые реализацией, допустимы согласно RFC 7159. Однако на практике такие проблемы гораздо более вероятны в других реализациях, поскольку обычно примитивный тип JSON number представляется в виде числа с плавающей запятой двойной точности IEEE 754 (что RFC 7159 явно предусматривает и допускает). При использовании JSON в качестве формата обмена данных с такими системами следует учитывать опасность потери точности чисел относительно данных, изначально сохраняемых в QHB.
И наоборот, как отмечено в таблице, существуют некоторые незначительные ограничения на формат ввода примитивных типов JSON, которые не применяются к соответствующим типам QHB.
Таблица 23. Примитивные типы JSON и соответствующие им типы QHB
Примитивный тип JSON | Тип QHB | Примечания |
---|---|---|
string | text | \u0000 не допускается, так как управляющие символы Unicode недоступны в этой кодировке базы данных |
number | numeric | Значения NaN и infinity не допускаются |
boolean | boolean | Допускается только строчное написание true и false |
null | (none) | В SQL NULL имеет другой смысл |
Синтаксис вводимых и выводимых значений JSON
Синтаксис ввода/вывода типов данных JSON соответствует RFC 7159.
Ниже приведены все допустимые выражения json (или jsonb):
-- Простое скалярное/примитивное значение
-- Примитивные значения могут быть числа, строки в кавычках, true, false или null
SELECT '5'::json;
-- Массив из нуля и более элементов (элементы могут быть разных типов)
SELECT '[1, 2, "foo", null]'::json;
-- Объект, содержащий пары ключей и значений
-- Обратите внимание, что ключи объекта всегда должны быть строками в кавычках
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;
-- Массивы и объекты могут вкладываться произвольным образом
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;
Как было сказано ранее, когда значение JSON вводится, а затем выводится без какой-либо дополнительной обработки, тип json выводит тот же текст, который был введен, тогда как тип jsonb не сохраняет семантически незначимые детали, такие как пробелы. Например, обратите внимание на эти различия:
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
json
-------------------------------------------------
{"bar": "baz", "balance": 7.77, "active":false}
(1 row)
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
jsonb
--------------------------------------------------
{"bar": "baz", "active": false, "balance": 7.77}
(1 row)
Одна семантически незначимая деталь, на которую стоит обратить внимание: в jsonb числа будут выводиться исходя из поведения нижележащего типа numeric. На практике это означает, что числа, введенные в записи с E, будут выведены без нее, например:
SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
json | jsonb
-----------------------+-------------------------
{"reading": 1.230e-5} | {"reading": 0.00001230}
(1 row)
Однако, как видно из этого примера, тип jsonb сохранит конечные нули дробного числа, даже если они семантически незначимы для таких целей, как проверки на равенство.
Список встроенных функций и операторов, доступных для построения и обработки значений JSON, см. в разделе Функции и операторы JSON.
Разработка документов JSON
Представлять данные в JSON можно значительно более гибко, чем в традиционной модели реляционных данных, что особенно важно в средах с изменчивыми требованиями. Оба подхода вполне могут сосуществовать и дополнять друг друга в одном приложении. Однако даже для приложений, где требуется максимальная гибкость, все же рекомендуется, чтобы документы JSON имели некоторую фиксированную структуру. Эта структура, как правило, не навязывается принудительно (хотя возможно декларативное внедрение некоторых бизнес-правил), но ее предсказуемость облегчает написание запросов, которые эффективно извлекают данные из набора «документов» (информационных блоков) в таблице.
Данные JSON подчиняются тем же соображениям управления параллельным доступом, что и любой другой тип данных при хранении в таблице. Хотя хранение больших документов вполне осуществимо, имейте в виду, что при любом изменении вся строка блокируется на уровне строки. Для снижения частоты блокировок транзакций, изменяющих данные, рассмотрите возможность ограничения размера документов JSON до разумных пределов. В идеале каждый документ JSON должен представлять собой атомарный информационных блок, который, согласно бизнес- правилам, нельзя разделить на более мелкие, независимо изменяемые блоки.
Проверка на вложение и существование в jsonb
Проверка вложения является важной особенностью jsonb, не имеющей аналога для типа json. Эта проверка определяет, вложен ли один документ jsonb в другой. Приведенные ниже примеры возвращают значение true, за исключением отмеченных случаев:
-- Простые скалярные/примитивные значения включают только одно идентичное значение:
SELECT '"foo"'::jsonb @> '"foo"'::jsonb;
-- Массив с правой стороны вложен в массив слева:
SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb;
-- Порядок элементов в массиве не важен, поэтому эта запись тоже верна:
SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb;
-- Повторяющиеся элементы массива тоже не имеют значения:
SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb;
-- Объект с одной парой с правой стороны вложен в объект слева:
SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb;
-- Массив с правой стороны не считается вложенным в массив слева, хотя в
-- последний и вложен подобный массив:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb; -- выдает false
-- Но если добавить уровень вложенности, массив считается вложенным:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;
-- Аналогично это вложением не считается:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb; -- выдает false
-- Ключ на верхнем уровне с пустым объектом вложен в объект с таким ключом:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb;
Общий принцип этой проверки заключается в том, что вложенный объект должен соответствовать содержащему его объекту по структуре и содержимому данных, возможно, после исключения из содержащего объекта некоторых неподходящих элементов массива или пар ключ/значение объекта. Но помните, что при сопоставлении вложений порядок элементов массива не имеет значения, а повторяющиеся элементы массива учитываются только один раз.
В качестве специального исключения из общего принципа соответствия структур, массив может содержать примитивное значение:
-- В этот массив вложено примитивное строковое значение:
SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;
-- Это исключение не действует в обратную сторону -- здесь вложения нет:
SELECT '"bar"'::jsonb @> '["bar"]'::jsonb; -- выдает false
У типа jsonb также имеется оператор существования, являющийся вариацией темы вложения: он проверяет, отображается ли строка (заданная как значение text) как ключ объекта или элемент массива на верхнем уровне значения jsonb. Приведенные ниже примеры возвращают значение true, за исключением отмеченных случаев:
-- Строка существует в качестве элемента массива:
SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar';
-- Строка существует в качестве ключа объекта:
SELECT '{"foo": "bar"}'::jsonb ? 'foo';
-- Значения объектов не рассматриваются:
SELECT '{"foo": "bar"}'::jsonb ? 'bar'; -- выдает false
-- Как и вложение, существование должно определяться на верхнем уровне:
SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; -- выдает false
-- Строка считается существующей, если она соответствует примитивной строке JSON:
SELECT '"foo"'::jsonb ? 'foo';
Объекты JSON больше подходят для проверок на вложение или существование со множеством ключей или элементов, нежели массивы, поскольку, в отличие от массивов, они внутренне оптимизированы для поиска и не требуют линейного поиска.
Совет
Поскольку содержимое JSON является вложенным, правильно составленный запрос может пропустить явную выборку внутренних объектов. В качестве примера предположим, что у нас есть столбец doc, содержащий объекты на верхнем уровне, и большинство этих объектов содержит поля tags с массивами внутренних объектов. Данный запрос находит записи, в которых внутренние объекты содержат как "term":"paris", так и "term":"food", при этом пропуская такие ключи вне массива tags:SELECT doc->'site_name' FROM websites WHERE doc @> '{"tags":[{"term":"paris"}, {"term":"food"}]}';
Можно сделать то же самое, скажем, так:
SELECT doc->'site_name' FROM websites WHERE doc->'tags' @> '[{"term":"paris"}, {"term":"food"}]';
Но такой подход менее гибок, а зачастую и менее эффективен.
С другой стороны, оператор существования JSON не является вложенным: он будет искать указанный ключ или элемент массива только на верхнем уровне значения JSON.
Различные операторы вложений и существования, а также все остальные операторы и функции JSON описаны в разделе Функции и операторы JSON.
Индексация jsonb
Для эффективного поиска ключей или пар ключ/значение, встречающихся в большом количестве документов jsonb (информационных блоков), можно использовать индексы GIN. Для этого предоставляются два «класса операторов» GIN, предлагающих различные компромиссы между производительностью и гибкостью.
Класс операторов GIN по умолчанию для jsonb поддерживает запросы с операторами
существования ключа на верхнем уровне: ?
, ?&
и ?|
и оператор
существования пути/значения @>
. (Подробнее семантика, которую реализуют эти
операторы, описана в таблице Дополнительные операторы jsonb.) Пример создания индекса с этим классом операторов:
CREATE INDEX idxgin ON api USING GIN (jdoc);
Нестандартный класс операторов GIN jsonb_path_ops поддерживает
индексирование только оператора @>
. Пример создания индекса с этим классом
операторов:
CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops);
Рассмотрим пример таблицы, в которой хранятся документы JSON, полученные от стороннего веб-сервиса, с документированным определением схемы. Типичный документ:
{
"guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
"name": "Angela Barton",
"is_active": true,
"company": "Magnafone",
"address": "178 Howard Place, Gulf, Washington, 702",
"registered": "2009-11-07T08:53:22 +08:00",
"latitude": 19.793713,
"longitude": 86.513373,
"tags": [
"enim",
"aliquip",
"qui"
]
}
Мы сохраняем эти документы в таблице с именем api, в столбце jsonb с именем jdoc. Если для этого столбца создается индекс GIN, он может использоваться в таких запросах:
-- Найти документы, в которых ключ "company" имеет значение "Magnafone"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';
Однако в запросах, подобных следующему, этот индекс применять нельзя, потому что
хотя оператор ?
и индексируется, он не применяется непосредственно к
индексируемому столбцу jdoc:
-- Найти документы, в которых ключ "tags" содержит ключ или элемент массива "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';
Тем не менее при надлежащем использовании индексов выражений в этом запросе можно использовать индекс. Если запросы к определенным элементам в ключе "tags" выполняются часто, возможно, будет целесообразно определить такой индекс:
CREATE INDEX idxgintags ON api USING GIN ((jdoc -> 'tags'));
Теперь предложение WHERE jdoc -> 'tags' ? 'qui'
будет распознаваться как
приложение индексируемого оператора ?
к индексируемому выражению jdoc -> 'tags'
.
(Более подробную информацию об индексах выражений можно найти в разделе
Индексы по выражениям).
Еще одним подходом к запросам является использование проверки на существование, например:
-- Найти документы, в которых ключ "tags" содержит элемент массива "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}';
В этом запросе можно задействовать простой индекс GIN по столбцу jdoc. Но обратите внимание, что такой индекс будет хранить копии каждого ключа и значения в столбце jdoc, тогда как индекс выражения из предыдущего примера хранить только данные, найденные в объекте с ключом tags. Хотя подход с простым индексом более гибкий (поскольку он поддерживает запросы по любому ключу), индексы целевых выражений скорее всего будут меньше и быстрее в поиске, чем простой индекс.
Кроме того, индекс GIN поддерживает операторы @@
и @?
, которые выполняют
сопоставление jsonpath.
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] ? (@ == "qui")';
Индекс GIN извлекает из jsonpath операторы следующей формы:
цепочка_аксессоров = константа. Цепочка аксессоров может включать
аксессоры .ключ
, [*]
и [индекс]
. jsonb_ops дополнительно
поддерживает аксессоры .*
и .**
.
Хотя класс операторов jsonb_path_ops поддерживает только запросы с операторами
@>
, @@
и @?
, его производительность заметно выше по сравнению с классом
операторов по умолчанию jsonb_ops. Индекс jsonb_path_ops обычно намного
меньше индекса jsonb_ops для тех же данных и более точен при поиске, особенно
когда запросы содержат ключи, часто встречающиеся в данных. Поэтому поисковые
операции с индексом jsonb_path_ops обычно выполняются лучше, чем с классом
операторов по умолчанию.
Техническое различие между индексами GIN jsonb_ops и jsonb_path_ops
состоит в том, что первый создает независимые элементы индекса для всех
ключей/значений в данных, а второй — только для значений.1 По сути, каждый
элемент индекса jsonb_path_ops является хешем значения и приводящего к
нему ключа (или ключей); например, для индексации {"foo": {"bar": "baz"}}
, будет
создан один элемент индекса, объединяющий в хеш все три элемента: foo, bar и
baz. Таким образом, запрос вложений, ищущий эту структуру, приведет к максимально
точному поиску по индексу; но определить, является ли foo ключом, с помощью
этого индекса невозможно. С другой стороны, индекс jsonb_ops создаст три
элемента индекса, представляющих foo, bar и baz по отдельности, а затем для
проверки на вложения будет искать строки, содержащие все три этих элемента. Хотя
индексы GIN могут выполнять поиск с AND довольно эффективно, он все равно будет
менее точным и более медленным, чем равнозначный поиск с jsonb_path_ops,
особенно если любой из этих трех элементов индекса содержится в очень большом
количестве строк.
Недостаток класса операторов jsonb_path_ops заключается в том, что он не
создает индексных записей для структур JSON, не содержащих никаких значений,
таких как {"a": {}}
. При запросе на поиск документов, содержащих такую
структуру, потребуется полное индексное сканирование, которое выполняется
довольно медленно. Поэтому jsonb_path_ops не подходит для приложений,
часто выполняющих такой поиск.
Тип jsonb также поддерживает индексы btree и hash. Обычно они полезны, только если необходимо проверить равенство документов JSON в целом. Порядок сортировки btree для данных jsonb редко представляет большой интерес, но для полноты он приводится ниже:
Объект > Массив > Логическое значение > Число > Строка > Null
Объект с n парами > объект с n - 1 парами
Массив с n элементами > массив с n - 1 элементами
Объекты с равным количеством пар сравниваются в следующем порядке:
ключ-1, значение-1, ключ-2 ...
Обратите внимание, что ключи объектов сравниваются в порядке их хранения; в частности, поскольку более короткие ключи хранятся перед более длинными, в итоге результаты могут оказаться не интуитивными. Например:
{ "aa": 1, "c": 1} > {"b": 1, "d": 1}
Массивы с равным количеством элементов сравниваются аналогично:
элемент-1, элемент-2 ...
Примитивные значения JSON сравниваются по тем же правилам сравнения, что и нижележащие типы данных QHB. Строки сравниваются с учетом порядка сортировки по умолчанию в этой базе данных.
Обращение по индексу к элементам jsonb
Тип данных jsonb поддерживает извлечение и изменение элементов в стиле обращения по индексу к элементам массива. Указывать на вложенные значения можно, задавая цепочку выражений, обращающихся к элементам, следуя при этом тем же правилам, что и аргумент path в функции jsonb_set. Если значение jsonb является массивом, числовые индексы начинаются с нуля, а отрицательные целые числа отсчитывают от последнего элемента в массива к первому. Обращения к срезам массивов не поддерживаются. Результат обращающегося по индексу выражения всегда имеет тип данных jsonb.
Для изменения значений jsonb можно воспользоваться обращением по индексу в
предложении SET оператора UPDATE
. Пути такого обращения должны быть
преодолимыми для всех задействованных значений, если они существуют. Например,
путь val['a']['b']['c'] можно пройти полностью до c, если val,
val['a'] и val['a']['b'] являются объектами. Если же значение
val['a'] или val['a']['b'] не определено, оно будет создано в виде
пустого объекта и должным образом заполнено. Однако если само значение val или
любое из промежуточных значений существует и является не объектом, а, к примеру,
строкой, числом или элементом jsonb null, пройти этот путь невозможно, поэтому
возникает ошибка и транзакция прерывается.
Пример синтаксиса обращения по индексу:
-- Извлечь значение объекта по ключу
SELECT ('{"a": 1}'::jsonb)['a'];
-- Извлечь значение вложенного объекта по пути ключа
SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c'];
-- Извлечь элемент массива по индексу
SELECT ('[1, "2", null]'::jsonb)[1];
-- Изменить значение объекта по ключу. Обратите внимание на апострофы вокруг '1':
-- присваиваемое значение также должно быть типа jsonb
UPDATE table_name SET jsonb_field['key'] = '1';
-- Это вызовет ошибку, если jsonb_field['a']['b'] в какой-либо записи не является
-- объектом. Например, в значении {"a": 1} ключу 'a' соответствует числовое
-- значение.
UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
-- Отфильтровать записи предложением WHERE с обращением по индексу. Поскольку результат
-- обращения имеет тип jsonb, значение, с которым мы его сравниваем, должно быть того же типа.
-- Благодаря кавычкам строка "value" тоже стала допустимой строкой jsonb.
SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
Присваивание jsonb посредством обращения по индексу в некоторых особых случаях работает не так, как с функцией jsonb_set. Когда исходное значение jsonb равно NULL, присваивание при обращении по индексу происходит так, будто это значение было пустым значением JSON (типа объект или массив, в зависимости от ключа обращения):
-- Там, где поле jsonb_field было NULL, оно станет {"a": 1}
UPDATE table_name SET jsonb_field['a'] = '1';
-- Там, где поле jsonb_field было NULL, оно станет [1]
UPDATE table_name SET jsonb_field[0] = '1';
Если индекс указан для массива, содержащего недостаточно элементов, в него будут добавляться элементы со значением NULL до тех пор, пока индекс не станет достижимым и пока не станет возможным задать значение.
-- Там, где поле jsonb_field было [], оно станет [null, null, 2];
-- Там, где поле jsonb_field было [0], оно станет [0, null, 2]
UPDATE table_name SET jsonb_field[2] = '2';
Значение jsonb будет принимать присваивания по несуществующим путям обращения, пока последний существующий элемент, который нужно пройти, является объектом или массивом, в зависимости от соответствующего компонента пути (элемент, на который указывает последний компонент пути, не проходится и может быть любым). Будут созданы вложенные структуры массивов и объектов, и для массивов они дополнятся элементами NULL до позиции, заданной в пути обращения, в которую можно будет поместить присваиваемое значение.
-- Там, где поле jsonb_field было {}, оно станет {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
-- Там, где поле jsonb_field было [], оно станет [null, {'a': 1}]
UPDATE table_name SET jsonb_field[1]['a'] = '1';
Преобразования
Для различных процедурных языков доступны дополнительные расширения, которые реализуют преобразования для типа jsonb.
Тип jsonpath
Тип jsonpath реализует поддержку языка путей SQL/JSON в QHB, позволяющего эффективно выполнять запросы данных JSON. Он обеспечивает двоичное представление разобранного выражения пути SQL/JSON, которое определяет, какие элементы нужно извлечь механизмом пути из данных JSON для дальнейшей обработки в функциях запроса SQL/JSON.
Семантика предикатов и операторов пути SQL/JSON в целом соответствует SQL. В то же время, чтобы обеспечить наиболее естественный способ работы с данными JSON, в синтаксисе пути SQL/JSON приняты некоторые соглашения JavaScript:
-
Точка (.) используется для доступа к члену объекта.
-
Квадратные скобки ([]) используются для доступа к массиву.
-
Массивы SQL/JSON нумеруются, начиная с 0, тогда как обычные массивы SQL — с 1.
Выражение пути SQL/JSON обычно записывается в запросе SQL в виде символьного строкового литерала, поэтому его необходимо заключать в апострофы, а любые апострофы, требуемые в этом значении, необходимо удвоить (см. подраздел Строковые константы). Строковые литералы также требуется использовать и внутри некоторых форм выражений пути. На такие встроенные строковые литералы распространяются соглашения JavaScript/ECMAScript: они должны заключаться в кавычки, а для представления символов, которые трудно ввести иначе, можно использовать экранированные символы с обратным слэшем. В частности, символ кавычки во встроенном строковом литерале записывается как \", а сам обратный слэш — как \\. Другие спецпоследовательности с обратным слэшем включают в себя те, которые распознаются в строках JSON: \b, \f, \n, \r, \t, \v для различных управляющих символов ASCII и \uNNNN для символа Unicode, идентифицируемого кодом из 4 шестнадцатеричных цифр. Синтаксис обратного слэша также включает два варианта, которые не допускаются JSON: \xNN для символьного кода в виде только двух шестнадцатеричных цифр и \u{N...} для символьного кода, записанного с использованием от 1 до 6 шестнадцатеричных цифр.
Выражение пути состоит из последовательности элементов пути, которые могут быть следующими:
-
Литералы пути примитивных типов JSON:Unicode text, numeric, true, false или null.
-
Переменные пути, перечисленные в Таблице 24.
-
Операторы аксессоров, перечисленные в Таблице 25.
-
Операторы и методы jsonpath, перечисленные в подразделе Операторы и методы пути SQL/JSON.
-
Круглые скобки, которые можно использовать для ввода выражений фильтра или определения порядка вычисления пути.
Подробную информацию об использовании выражений jsonpath с функциями запросов SQL/JSON см. в подразделе Язык путей SQL/JSON.
Таблица 24. Переменные jsonpath
Переменная | Описание |
---|---|
$ | Переменная, представляющая текст JSON для запроса (элемент контекста). |
$varname | Именованная переменная. Ее значение можно задать в параметре vars некоторых функций обработки JSON. Подробную информацию см. в таблице Функции для обработки JSON и ее примечаниях. |
@ | Переменная, представляющая результат вычисления пути в выражениях фильтров. |
Таблица 25. Операторы аксессоров jsonpath
Оператор аксессора | Описание |
---|---|
.ключ ."$имя_переменной" | Оператор аксессора к члену, который возвращает член объекта с указанным ключом. Если имя ключа задается именованной переменной, начинающейся с $, или не соответствует правилам JavaScript относительно идентификаторов, оно должно быть заключено в кавычки как символьный строковый литерал. |
.* | Оператор аксессора по подстановочному символу, который возвращает значения всех элементов, расположенных на верхнем уровне текущего объекта. |
.** | Рекурсивный оператор аксессора по подстановочному символу, который обрабатывает все уровни иерархии JSON текущего объекта и возвращает все значения элементов, независимо от уровня их вложенности. Это реализованное в QHB расширение стандарта SQL/JSON. |
.**{уровень} .**{начальный_уровень to конечный_уровень} | То же, что и .**, но с фильтром по уровням вложенности иерархии JSON. Уровни вложенности указываются как целые числа. Нулевой уровень соответствует текущему объекту. Чтобы получить доступ к самому низкому уровню вложенности, можно использовать ключевое слово last. Это реализованное в QHB расширение стандарта SQL/JSON. |
[нижний_индекс, ...] | Оператор аксессора к элементу массива. Нижний_индекс можно задать в двух формах: индекс или начальный_индекс to конечный_индекс. Первая форма возвращает единственный элемент массива по его индексу. Вторая форма возвращает срез массива по диапазону индексов, включая элементы, которые соответствуют предоставленным начальному_индексу и конечному_индексу. Задаваемый индекс может быть целым числом или выражением, возвращающим единственное числовое значение, которое автоматически приводится к целому числу. Нулевой индекс соответствует первому элементу массива. Также для обозначения последнего элемента массива можно использовать ключевое слово last, что полезно при обработке массивов неизвестной длины. |
[*] | Оператор аксессора к элементам массива по подстановочному символу, который возвращает все элементы массива. |
Поэтому термин «значение» включает элементы массива, хотя в терминологии JSON иногда элементы массива считаются отличными от значений внутри объектов.