9.16. Функции и операторы JSON
В этом разделе описываются:
- функции и операторы, предназначенные для работы с данными JSON 
- язык путей SQL/JSON 
Чтобы узнать больше о стандарте SQL/JSON, обратитесь к [sqltr-19075-6]. Типы JSON, поддерживаемые в Postgres Pro, описаны в Разделе 8.14.
9.16.1. Обработка и создание данных JSON
Примечание
Функции, работающие с JSONB, не принимают символы '\u0000'. Чтобы избежать ошибок и заменять их на лету, необходимо указать символ Unicode в параметре конфигурации unicode_nul_character_replacement_in_jsonb.
В Таблице 9.44 показаны имеющиеся операторы для работы с данными JSON (см. Раздел 8.14). Кроме них для типа jsonb, но не для json, определены обычные операторы сравнения, показанные в Таблице 9.1. Они следуют правилам упорядочивания для операций B-дерева, описанным в Подразделе 8.14.4. В Разделе 9.21 вы также можете узнать об агрегатной функции json_agg, которая агрегирует значения записи в виде JSON, и агрегатной функции json_object_agg, агрегирующей пары значений в объект JSON, а также их аналогах для jsonb, функциях jsonb_agg и jsonb_object_agg.
Таблица 9.44. Операторы для типов json и jsonb
| Оператор Описание Пример(ы) | 
|---|
| 
 
 Извлекает  
 
 | 
| 
 
 Извлекает поле JSON-объекта по заданному ключу. 
 | 
| 
 
 Извлекает  
 | 
| 
 
 Извлекает поле JSON-объекта по заданному ключу, в виде значения  
 | 
| 
 
 Извлекает внутренний JSON-объект по заданному пути, элементами которого могут быть индексы массивов или ключи. 
 | 
| 
 
 Извлекает внутренний JSON-объект по заданному пути в виде значения  
 | 
Примечание
Если структура входного JSON не соответствует запросу, например указанный ключ или элемент массива отсутствует, операторы извлечения поля/элемента/пути не выдают ошибку, а возвращают NULL.
Некоторые из следующих операторов существуют только для jsonb, как показано в Таблице 9.45. В Подразделе 8.14.4 описано, как эти операторы могут использоваться для эффективного поиска в индексированных данных jsonb.
Таблица 9.45. Дополнительные операторы jsonb
| Оператор Описание Пример(ы) | 
|---|
| 
 Первое значение JSON содержит второе? (Что означает «содержит», подробно описывается в Подразделе 8.14.3.) 
 | 
| 
 Первое значение JSON содержится во втором? 
 | 
| 
 Текстовая строка присутствует в значении JSON в качестве ключа верхнего уровня или элемента массива? 
 
 | 
| 
 Какие-либо текстовые строки из массива присутствуют в качестве ключей верхнего уровня или элементов массива? 
 | 
| 
 Все текстовые строки из массива присутствуют в качестве ключей верхнего уровня или элементов массива? 
 | 
| 
 Соединяет два значения  
 
 
 
 Чтобы вставить один массив в другой в качестве массива, поместите его в дополнительный массив, например: 
 | 
| 
 Удаляет ключ (и его значение) из JSON-объекта или соответствующие строковые значения из JSON-массива. 
 
 | 
| 
 Удаляет из левого операнда все перечисленные ключи или элементы массива. 
 | 
| 
 Удаляет из массива элемент в заданной позиции (отрицательные номера позиций отсчитываются от конца). Выдаёт ошибку, если переданное значение JSON — не массив. 
 | 
| 
 Удаляет поле или элемент массива с заданным путём, в составе которого могут быть индексы массивов или ключи. 
 | 
| 
 Выдаёт ли путь JSON какой-либо элемент для заданного значения JSON? 
 | 
| 
 Возвращает результат проверки предиката пути JSON для заданного значения JSON. При этом учитывается только первый элемент результата. Если результат не является логическим, возвращается  
 | 
Примечание
Операторы jsonpath @? и @@ подавляют следующие ошибки: отсутствие поля объекта или элемента массива, несовпадение типа элемента JSON и ошибки в числах и дате/времени. Описанные ниже функции, связанные с jsonpath, тоже могут подавлять ошибки такого рода. Это может быть полезно, когда нужно произвести поиск по набору документов JSON, имеющих различную структуру.
В Таблице 9.46 показаны функции, предназначенные для создания значений json и jsonb.
Таблица 9.46. Функции для создания JSON
| Функция Описание Пример(ы) | 
|---|
|   Преобразует произвольное SQL-значение в  
 
 | 
|   Преобразует массив SQL в JSON-массив. Эта функция работает так же, как  
 | 
|   Преобразует составное значение SQL в JSON-объект. Эта функция работает так же, как  
 | 
|     Формирует JSON-массив (возможно, разнородный) из переменного списка аргументов. Каждый аргумент преобразуется методом  
 | 
|     Формирует JSON-объект из переменного списка аргументов. По соглашению в этом списке перечисляются по очереди ключи и значения. Аргументы, задающие ключи, приводятся к текстовому типу, а аргументы-значения преобразуются методом  
 | 
|   Формирует объект JSON из текстового массива. Этот массив должен иметь либо одну размерность с чётным числом элементов (в этом случае они воспринимаются как чередующиеся ключи/значения), либо две размерности и при этом каждый внутренний массив содержит ровно два элемента, которые воспринимаются как пара ключ/значение. Все значения преобразуются в строки JSON. 
 
 | 
| 
 
 Эта форма  
 | 
В Таблице 9.47 показаны функции, предназначенные для работы со значениями json и jsonb.
Таблица 9.47. Функции для обработки JSON
| Функция Описание Пример(ы) | 
|---|
|     Разворачивает JSON-массив верхнего уровня в набор значений JSON. 
 value ----------- 1 true [2,false] | 
|     Разворачивает JSON-массив верхнего уровня в набор значений  
 value ----------- foo bar | 
|     Возвращает число элементов во внешнем JSON-массиве верхнего уровня. 
 
 | 
|     Разворачивает JSON-объект верхнего уровня в набор пар ключ/значение (key/value). 
 key | value -----+------- a | "foo" b | "bar" | 
|     Разворачивает JSON-объект верхнего уровня в набор пар ключ/значение (key/value). Возвращаемые значения будут иметь тип  
 key | value -----+------- a | foo b | bar | 
