vol7ron
vol7ron

Reputation: 42109

PostgreSQL: What's the best way to sort multiple array_agg() with different conditions?

Conditions: PostgreSQL 8.4 (this would be too simple with 9.x)

Table:

  id  | gpa | class   | rank 
  ----+-----+---------+--------
  1   | 2.0 | english | low
  1   | 2.0 | math    | low
  1   | 2.0 | pe      | low
  1   | 2.0 | spanish | medium
  2   | 3.5 | english | high
  2   | 3.5 | history | high
  2   | 3.5 | art     | great
  2   | 3.5 | tech    | high
  3   | 4.0 | pe      | medium
  3   | 4.0 | spanish | high
  3   | 4.0 | english | great
  3   | 4.0 | art     | great

Wanted:

  id  | gpa |    great     |          high          | medium  | low 
  ----+-----+--------------+------------------------+---------+-----
  1   | 2.0 |              |                        | spanish | english, math, pe
  2   | 3.5 | art          | english, history, tech |         |
  3   | 4.0 | art, english | spanish                | pe      |  

Current Method:

WITH details AS (
   select * from table order by rank, class
)
SELECT    id
        , gpa
        , array_to_string(array_agg(CASE WHEN rank='great'  THEN class END,', ')) as great
        , array_to_string(array_agg(CASE WHEN rank='high'   THEN class END,', ')) as high
        , array_to_string(array_agg(CASE WHEN rank='medium' THEN class END,', ')) as medium
        , array_to_string(array_agg(CASE WHEN rank='low'    THEN class END,', ')) as low
FROM      details
ORDER BY  gpa;

So I tried to put this as an example from what I'm doing - no I don't have an actual table with this structure that isn't much normalized, but I do have a subquery that produces something like this.

In actuality my array_agg() is also concatenating two fields (a word and a number) and i'm sorting by the number e.g.(array_agg(CASE ... THEN foo || ' - ' || bar), unfortunately my output is only mostly sorted. Perhaps I can improve this question if I have more time.

Upvotes: 2

Views: 1713

Answers (2)

user731136
user731136

Reputation:

When I execute this query:

with details AS (
   select * from table order by class
)
select t.id, t.gpa,
  (select array_to_string(array_agg(class),',')
   from details d
   where d.id = t.id and d.gpa = t.gpa and rank = 'great')
  as great,
  (select array_to_string(array_agg(class),',')
   from details d
   where d.id = t.id and d.gpa = t.gpa and rank = 'high')
  as high,
 (select array_to_string(array_agg(class),',')
  from details d
  where d.id = t.id and d.gpa = t.gpa and rank = 'medium')   
 as medium,
 (select array_to_string(array_agg(class),',')
  from details d
  where d.id = t.id and d.gpa = t.gpa and rank = 'low')
  as low
from table t
group by t.id, t.gpa
order by t.gpa

I get the results:

 id   gpa      great         high                 medium       low
----+-----+-------------+----------------------+----------+------------------
  1   2.0                                         spanish   english,math,pe
  2   3.5       art       english,history,tech
  3   4.0    art,english  spanish                 pe

I hope you serve.

UPDATE: Other option with a better performance.

select t.id, t.gpa,
       a.class as great,
       b.class as high,
       c.class as medium,
       d.class as low
from table t 
left join (select id, gpa, array_to_string(array_agg(class),',') as class
           from table
           where rank = 'great'
           group by id, gpa
          ) a on (a.id = t.id and a.gpa = t.gpa)
left join (select id, gpa, array_to_string(array_agg(class),',') as class
           from table
           where rank = 'high'
           group by id, gpa
          ) b on (b.id = t.id and b.gpa = t.gpa)
left join (select id, gpa, array_to_string(array_agg(class),',') as class
           from table
           where rank = 'medium'
           group by id, gpa
          ) c on (c.id = t.id and c.gpa = t.gpa)
 left join (select id, gpa, array_to_string(array_agg(class),',') as class
            from table
            where rank = 'low'
            group by id, gpa
           ) d on (d.id = t.id and d.gpa = t.gpa)          
 group by t.id, t.gpa, a.class, b.class, c.class, d.class
 order by t.gpa

Upvotes: 1

maniek
maniek

Reputation: 7307

Create a function like:

create or replace function sort(anyarray) returns anyarray language sql as 
$$
    select array_agg(x) from(
        select unnest($1) as x order by 1
    ) tab;
$$;

and then:

... 
array_to_string(sort(array_agg(CASE WHEN rank='great'  THEN class END,', '))) 
...

Upvotes: 1

Related Questions