Рекурсивный JSONB postgres
Я пытаюсь создать рекурсивный CTE в Postgres, который поддерживает как массивы, так и объекты, чтобы вернуть список пар ключ-значение и, похоже, не смог найти хороший пример. Это мой текущий код.
with recursive jsonRecurse as
(
select
j.key as Path
,j.key
,j.value
from jsonb_each(to_jsonb('{
"key1": {
"key2": [
{
"key3": "test3",
"key4": "test4"
}
]
},
"key5": [
{
"key6":
[
{
"key7": "test7"
}
]
}
]
}'::jsonb)) j
union all
select
jr.path || '.' || jr2.Key
,jr2.key
,jr2.value
from jsonRecurse jr
left join lateral jsonb_each(jr.value) jr2 on true
where jsonb_typeof(jr.value) = 'object'
)
select
*
from jsonRecurse;
Как вы можете видеть, код перестает повторяться, как только я попадаю в массив вместо объекта. Я попытался поэкспериментировать с использованием оператора case и поместить вместо него вызов функции jsonb_each или jsonb_array_element в оператор case, но я получаю сообщение об ошибке, сообщающее мне использовать вместо этого боковые объединения.
1 ответ
Я использовал этот пример таблицы, чтобы сделать запрос более читабельным:
create table my_table(id serial primary key, jdata jsonb);
insert into my_table (jdata) values
('{
"key1": {
"key2": [
{
"key3": "test3",
"key4": "test4"
}
]
},
"key5": [
{
"key6":
[
{
"key7": "test7"
}
]
}
]
}');
Вы должны присоединиться к обоим jsonb_each(value)
а также jsonb_array_elements(value)
условно, в зависимости от типа value
:
with recursive extract_all as
(
select
key as path,
value
from my_table
cross join lateral jsonb_each(jdata)
union all
select
path || '.' || coalesce(obj_key, (arr_key- 1)::text),
coalesce(obj_value, arr_value)
from extract_all
left join lateral
jsonb_each(case jsonb_typeof(value) when 'object' then value end)
as o(obj_key, obj_value)
on jsonb_typeof(value) = 'object'
left join lateral
jsonb_array_elements(case jsonb_typeof(value) when 'array' then value end)
with ordinality as a(arr_value, arr_key)
on jsonb_typeof(value) = 'array'
where obj_key is not null or arr_key is not null
)
select *
from extract_all;
Выход:
path | value
--------------------+------------------------------------------------
key1 | {"key2": [{"key3": "test3", "key4": "test4"}]}
key5 | [{"key6": [{"key7": "test7"}]}]
key1.key2 | [{"key3": "test3", "key4": "test4"}]
key5.0 | {"key6": [{"key7": "test7"}]}
key1.key2.0 | {"key3": "test3", "key4": "test4"}
key5.0.key6 | [{"key7": "test7"}]
key1.key2.0.key3 | "test3"
key1.key2.0.key4 | "test4"
key5.0.key6.0 | {"key7": "test7"}
key5.0.key6.0.key7 | "test7"
(10 rows)
Элементы массивов json не имеют ключей, мы должны использовать их индексы для построения пути. Поэтому функция jsonb_array_elements()
следует называть с порядком. Согласно документации (см. 7.2.1.4. Табличные функции):
Если указано предложение WITH ORDINALITY, в столбцы результата функции будет добавлен дополнительный столбец типа bigint. В этом столбце нумеруются строки набора результатов функции, начиная с 1.
Вызов функции
jsonb_array_elements(case jsonb_typeof(value) when 'array' then value end)
with ordinality as a(arr_value, arr_key)
возвращает пары (value, ordinality)
с псевдонимом как (arr_value, arr_key)
,