Как запросить глубоко вложенные данные JSON в PSQL

Как запросить глубоко вложенные данные JSON в PSQL

28 марта 2023 г.

Недавно мне пришлось написать скрипт, который должен был изменить некоторую структуру данных JSON в базе данных PSQL. По сути, мне приходилось анализировать одно значение и помещать его в другое поле. Поскольку у меня не было большого опыта работы с SQL или PSQL в этом отношении, чтение и понимание документации для моих конкретных случаев использования заняло слишком много времени.

У меня был сложный и глубоко вложенный JSON, в котором было не очень понятно, как получить доступ к некоторой информации.

В этой статье я хочу задокументировать некоторые вещи, которые я узнал.

Для начала мне нужно место, где я могу возиться с базами данных, и по счастливой случайности я нашел dbfiddle. Вы можете ознакомиться с запросами из этой статьи здесь.

Сначала давайте начнем с создания таблицы и указания, что нам нужна база данных PSQL. И сразу добавим некоторые данные, с которыми мы сможем работать позже.

CREATE TABLE jsonb_exp (
  id serial primary key,
  data jsonb
);

INSERT INTO jsonb_exp (data) VALUES 
  ('{"data":{"1":{"items":[{"name":"John"},{"name":"Jack"}]},"2":{"items":[{"name":"John Dorian"},{"name":"Jack Sparrow"}]},"3":{"items":[{"name":"Tom Cruise"},{"name":"somerandomtext"}]},"5":{"items":[{"name":"Tom Cruise"}]}},"property":"Some string"}'),
  ('{"data":{"4":{"items":[{"name":"Maria"},{"name":"Jack"}]},"6":{"items":[{"name":"Jack Jackson"},{"name":"Thomas ---"}]},"7":{"items":[{"name":"-"},{"name":"somerandomtext"}]},"15":{"items":[{"name":"hello"}]}},"property":"Some string"}'),
  ('{"a": "b", "c": [1, 2, 3]}'

Обычно у вас не было бы другой структуры внутри вашего JSON для разных записей, но это только для тестов. Теперь мы можем выбрать и посмотреть, что мы создали в формате таблицы.

SELECT * from jsonb_exp;

Во-первых, давайте разберемся с основами.

-> returns jsonb
->> returns text

Не знаете, с каким типом вы работаете, вы можете использовать pg_typeof.

SELECT data->>'data' as data, pg_typeof(data->'data'), pg_typeof(data->>'data') from jsonb_exp where data->>'data' IS NOT NULL;

Запрос из глубоко вложенного JSONB

В этом сценарии, как вы можете видеть в приведенном выше примере: у нас есть объект, он имеет определенную структуру, но некоторые ключи похожи на индексы массива. Не обычное дело, наверное. Но бывает.

На помощь приходит следующий метод:

jsonb_path_query()

Он есть в документации, но мне не сразу было понятно, как он работает. В качестве первого аргумента вы передаете объект, затем вы можете передать запрос. Допустим, нам нужно запросить определенные элементы из объекта, из свойства «элементы» и только те, которые содержат «Джек».

SELECT id, data, item FROM jsonb_exp t, jsonb_path_query(data->'data', '$.*.items[*]') as item
 WHERE data->'data' IS NOT NULL AND item->>'name'='Jack';

Распаковка. Давайте сначала посмотрим на запрос jsonb_path_query, все остальное — это просто поддержка вокруг него, чтобы продемонстрировать, что мы сделали.

Чтение ‘$..items[]’, вот как это можно прочитать. $ — сам объект, он может содержать ключ, которого мы не знаем — помечаем звездочкой. Внутри этого мы хотим найти элементы свойств, которые являются массивом, и с помощью [*] мы говорим, что нам нужен каждый элемент внутри этого массива.

Запятая в запросе — это просто неявное перекрестное соединение, не имеющее отношения к методам JSONB.

Удалите пункт «ГДЕ» и сразу отфильтруйте

Вот код, вы можете добавить условия внутри запроса.

SELECT id, data, jsonb_path_query(data->'data', '$.*.items[*] ? (@.name == $name)', '{"name": "John"}') FROM jsonb_exp
 WHERE data->'data' IS NOT NULL;

Как вы можете видеть здесь, вы даже можете добавить третий параметр, в котором вы можете хранить переменные, которые будут использоваться в запросе. Это просто для демонстрационных целей, вы можете полностью удалить его и сравнить со строкой без использования дополнительных переменных.

Обратите внимание, здесь вы используете двойные знаки равенства, а не один, как за пределами этого запроса.

Вы также не можете просто написать LIKE в качестве оператора сравнения. Но вы можете использовать like_regex. Допустим, мы хотим найти каждый элемент, имя которого начинается с «Джон».

SELECT id, data, jsonb_path_query(data->'data', '$.*.items[*] ? (@.name like_regex "^John")') FROM jsonb_exp
 WHERE data->'data' IS NOT NULL;

Надеюсь, это было полезно.


Главное изображение Люка Чессера на Unsplash


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE