0

I have a table structured like:

CREATE TABLE artists (artist TEXT UNIQUE, facts JSONB);

INSERT INTO artists (artist, facts) 
  VALUES ('adele', '[{"type": "full_name", "value": "Adele Laurie"}, {"type": "age", "value": "25"}]');

INSERT INTO artists (artist, facts) 
  VALUES ('taylor', '[{"type": "age", "value": "25"}, {"type": "last_album", "value": "1989"}]');

There are a fixed number of fact "type"s, but not every artist will have each fact. How can I select a result with columns for each fact type and null's for missing fact names?

Desired output:

| artist |  full_name    | age  | last_album |
|--------|---------------|------|------------|
| adele  | Adele Laurie  | 25   | null       |
| taylor | null          | 25   | 1989       |
Jorge Campos
  • 22,647
  • 7
  • 56
  • 87
Andrew C
  • 3,560
  • 1
  • 23
  • 24

2 Answers2

1

You can do as this:

select a.artist, 
     max(case when b.value->>'type' = 'full_name' 
         then b.value->>'value' 
         else b.value->>'full_name' end) as full_name,
     max(case when b.value->>'type' = 'age' 
         then b.value->>'value' 
         else b.value->>'age' end) as age,
     max(case when b.value->>'type' = 'last_album' 
         then b.value->>'value' 
         else b.value->>'last_album' end) as last_album
from artists a, 
     json_array_elements(a.facts) b
group by a.artist
order by a.artist

See it here: http://sqlfiddle.com/#!15/e376b/2

In the fiddle I created the field as JSON since there is not available the JSONB type

If you need to add more types, just add it as a case condition like the others. I think you can figure it out from here :)

EDIT

Even with your change in the format this query should solve your problem. Just edited the fiddle. See it here: http://sqlfiddle.com/#!15/1c2b6/2

The only difference is that you don't really need the else for the case statement.

This is the query without the else statements

select a.artist, 
     max(case when b.value->>'type' = 'full_name' 
         then b.value->>'value' end) as full_name,
     max(case when b.value->>'type' = 'age' 
         then b.value->>'value' end) as age,
     max(case when b.value->>'type' = 'last_album' 
         then b.value->>'value' end) as last_album
from artists2 a, 
     json_array_elements(a.facts) b
group by a.artist
order by a.artist;

I edited the SqlFiddle link up here.

Jorge Campos
  • 22,647
  • 7
  • 56
  • 87
  • That works great! I'm having trouble viewing the sqlfiddle's though. Can you fix the links and edit to just answer the question as is? – Andrew C Nov 24 '15 at 21:22
  • I've edited it with the query. And the sqlfiddle links are working to me here. It sometimes stop working, lately it is unstable. Try again. – Jorge Campos Nov 25 '15 at 01:20
0

I would go with crosstab() function. @Eriwn has really good answers with using this approach.

Example:

SELECT * FROM crosstab(
    'WITH data AS (
      SELECT artist,fact
      FROM artists a, jsonb_array_elements(a.facts) fact
    )
    SELECT artist,type,value
    FROM data,jsonb_to_record(data.fact) AS x(type text,value text)',
    'WITH data AS (
      SELECT artist,fact
      FROM artists a, jsonb_array_elements(a.facts) fact
     )
    SELECT DISTINCT type
   FROM data,jsonb_to_record(data.fact) AS x(type text,value text)'
) AS ct (artist text,age text,full_name text,last_album TEXT);

Result:

 artist | age  |  full_name   | last_album 
--------+------+--------------+------------
 adele  |      | Adele Laurie | 25
 taylor | 1989 |              | 25
(2 rows)
Community
  • 1
  • 1
Dmitry S
  • 4,990
  • 2
  • 24
  • 32
  • You are using four select statements for doing something that needs only one. In a large data sets this would be an overkill. Always use K.I.S.S. methodology :) – Jorge Campos Nov 25 '15 at 01:21
  • Yeah, with crosstab there is no way you can reuse actual text SQL query, unless you show the one. The drawback to use 2 selects, but with adding more attributes into Json SQL should not be changed greatly @JorgeCampos – Dmitry S Nov 25 '15 at 04:29