Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

I am using array_to_json in combination with array_agg to format certain results in PostgreSQL as JSON. This works fine for queries in which I want to return the default value of a query (all columns, unmodified). But I'm stumped on how I could use array_agg to create a JSON object for a query in which I want to modify some of the output.

Here's an example. Let's say we have the following:

CREATE TABLE temp_user ( 
   user_id  serial PRIMARY KEY,
   real_name text
);
CREATE TABLE temp_user_ip (
   user_id  integer,
   ip_address text
);
INSERT INTO temp_user (user_id, real_name) VALUES (1, 'Elise'),  (2, 'John'), (3, NULL);
INSERT INTO temp_user_ip (user_id, ip_address) VALUES (1, '10.0.0.4'),  (2, '10.0.0.7'), (3, '10.0.0.9');

The following query works fine:

# SELECT array_to_json(array_agg(temp_user)) as users from temp_user;
                                            users                                                
-----------------------------------------------------------------------------------------------------
 [{"user_id":1,"real_name":"Elise"},{"user_id":2,"real_name":"John"},{"user_id":3,"real_name":null}]

But let's say that I don't like the null value appearing for user 3. I'd rather see the string "User logged in from $ip" instead.

I can do this:

# SELECT user_id, (CASE WHEN real_name IS NULL THEN (select 'User logged in from ' || ip_address FROM temp_user_ip WHERE user_id = temp_user.user_id) ELSE real_name END) as name from temp_user;

And I get the following results:

 user_id |             name             
---------+------------------------------
       1 | Elise
       2 | John
       3 | User logged in from 10.0.0.9

Which is great. But I can't figure out how to manipulate this data into JSON format like the first example.

The desired output is of course the following:

[{"user_id":1,"name":"Elise"},{"user_id":2,"name":"John"},{"user_id":3,"name":"User logged in from 10.0.0.9"}]

The following doesn't work:

# select array_to_json(array_agg ( (SELECT user_id, (CASE WHEN real_name IS NULL THEN (select 'User logged in from ' || ip_address FROM temp_user_ip WHERE user_id = temp_user.user_id) ELSE real_name END) as name from temp_user)));
ERROR:  subquery must return only one column

I can't figure out any way to get the data into a format that array_agg accepts. I even tried creating a custom type which matched the format of temp_user and trying to array_agg calls to the type constructor, which returned the same error. The error doesn't make sense to me - if the subquery is aggregated, then it shouldn't matter if it returns more than one column. Any advice?

share|improve this question
up vote 5 down vote accepted

You can separate the aggregate call from the subquery and use the row constructor for generating the compound data:

SELECT 
    array_to_json(array_agg(row(t.*))) AS users 
FROM 
    (
        SELECT user_id, 
            CASE 
                WHEN real_name IS NULL 
                THEN (
                    SELECT 'User logged in from ' || ip_address 
                    FROM temp_user_ip 
                    WHERE user_id = temp_user.user_id
                ) ELSE real_name 
            END AS name 
        FROM temp_user
    ) t
;

You can also check this on SQLFiddle.

share|improve this answer
    
I can confirm this works; I've just applied this method successfully to my real query. Thanks! – hs0 Dec 11 '12 at 15:46
2  
The query can be simplified a little bit by replacing array_agg(row(t.*)) by simply array_agg(t). That way you also retain the column names from the subquery. – Jörn Horstmann Jul 13 '13 at 17:10

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.