|     Извлекает внутренний JSON-объект по заданному пути. (То же самое делает оператор  
 | 
|     Извлекает внутренний JSON-объект по заданному пути в виде значения  
 | 
|     Выдаёт множество ключей в JSON-объекте верхнего уровня. 
 json_object_keys ----------------- f1 f2 | 
|     Разворачивает JSON-объект верхнего уровня в строку, имеющую составной тип аргумента  Для преобразования значения JSON в SQL-тип выходного столбца последовательно применяются следующие правила: 
 В следующем примере значение JSON фиксировано, но обычно такая функция обращается с использованием  
 
  a |   b       |      c
---+-----------+-------------
 1 | {2,"a b"} | (4,"a b c") | 
|     Разворачивает JSON-массив верхнего уровня с объектами в набор строк, имеющих составной тип аргумента  
 
 a | b ---+--- 1 | 2 3 | 4 | 
|     Разворачивает JSON-объект верхнего уровня в строку, имеющую составной тип, определённый в предложении  
 
  a |    b    |    c    | d |       r
---+---------+---------+---+---------------
 1 | [1,2,3] | {1,2,3} |   | (123,"a b c") | 
|     Разворачивает JSON-массив верхнего уровня с объектами в набор строк, имеющих составной тип, определённый в предложении  
 a | b ---+----- 1 | foo 2 | | 
|   Возвращает объект  
 
 | 
|   Если значение  
 
 | 
|   Возвращает объект  
 
 | 
|     Удаляет из данного значения JSON все поля объектов, имеющие значения null, на всех уровнях вложенности. Значения null, не относящиеся к полям объектов, сохраняются без изменений. 
 | 
|   Проверяет, есть ли в заданном значении JSON какой-либо элемент, соответствующий пути JSON. В случае присутствия аргумента  
 | 
|   Возвращает результат проверки предиката пути JSON для заданного значения JSON. При этом учитывается только первый элемент результата. Если результат не является логическим, возвращается  
 | 
|   Возвращает все элементы JSON, полученные по указанному пути для заданного значения JSON. Дополнительные аргументы  
 jsonb_path_query ------------------ 2 3 4 | 
|   Возвращает все элементы JSON, полученные по указанному пути для заданного значения JSON, в виде JSON-массива. Дополнительные аргументы  
 | 
|   Возвращает первый элемент JSON, полученный по указанному пути для заданного значения JSON, либо NULL, если этому пути не соответствуют никакие элементы. Дополнительные аргументы  
 | 
|           Эти функции работают подобно их двойникам без суффикса  
 | 
| Преобразует данное значение JSON в визуально улучшенное текстовое представление с отступами. 
 [
    {
        "f1": 1,
        "f2": null
    },
    2
] | 
| Возвращает тип значения на верхнем уровне JSON в виде текстовой строки. Возможные типы:  
 
 
 | 
9.16.2. Язык путей SQL/JSON
Выражения путей SQL/JSON определяют элементы, извлекаемые из данных JSON, подобно тому, как выражения XPath позволяют обращаться из SQL к XML. В Postgres Pro выражения путей представляются в виде типа данных jsonpath и могут использовать любые элементы, описанные в Подразделе 8.14.6.
Операторы и функции запросов к JSON передают поступившее им выражение обработчику путей для вычисления. Если выражению соответствуют фигурирующие в запросе данные JSON, в результате выдаётся соответствующий элемент JSON или набор элементов. Выражения путей записываются на языке путей SQL/JSON и могут включать сложные арифметические выражения и функции.
Выражение пути состоит из последовательности элементов, допустимых для типа jsonpath. Обычно оно вычисляется слева направо, но при необходимости порядок операций можно изменить, добавив скобки. В случае успешного вычисления выдаётся последовательность элементов JSON, и результат вычисления возвращается в функцию JSON-запроса, которая завершает обработку выражения.
Для обращения к поступившему в запрос значению JSON (элементу контекста) в выражении пути используется переменная $. Затем могут следовать один или более операторов обращения, которые, опускаясь в структуре JSON с одного уровня на другой, извлекают элементы, вложенные в текущий элемент контекста. При этом каждый последующий оператор имеет дело с результатом вычисления, полученным на предыдущем шаге.
Например, предположим, что у вас есть данные JSON, полученные от GPS-трекера, которые вы хотели бы проанализировать:
{
  "track": {
    "segments": [
      {
        "location":   [ 47.763, 13.4034 ],
        "start time": "2018-10-14 10:05:14",
        "HR": 73
      },
      {
        "location":   [ 47.706, 13.2635 ],
        "start time": "2018-10-14 10:39:21",
        "HR": 135
      }
    ]
  }
}Чтобы получить имеющиеся сегменты треков, воспользуйтесь оператором обращения ., который позволяет погрузиться внутрь JSON-объектов: ключ
$.track.segments
Для получения содержимого массива обычно используется оператор [*]. Например, следующий путь выдаст координаты концов всех имеющихся сегментов треков: 
$.track.segments[*].location
Чтобы получить координаты только первого сегмента, можно задать соответствующий индекс в операторе обращения []. Заметьте, что индексы в JSON-массивах отсчитываются с 0: 
$.track.segments[0].location
Результат каждого шага вычисления выражения может быть обработан операторами и методами jsonpath, перечисленными в Подразделе 9.16.2.2. Перед именем метода должна стоять точка. Например, так можно получить размер массива: 
$.track.segments.size()
 Другие примеры использования операторов и методов jsonpath в выражениях пути приведены ниже в Подразделе 9.16.2.2.
Определяя путь, также можно использовать выражения фильтра, работающие подобно предложению WHERE в SQL. Выражение фильтра начинается со знака вопроса и содержит условие в скобках: 
? (условие)Выражения фильтра указываются сразу после шага вычисления пути, к которому они должны применяться. Результаты шага проходят через фильтр, и на выходе остаются только те элементы, которые удовлетворяют заданному условию. В SQL/JSON действует троичная логика, то есть результатом выражения может быть true, false или unknown (неизвестность). Значение unknown играет ту же роль, что и NULL в SQL, и может быть проверено предикатом is unknown. На последующих шагах вычисления пути будут обрабатываться только те элементы, для которых выражение фильтра выдало true.
Функции и операторы, которые можно использовать в выражениях фильтра, перечислены в Таблице 9.49. Переменная @ в выражении фильтра представляет фильтруемое значение (результат предыдущего шага в пути). Для получения внутренних элементов этого значения вы можете добавить после @ операторы обращения.
Например, предположим, что вы хотите получить все показатели пульса, превышающие 130. Это можно сделать с помощью следующего выражения:
$.track.segments[*].HR ? (@ > 130)
Чтобы получить в результате время начала соответствующих сегментов, вы должны отфильтровать ненужные сегменты, а затем выбрать время, так что фильтр будет применяться к предыдущему шагу и путь окажется другим:
$.track.segments[*] ? (@.HR > 130)."start time"
Можно также использовать несколько выражений фильтра по очереди, когда это требуется. Например, следующее выражение выбирает время начала всех сегментов с определёнными координатами и высоким показателем пульса:
$.track.segments[*] ? (@.location[1] < 13.4) ? (@.HR > 130)."start time"
Также возможно использовать фильтры на разных уровнях вложенности. В следующем примере сначала сегменты фильтруются по координатам, а затем для подходящих сегментов, если они находятся, выбираются значения высокого пульса:
$.track.segments[*] ? (@.location[1] < 13.4).HR ? (@ > 130)
Можно также вкладывать выражения фильтра одно в другое:
$.track ? (exists(@.segments[*] ? (@.HR > 130))).segments.size()
Данное выражение возвращает количество сегментов в треке, если он содержит сегменты с высокими показателями пульса, или пустую последовательность, если таких сегментов нет.
Реализация языка путей SQL/JSON в Postgres Pro имеет следующие отличия от стандарта SQL/JSON:
- Выражение пути может быть булевым предикатом, хотя стандарт SQL/JSON допускает предикаты только в фильтрах. Это необходимо для реализации оператора - @@. Например, следующее выражение- jsonpathдопускается в Postgres Pro:- $.track.segments[*].HR < 70 
- Есть небольшие различия в интерпретации шаблонов регулярных выражений, используемых в фильтрах - like_regex; имеющиеся особенности описаны в Подразделе 9.16.2.3.
9.16.2.1. Строгий и нестрогий режимы
Когда вы обращаетесь к данным JSON, выражение пути может не соответствовать фактической структуре данных JSON. Попытка обратиться к несуществующему члену объекта или элементу массива приводит к ошибке структурного типа. Для обработки такого рода ошибок в выражениях путей SQL/JSON предусмотрены два режима:
- lax (по умолчанию) — нестрогий режим, в котором обработчик путей неявно адаптирует обрабатываемые данные к указанному пути. Любые возникающие структурные ошибки подавляются и заменяются пустыми последовательностями SQL/JSON. 
- strict — строгий режим, в котором структурные ошибки выдаются как есть. 
Нестрогий режим упрощает сопоставление структуры документа JSON с выражением пути в случаях, когда данные JSON не соответствуют ожидаемой схеме. Если операнд не удовлетворяет требованиям определённой операции, он может перед выполнением этой операции автоматически оборачиваться в массив SQL/JSON или наоборот, разворачиваться так, чтобы его элементы образовали последовательность SQL/JSON. Помимо этого, в нестрогом режиме операторы сравнения автоматически разворачивают свои операнды, что позволяет легко сравнивать массивы SQL/JSON. Массив с одним элементом в таком режиме считается равным своему элементу. Автоматическое разворачивание не выполняется только в следующих случаях:
- В выражении пути фигурируют методы - size()и- type(), возвращающие соответственно число элементов в массиве и тип.
- Обрабатываемые данные JSON содержат вложенные массивы. В этом случае разворачивается только массив верхнего уровня, а внутренние массивы остаются без изменений. Таким образом, неявное разворачивание может опускаться на каждом шаге вычисления пути только на один уровень. 
Например, обрабатывая данные GPS, показанные выше, в нестрогом режиме можно не обращать внимание на то, что в них содержится массив сегментов:
lax $.track.segments.location
В строгом режиме указанный путь должен в точности соответствовать структуре обрабатываемого документа JSON и выдавать элемент SQL/JSON, поэтому использование такого выражения пути приведёт к ошибке. Чтобы получить такой же результат, как в нестрогом режиме, необходимо явно развернуть массив segments: 
strict $.track.segments[*].location
Оператор обращения .** в нестрогом режиме может выдавать несколько неожиданные результаты. Например, следующий запрос выберет каждое значение HR дважды: 
lax $.**.HR
 Это происходит потому, что оператор .** выбирает и массив segments, и каждый из его элементов, а обращение .HR в нестрогом режиме автоматически разворачивает массивы. Во избежание подобных сюрпризов мы рекомендуем использовать оператор обращения .** только в строгом режиме. Следующий запрос выбирает каждое значение HR в единственном экземпляре: 
strict $.**.HR
9.16.2.2. Операторы и методы SQL/JSON
В Таблице 9.48 показаны операторы и методы, поддерживаемые в значениях jsonpath. Заметьте, что унарные операторы и методы могут применяться к множеству значений, полученных на предыдущем шаге пути, тогда как бинарные операторы (сложение и т. п.) применяются только к отдельным значениям.
Таблица 9.48. Операторы и методы jsonpath
| Оператор/Метод Описание Пример(ы) | 
|---|
| 
 Сложение 
 | 
| 
 Унарный плюс (нет операции); в отличие от сложения, он может итерационно применяться к множеству значений 
 | 
| 
 Вычитание 
 | 
| 
 Смена знака; в отличие от вычитания, этот оператор может итерационно применяться к множеству значений 
 | 
| 
 Умножение 
 | 
| 
 Деление 
 | 
| 
 Остаток от деления 
 | 
| 
 Тип элемента JSON (см.  
 | 
| 
 Размер элемента JSON (число элементов в массиве либо 1, если это не массив) 
 | 
| 
 Приблизительное число с плавающей точкой, преобразованное из строки или числа JSON 
 | 
| 
 Ближайшее целое, большее или равное заданному числу 
 | 
| 
 Ближайшее целое, меньшее или равное заданному числу 
 | 
| 
 Модуль заданного числа (абсолютное значение) 
 | 
| 
 Значение даты/времени, полученное из строки 
 | 
| 
 Значение даты/времени, преобразованное из строки по шаблону  
 | 
| 
 Пары ключ-значение, представленные в виде массива объектов со следующими тремя полями:  
 | 
Примечание
Результирующим типом методов datetime() и datetime( может быть шаблон)date, timetz, time, timestamptz или timestamp. Эти два метода определяют тип своего результата автоматически.
Метод datetime() пытается последовательно сопоставить поступившую на вход строку с ISO-форматами типов date, timetz, time, timestamptz и timestamp. Встретив первый подходящий формат, он останавливается и возвращает соответствующий тип данных.
Метод datetime( определяет результирующий тип в соответствии с полями заданного шаблона.шаблон)
Методы datetime() и datetime( применяют те же правила разбора строки, что и SQL-функция шаблон)to_timestamp (см. Раздел 9.8), но с тремя исключениями. Во-первых, эти методы не позволяют использовать в шаблоне поля, которым не находится соответствие. Во-вторых, в шаблоне допускаются только следующие разделители: знак минуса, точка, косая черта, запятая, апостроф, точка с запятой, запятая и пробел. В-третьих, разделители в шаблоне должны в точности соответствовать входной строке.
Если требуется сравнить значения разных типов даты/времени, применяется неявное приведение типа. Значение date может быть приведено к типу timestamp или timestamptz; timestamp — к типу timestamptz, а time — к timetz. Однако все эти приведения, кроме первого, зависят от текущего значения TimeZone и поэтому не могут выполняться в функциях jsonpath, не учитывающих часовой пояс.
В Таблице 9.49 перечислены допустимые элементы выражения фильтра.
Таблица 9.49. Элементы выражения фильтра jsonpath
| Предикат/значение Описание Пример(ы) | 
|---|
| 
 Проверка равенства (все операторы сравнения, включая этот, работают с любыми скалярными значениями JSON) 
 
 | 
| 
 
 Проверка неравенства 
 
 | 
| 
 Проверка «меньше» 
 | 
| 
 Проверка «меньше или равно» 
 | 
| 
 Проверка «больше» 
 | 
| 
 Проверка «больше или равно» 
 | 
| 
 JSON-константа  
 | 
| 
 JSON-константа  
 | 
| 
 JSON-константа  
 | 
| 
 Логическое И 
 | 
| 
 Логическое ИЛИ 
 | 
| 
 Логическое НЕ 
 | 
| 
 Проверяет, является ли  
 | 
| 
 Проверяет, соответствует ли первый операнд регулярному выражению, которое задаёт второй операнд с необязательным аргументом  
 
 | 
| 
 Проверяет, является ли второй операнд начальной подстрокой первого. 
 | 
| 
 Проверяет, соответствует ли выражению пути минимум один элемент SQL/JSON. Возвращает  
 
 | 
9.16.2.3. Регулярные выражения SQL/JSON
Выражения путей SQL/JSON могут содержать фильтры like_regex, позволяющие сопоставлять текст с регулярным выражением. Например, следующий запрос пути SQL/JSON выберет все строки в массиве, которые начинаются с английской гласной в любом регистре: 
$[*] ? (@ like_regex "^[aeiou]" flag "i")
Необязательная строка flag может содержать один или несколько следующих символов: i, делающий поиск регистронезависимым, m, допускающий сопоставление ^ и $ с переводами строк, s, допускающий сопоставление . с символом новой строки, и q, берущий в кавычки весь шаблон (в результате производится простой поиск подстроки).
Стандарт SQL/JSON заимствует определение регулярных выражений от оператора LIKE_REGEX, который, в свою очередь, реализуется по стандарту XQuery. Однако в Postgres Pro оператор LIKE_REGEX в настоящее время отсутствует. Поэтому фильтр like_regex реализован с использованием механизма регулярных выражений POSIX, который описан в Подразделе 9.7.3. Вследствие этого наблюдается ряд небольших отклонений от описанного в стандарте поведения SQL/JSON, о которых рассказывается в Подразделе 9.7.3.8. Заметьте однако, что описанная там несовместимость букв флагов не проявляется на уровне SQL/JSON, так как заданные в SQL/JSON флаги XQuery переводятся во флаги, воспринимаемые механизмом POSIX.
Помните, что аргумент, задающий шаблон для like_regex, является строкой пути JSON и записывается по правилам, описанным в Подразделе 8.14.6. Это в частности означает, что каждую косую черту в регулярном выражении надо дублировать. Например, чтобы отобрать строковые значения, находящиеся на уровне корня документа и содержащие только цифры, нужно написать: 
$.* ? (@ like_regex "^\\d+$")