3

In postgres 9.6 I have a table that includes these 3 columns.

id | val1 | val2
------------------
1  | x    | 1
1  | x    | 2
1  | x    | 3
1  | y    | 4
2  | y    | 1
2  | y    | 2

Can I use aggregate functions to turn it into this?

id | 
------------------------------
1  | { x: [1, 2, 3], y: [4] }
2  | { y: [1, 2] }

Or if what I'm looking for isn't possible, something like below could work too. Or anything that would allow me to transform the results into the above in my application code.

id | 
------------------------------
1  | [ {x: 1}, {x: 2}, {x: 3}, {y: 4} ]
2  | [ {y: 1}, {y: 2} ]

I know I can do something like select id, array_agg(val2) from mytable group by val2, but that only groups by val2 and returns something like

id | 
------------------
1  | [1, 2, 3, 4]
2  | [1, 2]
1
  • Did any of the answer solve your problem? You haven't commented on any. Commented Jul 15, 2017 at 23:05

4 Answers 4

5

You can do it with:

SELECT
    id, json_object_agg(val1, aaa)
FROM
    (
    SELECT
        id, val1, json_agg(val2) AS aaa
    FROM
        t
    GROUP BY
        id, val1
    ) AS q 
GROUP BY
    id;
id | json_object_agg               
-: | :-----------------------------
 1 | { "x" : [1, 2, 3], "y" : [4] }
 2 | { "y" : [1, 2] }              

You can find the needed definitions to produce this result at dbfiddle here

You'll be using these two functions:

  • json_agg
  • json_object_agg

The documentation can be found at 9.20. Aggregate Functions in PostgreSQL doc

Sign up to request clarification or add additional context in comments.

Comments

4

Here is one way using jsonb_agg and jsonb_object_agg functions

with the_table(id , val1 , val2) as(
    select 1 ,'x',1 union all
    select 1  ,'x', 2 union all
    select 1  ,'x', 3 union all
    select 1  ,'y', 4 union all
    select 2 ,'y', 1 union all
    select 2 ,'y', 2
)


select id, jsonb_object_agg(val1, arr) from (
    select id, val1, jsonb_agg(val2) as arr
    from the_table
    group by id, val1
)t
group by id

Comments

1

And yet another useful piece of code inspired by SO question:

drop aggregate if exists jsonb_objarr_agg(text, anyelement);
drop function if exists jsonb_objarr_agg_func(jsonb, text, anyelement);
create function jsonb_objarr_agg_func(jsonb, text, anyelement) returns jsonb immutable language sql as $$
  select
    jsonb_set(
      $1,
      array[$2],
      coalesce(
        case
          when jsonb_typeof($1->$2) <> 'array' then to_jsonb(array[$1->$2])
          else $1->$2
        end, 
        '[]'::jsonb) || to_jsonb($3), true);
$$;

select jsonb_objarr_agg_func('{"a":1}', 'a', 2);

create aggregate jsonb_objarr_agg(text, anyelement) (
  sfunc = jsonb_objarr_agg_func,
  stype = jsonb,
  initcond = '{}');

with t(i, v1, v2) as (values
    (1, 'x', 1),
    (1, 'x', 2),
    (1, 'x', 3),
    (1, 'y', 4),
    (2, 'y', 1),
    (2, 'y', 2))
select
  i,
  jsonb_objarr_agg(v1, v2)
from t
group by i;
╔═══╤════════════════════════════╗
║ i │      jsonb_objarr_agg      ║
╠═══╪════════════════════════════╣
║ 1 │ {"x": [1, 2, 3], "y": [4]} ║
║ 2 │ {"y": [1, 2]}              ║
╚═══╧════════════════════════════╝

This aggregate allows to make jsonb with first parameter as keys and also aggregate the second parameter into arrays.

If you need the json type instead then you can easily replace jsonb to json in this code.

live demo

Comments

0

I think the following does what you want:

select id, json_agg(json_build_object(val1, val2s ))
from (select id, val1, array_agg(val2) as val2s
      from t
      group by id, val1
     ) t
group by id;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.