Looking to Split Hobby column to N multiple columns in PostgreSQL 9.4, where each column header shows the Hobby.
Original table
| Name | Hobby | 
|---|---|
| Rene | Python, Monkey Bars | 
| CJ | Trading, Python | 
| Herlinda | Fashion | 
| DJ | Consulting, Sales | 
| Martha | Social Media, Teaching | 
| Doug | Leadership, Management | 
| Mathew | Finance, Emp Engagement | 
| Meyers | Sleeping, Coding, CrossFit | 
| Mike | YouTube, Athletics | 
| Peter | Eat, Sleep, Python | 
| Thomas | Read, Trading, Sales | 
notes: without using crosstab()
Desire table
| Name | Hobby | Python | Monkey Bars | Trading | Fashion | Consutling | Sales | Social Media | Teaching | Leadership | Management | Finance | Emp Engagement | Sleeping | Coding | Crossfit | YouTube | Athletics | Eat | Sleep | Read | 
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Rene | Python, Monkey Bars | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| CJ | Trading, Python | TRUE | FALSE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Herlinda | Fashion | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| DJ | Consulting, Sales | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Martha | Social Media, Teaching | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Doug | Leadership, Management | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Mathew | Finance, Emp Engagement | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Meyers | Sleeping, Coding, CrossFit | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | 
| Mike | YouTube, Athletics | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | 
| Peter | Eat, Sleep, Python | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | 
| Thomas | Read, Trading, Sales | FALSE | FALSE | TRUE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | 
---removing the 1NF removing the 1NF
CREATE TABLE ws_bi.split_clm(
  id integer PRIMARY KEY,
  name text,
  hobby text
);
INSERT INTO ws_bi.split_clm (id, name, hobby) VALUES
(1, 'Rene', 'Python, Monkey Bars'),
(2, 'CJ', 'Trading, Python'),
(3, 'Herlinda', 'Fashion'),
(4, 'DJ', 'Consulting, Sales'),
(5, 'Martha', 'Social Media, Teaching'),
(6, 'Doug', 'Leadership, Management'),
(7, 'Mathew', 'Finance, Emp Engagement'),
(8, 'Meyers', 'Sleeping, Coding, CrossFit'),
(9, 'Mike', 'YouTube, Athletics'),
(10, 'Peter', 'Eat, Sleep, Python'),
(11, 'Thomas', 'Read, Trading, Sales');
/***query****/
SELECT id, unnest(string_to_array(hobby, ', ')) AS values 
FROM ws_bi.split_clm
ORDER BY id;
result image result image
---update March.2.2023. by using this solution: stackoverflow.com/questions/50299360
DB FIDDLE (UK): https://dbfiddle.uk/Sn7iO4zL
Making some adjustment from the L. Rodgers solution for some reason still falling... maybe because Json function are something very new for me. image result tmpMoToJson
     ---sample data 
DROP TABLE IF EXISTS ws_bi.split_clm;
CREATE TABLE ws_bi.split_clm(
  id integer PRIMARY KEY,
  name text,
  hobby text, 
  value int
);
INSERT INTO ws_bi.split_clm (id, name, hobby,value) VALUES
(1, 'Rene', 'Python, Monkey_Bars','5'),
(2, 'CJ', 'Trading, Python','25'),
(3, 'Herlinda', 'Fashion','15'),
(4, 'DJ', 'Consutling, Sales','35'),
(5, 'Martha', 'Social_Media, Teaching','45'),
(6, 'Doug', 'Leadership, Management','55'),
(7, 'Mathew', 'Finance, Emp_Engagement','65'),
(8, 'Mayers', 'Sleeping, Coding, Crossfit','75'),
(9, 'Mike', 'YouTube, Athletics','85'),
(10, 'Peter', 'Eat, Sleep, Python','95'),
(11, 'Thomas', 'Read, Trading, Sales','105');
/****query****/
--1NF <done>
--DROP TABLE IF EXISTS ws_bi.split_clm_Nor;
  CREATE     TABLE     ws_bi.split_clm_Nor  AS  (
  SELECT id, name, unnest(string_to_array(hobby, ', ')) AS Ivalues , value
  FROM ws_bi.split_clm
  ORDER BY id
--;
) with data
    DISTRIBUTED BY (id) ;
 Analyze  ws_bi.split_clm_Nor;
 --Select * from   ws_bi.split_clm_Nor limit 6; ---
 ---ver 2.0
 --DROP TABLE IF EXISTS ws_bi.split_clm_Nor2;
  CREATE     TABLE     ws_bi.split_clm_Nor2  AS (
  SELECT id, name, lower(unnest(string_to_array(hobby, ', '))) AS Ivalues , value,count(1)  as "Case_Volume"
  FROM ws_bi.split_clm
  GROUP BY 1,2,3,4
  ORDER BY id
--;
) with data
    DISTRIBUTED BY (id) ;
 Analyze  ws_bi.split_clm_Nor2;
 --Select * from   ws_bi.split_clm_Nor2 limit 6; 
 ---------------------------
 DROP TABLE IF EXISTS  ws_bi.tmpTblTyp2 CASCADE ; 
DO LANGUAGE plpgsql $$ 
DECLARE v_sqlstring VARCHAR  = ''; 
BEGIN 
v_sqlstring := CONCAT( 'CREATE  TABLE ws_bi.tmpTblTyp2 AS SELECT '   
                       ,(SELECT  STRING_AGG( CONCAT('NULL::int AS ' , ivalues )::TEXT , ' ,' 
                            ORDER BY ivalues                        
                           )::TEXT
                           FROM
                           (SELECT DISTINCT ivalues  FROM ws_bi.split_clm_Nor2 )a
                        )
                      ,' LIMIT 0 '    
                       ) ; -- RAISE NOTICE '%', v_sqlstring ;  
EXECUTE( v_sqlstring ) ; END $$; 
 --------------------------------------------
 DROP TABLE IF EXISTS ws_bi.tmpMoJson ;
CREATE     TABLE     ws_bi.tmpMoJson  AS    (
--CREATE TEMP TABLE tmpMoJson AS
      SELECT 
         name AS name
         ,(json_build_array( mivalues )) AS js_mivalues_arr
         ,json_populate_recordset ( NULL::ws_bi.tmpTblTyp2 /** use temp table as a record type!!*/
                                   , json_build_array( mivalues )  /** builds row-type column that can be expanded with (jprs).* */
                                  ) jprs /**no error with wrong JSON values result under jprs**/
      FROM ( SELECT name
             ,json_object_agg(ivalues,value) AS mivalues 
             FROM ws_bi.split_clm_Nor2
             GROUP BY 1
             ORDER BY 1
            ) a
--;
) with data
    DISTRIBUTED BY (name) ;
 Analyze  ws_bi.tmpMoJson;
--Select * from   ws_bi.tmpMoJson;  
 SELECT  
  name
,(ROW((jprs).*)::ws_bi.tmpTblTyp2).* -- explode the composite type row
FROM ws_bi.tmpMoJson ;
DB FIDDLE (UK) before : https://dbfiddle.uk/BoyKmDrT
DB FIDDLE (UK) after : https://dbfiddle.uk/gP0kAnZ5
