74

I would like to aggregate two columns into one "array" when grouping.

Assume a table like so:

friends_map:
=================================
user_id    friend_id    confirmed
=================================
1          2            true
1          3            false
2          1            true
2          3            true
1          4            false

I would like to select from this table and group by user_id and get friend_id and confirmed as a concatenated value separated by a comma.

Currently I have this:

SELECT user_id, array_agg(friend_id) as friends, array_agg(confirmed) as confirmed
FROM friend_map
WHERE user_id = 1
GROUP BY user_id

which gets me:

=================================
user_id    friends      confirmed
=================================
1         [2,3,4]       [t, f, f]

How can I get:

=================================
user_id    friends     
=================================
1         [ [2,t], [3,f], [4,f] ]
2
  • 1
    In a sense, that's what you had when you started. :) Commented Dec 8, 2015 at 18:29
  • But what I'm looking for is grouping under one variable name. This is just an example, it's really part of a larger join query, which contains more tables and data. Commented Dec 8, 2015 at 18:30

5 Answers 5

90

You could avoid the ugliness of the multidimentional array and use some json which supports mixed datatypes:

SELECT user_id, json_agg(json_build_array(friend_id, confirmed)) AS friends 
    FROM friends_map 
    WHERE user_id = 1
    GROUP BY user_id

Or use some key : value pairs since json allows that, so your output will be more semantic if you like:

SELECT user_id, json_agg(json_build_object(
        'friend_id', friend_id, 
        'confirmed', confirmed
    )) AS friends 
    FROM friends_map 
    WHERE user_id = 1
    GROUP BY user_id;
Sign up to request clarification or add additional context in comments.

3 Comments

This is definitely the right answer, you get nice result as json (which is probably what we all want anyways), and it looks like [[1990,"pm25"],[1995,"pm25"],[2000,"pm25"]]
This is absolutely beautiful - and as @chrismarx said, it's exactly what I wanted my end result to be. (I assumed I would have to process outside of postgres after query)
This is the right answer; The goal here is to create a nested array, and this does exactly that. As reflected in the other conversations, nested arrays of different types is not allowed.
75
SELECT user_id, array_agg((friend_id, confirmed)) as friends
FROM friend_map
WHERE user_id = 1
GROUP BY user_id

user_id |           array_agg            
--------+--------------------------------
      1 | {"(2,true)","(3,false)","(4,false)"}

Comments

37

You can concatenate the values together prior to feeding them into the array_agg() function:

SELECT user_id, array_agg('[' || friend_id || ',' || confirmed || ']') as friends
FROM friends_map
WHERE user_id = 1
GROUP BY user_id

Demo: SQL Fiddle

4 Comments

Note this is building strings like '[2,t]', not nested arrays. Actual Postgres arrays cannot contain values of different types (like integer and boolean).
@DanielLyons Good point, I'm not accustomed to thinking about storing nested arrays like this in an rdbms so was thinking strings.
It's alright; there's no right way to do the wrong thing. :)
Thanks, this would probably do well, the end result goes to javascript, which can have different types in an array. The other way for me to do this is to convert the type of confirmed to an int (0 and 1) for representing false and true, then types would be the same.
21

In Postgres 9.5 you can obtain array of arrays of text:

SELECT user_id, array_agg(array[friend_id::text, confirmed::text])
FROM friend_map
WHERE user_id = 1
GROUP BY user_id;

 user_id |           array_agg            
---------+--------------------------------
       1 | {{2,true},{3,false},{4,false}}
(1 row)

or array of arrays of int:

SELECT user_id, array_agg(array[friend_id, confirmed::int])
FROM friend_map
WHERE user_id = 1
GROUP BY user_id;

 user_id |      array_agg      
---------+---------------------
       1 | {{2,1},{3,0},{4,0}}
(1 row) 

3 Comments

Just question, what version are you using? array_agg isn't supposed to aggregate text[] or integer[]...
Ops! PostgreSQL 9.5beta2
Aha! You got me there, I was wondering what kind of sorcery was that :) 9.5 isn't released yet, so I assume the OP is looking for some production-ready solution.
0

You can use: array[]

Just do that:

SELECT user_id, array[friend_id, confirmed]
FROM friend_map
WHERE user_id = 1
GROUP BY 1;

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.