递归 JSONB postgres

Recursive JSONB postgres

我正在尝试在 Postgres 中构建一个同时支持数组和对象的递归 CTE,到 return 键值对列表,但似乎找不到很好的例子。这是我当前的代码。

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 语句并将函数调用放在 case 语句中 jsonb_each 或 jsonb_array_element,但我收到一条错误消息,告诉我改用横向连接。

我使用这个例子 table 使查询更具可读性:

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() 应该按顺序调用。根据 the documentation(参见 7.2.1.4。Table 函数 ):

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.

函数调用

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

returns 对 (value, ordinality) 别名为 (arr_value, arr_key).