Search code examples
sqljsonpostgresqljsonb

In PostgreSQL 9.6, what's the simplest way to expand a JSONB column filled with simple JSON dicts?


Say, I have a table json_table with a JSONB column, json_field. Each element in this column is a single uncomplicated dict, e.g.,

{'first_field': 2 , 'second_field': 42}

Is there a way to create a new table were the dicts are turned into columns?

My current approach is as follows:

CREATE TABLE normal_table ... first_field, second_field ... etc;
INSERT INTO normal_table (
    id,
    first_field,
    second_field,
    ...
)
SELECT
    id,
    json_field->>'first_field',
    json_field->>'second_field',
    ...
FROM json_table;

Is there a way to do something like the following?

SELECT
    id,
    expand_json_dict(json_field)
FROM json_table;

Or a similar succinct way of doing it? The JSONB column has a lot of fields I want to expand, and the queries become unwieldy. I've actually made a Python function which generates create/insert scripts. Still, I'd love for there to be a nice PostgreSQL way to do it.

Any thoughts?

Edit

The following is the working solution based on feedback here. Thanks guys.

drop table if exists json_table;
create table json_table (
    id int,
    json_field jsonb
);
insert into json_table VALUES
    (2, ('{"second_field": 43, "first_field": 3}'::jsonb)),
    (1, ('{"first_field": 2 , "second_field": 42}'::jsonb));

drop table if exists normal_table;
create table normal_table (
    id int,
    first_field int,
    second_field int
);

insert into normal_table
select (
    jsonb_populate_record(
        null::normal_table,
        jsonb_set(json_field, '{id}', id::text::jsonb)
    )
).*
from json_table;

select * from normal_table;

Solution

  • Use the normal_table type as the base type to the jsonb_populate_record function:

    create table normal_table (
        id int,
        first_field int,
        second_field int
    );
    
    with json_table (json_field) as ( values
        ('{"first_field": 2 , "second_field": 42}'::jsonb)
    )
    select (jsonb_populate_record(null::normal_table, json_field)).*
    from json_table
    ;
     id | first_field | second_field                                                                                                                                          
    ----+-------------+--------------                                                                                                                                         
        |           2 |           42
    

    If it is necessary to generate the id to be inserted use jsonb_set:

    with json_table (json_field) as ( values
        ('{"first_field": 2 , "second_field": 42}'::jsonb),
        ('{"first_field": 5 , "second_field": 1}')
    )
    select (
        jsonb_populate_record(
            null::normal_table,
            jsonb_set(json_field, '{id}', (row_number() over())::text::jsonb)
        )
    ).*
    from json_table
    ;
     id | first_field | second_field 
    ----+-------------+--------------
      1 |           2 |           42
      2 |           5 |            1