Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatten aggregated key/value pairs from a JSONB field?

I am working in Postgres 9.4 with the following table:

     Column      │         Type         │ Modifiers
─────────────────┼──────────────────────┼──────────────────────
 id              │ integer              │ not null default
 practice_id     │ character varying(6) │ not null
 date            │ date                 │ not null
 pct_id          │ character varying(3) │
 total_list_size │ double precision     │ not null
 star_pu         │ jsonb                │

I have the following query:

SELECT date,
       AVG(total_list_size) AS total_list_size,
       json_object_agg(key, val) AS star_pu
FROM (SELECT date,
             SUM(total_list_size) AS total_list_size,
             key, SUM(value::numeric) val FROM frontend_practicelist p,
             jsonb_each_text(star_pu)
       GROUP BY date, key ) p
GROUP BY date
ORDER BY date;

It gives me results with a JSON object attached to star_pu:

date            │ 2013-04-01
total_list_size │ 56025123.000000000000
star_pu         │ { "antidepressants_cost" : 180102416.8036909901975399, "antiepileptic_drugs_cost" : 296228344.171576079922216... }

Instead I would like to flatten the JSON result to a series of namespaced keys, so the result looks like this:

date                             │ 2013-04-01
total_list_size                  │ 56025123.000000000000
star_pu.antidepressants_cost     │ 180102416.8036909901975399
star_pu.antiepileptic_drugs_cost │ 296228344.171576079922216 
...

Is this possible?

like image 563
Richard Avatar asked Feb 03 '16 13:02

Richard


People also ask

Is Jsonb faster than JSON?

Json processes input faster than jsonb as there is no conversion involved in this. Jsonb converts the JSON data into the binary form so it has slightly slower input due to the binary conversion overhead.

Is Jsonb efficient?

Most applications should use JSONB for schemaless data. It stores parsed JSON in a binary format, so queries are efficient.

What is Jsonb in SQL?

The JSONB data type stores JSON (JavaScript Object Notation) data as a binary representation of the JSONB value, which eliminates whitespace, duplicate keys, and key ordering. JSONB supports GIN indexes.

Which index type is appropriate for column of type Jsonb?

As a result, in the choice of an index for JSONB type, if the index searches a few predetermined attributes, BTREE is the best choice, however, if the index searches arbitrary attributes prefer the GIN index.


1 Answers

This particular case

The function below dynamically creates a view based on a table:

create or replace function create_totals_view(table_name text)
returns void language plpgsql as $$
declare
    s text;
begin
    execute format ($fmt$
        select string_agg(format('star_pu->>''%s'' "%s"', key, key), ',')
        from (
            select distinct key
            from %s, json_each(star_pu)
            order by 1
            ) s;
        $fmt$, '%s', '%s', table_name)
    into s;
    execute format('
        drop view if exists %s_view;
        create view %s_view as 
        select date, total_list_size, %s from %s', 
        table_name, table_name, s, table_name);
end $$;

First, create a table from your query.

create table totals as

    SELECT date,
           AVG(total_list_size) AS total_list_size,
           json_object_agg(key, val) AS star_pu
    FROM (SELECT date,
                 SUM(total_list_size) AS total_list_size,
                 key, SUM(value::numeric) val FROM frontend_practicelist p,
                 jsonb_each_text(star_pu)
           GROUP BY date, key ) p
    GROUP BY date
    ORDER BY date;

Next, use the function, which will create a view named after the table with _view postfix:

select create_totals_view('totals');

Finally, query the view:

select * from totals_view;

Generalized solution (for jsonb)

create or replace function create_jsonb_flat_view
    (table_name text, regular_columns text, json_column text)
    returns text language plpgsql as $$
declare
    cols text;
begin
    execute format ($ex$
        select string_agg(format('%2$s->>%%1$L "%%1$s"', key), ', ')
        from (
            select distinct key
            from %1$s, jsonb_each(%2$s)
            order by 1
            ) s;
        $ex$, table_name, json_column)
    into cols;
    execute format($ex$
        drop view if exists %1$s_view;
        create view %1$s_view as 
        select %2$s, %3$s from %1$s
        $ex$, table_name, regular_columns, cols);
    return cols;
end $$;

Usage:

create table example (id int, name text, params jsonb);
insert into example values
(1, 'Anna', '{"height": 175, "weight": 55}'),
(2, 'Bob', '{"age": 22, "height": 188}'),
(3, 'Cindy', '{"age": 25, "weight": 48, "pretty": true}');

select create_jsonb_flat_view('example', 'id, name', 'params');

select * from example_view;

 id | name  | age | height | pretty | weight 
----+-------+-----+--------+--------+--------
  1 | Anna  |     | 175    |        | 55
  2 | Bob   | 22  | 188    |        | 
  3 | Cindy | 25  |        | true   | 48
(3 rows)
like image 72
klin Avatar answered Oct 11 '22 20:10

klin