Recursive JSONB postgres

假如想象 提交于 2019-12-11 08:43:26

问题


I am trying to build a recursive CTE in Postgres that supports both arrays and objects, to return a list of key-value pairs and don't seem to be able to find a good example. This is my current code.

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;

As you can see the code stops recursing as soon as I hit an array instead of an object. I've tried playing around with using a case statement and putting the function call to jsonb_each or jsonb_array_element in the case statement instead but I get an error telling me to use lateral joins instead.


回答1:


I have used this example table to make the query more readable:

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"
                }
            ]
        }
    ]
}');

You have to join both jsonb_each(value) and jsonb_array_elements(value) conditionally, depending on the type of 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;

Output:

        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)

Elements of json arrays have no keys, we should use their indexes to build a path. Therefore the function jsonb_array_elements() should be called with ordinality. Per the documentation (see 7.2.1.4. Table Functions):

If the WITH ORDINALITY clause is specified, an additional column of type bigint will be added to the function result columns. This column numbers the rows of the function result set, starting from 1.

The function call

jsonb_array_elements(case jsonb_typeof(value) when 'array' then value end) 
with ordinality as a(arr_value, arr_key)

returns pairs (value, ordinality) aliased as (arr_value, arr_key).



来源:https://stackoverflow.com/questions/50598286/recursive-jsonb-postgres

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!