2

Suppose I have this table:

Content
+----+---------+
| id |  title  |
+----+---------+
| 1  | lorem   |
+----|---------|

And this one:

Fields
+----+------------+----------+-----------+
| id | id_content |   name   |   value   |
+----+------------+----------+-----------+
| 1  |      1     | subtitle |   ipsum   |
+----+------------+----------+-----------|
| 2  |      1     |   tags   |   tag1    |
+----+------------+----------+-----------|
| 3  |      1     |   tags   |   tag2    |
+----+------------+----------+-----------|
| 4  |      1     |   tags   |   tag3    |
+----+------------+----------+-----------|

The thing is: i want to query the content, transforming all the rows from "Fields" into columns, having something like:

+----+-------+----------+---------------------+
| id | title | subtitle |        tags         |
+----+-------+----------+---------------------+
|  1 | lorem | ipsum    | [tag1,tag2,tag3]    |
+----+-------+----------+---------------------|

Also, subtitle and tags are just examples. I can have as many fields as I desired, them being array or not.

But I haven't found a way to convert the repeated "name" values into an array, even more without transforming "subtitle" into array as well. If that's not possible, "subtitle" could also turn into an array and I could change it later on the code, but I needed at least to group everything somehow. Any ideas?

2
  • subtitle is the only special "constant" that you would like to separate ? Commented Nov 11, 2014 at 12:17
  • @Houari, no, I can have as many rows in "Fields" as I want. For example: subtitle, description, image, tags (array), dimensions (array), etc.. That's why it's a little more complex than it seems Commented Nov 11, 2014 at 13:23

2 Answers 2

3

You can use array_agg, e.g.

SELECT id_content, array_agg(value)
FROM fields
WHERE name = 'tags'
GROUP BY id_content

If you need the subtitle, too, use a self-join. I have a subselect to cope with subtitles that don't have any tags without returning arrays filled with NULLs, i.e. {NULL}.

SELECT f1.id_content, f1.value, f2.value
FROM fields f1
LEFT JOIN (
    SELECT id_content, array_agg(value) AS value
    FROM fields
    WHERE name = 'tags'
    GROUP BY id_content
) f2 ON (f1.id_content = f2.id_content)
WHERE f1.name = 'subtitle';

See http://www.postgresql.org/docs/9.3/static/functions-aggregate.html for details.

If you have access to the tablefunc module, another option is to use crosstab as pointed out by Houari. You can make it return arrays and non-arrays with something like this:

SELECT id_content, unnest(subtitle), tags
FROM crosstab('
    SELECT id_content, name, array_agg(value)
    FROM fields
    GROUP BY id_content, name
    ORDER BY 1, 2
') AS ct(id_content integer, subtitle text[], tags text[]);

However, crosstab requires that the values always appear in the same order. For instance, if the first group (with the same id_content) doesn't have a subtitle and only has tags, the tags will be unnested and will appear in the same column with the subtitles.

See also http://www.postgresql.org/docs/9.3/static/tablefunc.html

1
  • The "subtitle" and "tags" are just examples. I can have 10 different fields, with 4 being array and 6 just strings, for example. That's why I was thinking about pivot. I'll try to use it with array_agg Commented Nov 11, 2014 at 13:22
1

If the subtitle value is the only "constant" that you wan to separate, you can do:

SELECT * FROM crosstab
(
  'SELECT content.id,name,array_to_string(array_agg(value),'','')::character varying FROM content    inner join 
    (
    select * from fields where fields.name = ''subtitle''  
        union all  
    select * from fields where fields.name <> ''subtitle'' 
    ) fields_ordered 
   on fields_ordered.id_content = content.id   group by content.id,name'
)
AS
(
       id integer,
       content_name character varying,
       tags character varying
);
2
  • It's not the only one. I cant have as many as I want, some being array and some not. Commented Nov 11, 2014 at 13:24
  • Apparently I began editing my answer and read yours afterwards. Commented Nov 11, 2014 at 15:55

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.