1

I have this database schema:

drop table if exists demo_contact cascade;
create table demo_contact (id serial primary key);
insert into demo_contact values (1);
insert into demo_contact values (2);
insert into demo_contact values (3);

drop table if exists demo_contact_custom_field cascade;
create table demo_contact_custom_field (custom_field_id text, contact_id numeric, value text);
insert into demo_contact_custom_field values ('7759512f-662f-4139-94fb-8b708c5d11eb', 1, '3232');
insert into demo_contact_custom_field values ('a96993bf-eb38-446c-a5a7-416485e8b933', 1, 'true');
insert into demo_contact_custom_field values ('a96993bf-eb38-446c-a5a7-416485e8b933', 2, 'true');

How can I produce this sort of output?

contact_id 7759512f-662f-4139-94fb-8b708c5d11eb a96993bf-eb38-446c-a5a7-416485e8b933
1 3232 true
2 true

I searched around for various queries relating to transposing a table, pivot tables in Postgres, this literal question title "turn postgres join rows into columns" but I haven't found a solution:

Tomáš Hübelbauer
  • 9,179
  • 14
  • 63
  • 125

3 Answers3

2

This is typically done using filtered aggregation:

select contact_id, 
        max(value) filter (where custom_field_id = 'a96993bf-eb38-446c-a5a7-416485e8b933') as "a96993bf-eb38-446c-a5a7-416485e8b933",
        max(value) filter (where custom_field_id = '7759512f-662f-4139-94fb-8b708c5d11eb') as "7759512f-662f-4139-94fb-8b708c5d11eb"
from demo_contact_custom_field 
group by contact_id
order by contact_id;

Online example

A fundamental restriction of the SQL language is that, the number, names and data types of all columns of a query must be known to the database before it retrieves the result of the query. You can not have a query that returns 2 columns today and 42 tomorrow without any change to the query itself.


A workaround is to aggregate into JSON values as shown in histocrat's answer. Or create a view with all possible columns based on the data. See e.g. this answer for an example

1

I don't think it's possible to do this exact thing with PostgreSQL. Columns are assumed to be static per query, so they can't be built dynamically (as far as I know). You can do something very similar, though:

SELECT contact_id, json_object_agg(custom_field_id,value)
FROM demo_contact_custom_field 
GROUP BY contact_id;

That will get you output that looks like

contact_id json_object_agg
1 {"7759512f-662f-4139-94fb-8b708c5d11eb":"3232","a96993bf-eb38-446c-a5a7-416485e8b933":"true"}
2 {"a96993bf-eb38-446c-a5a7-416485e8b933":"true"}

View on DB Fiddle

You could also take the approach described here of programmatically building a return type or query based on the rows of the table.

histocrat
  • 2,291
  • 12
  • 21
0

You can also consider crosstab function https://www.postgresql.org/docs/current/tablefunc.html#id-1.11.7.52.5

further this would help - demonstration of crosstab: https://learnsql.com/blog/creating-pivot-tables-in-postgresql-using-the-crosstab-function/

below is not your answer, but it is basic implementation

SELECT *
FROM   crosstab(
       'select custom_field_id, contact_id, value from demo_contact_custom_field a inner join demo_contact b on a.contact_id=b.id'
   ) t (col text, r1 text,r2 text);

enter image description here

shariqayaz
  • 63
  • 1
  • 8
  • I have looked at Crosstab related answers before posting this question and I've come away thinking it doesn't have a solution to this problem. Do you have an example where it would produce the result schema like in my question? – Tomáš Hübelbauer Jan 18 '23 at 08:16