PostgreSQL 9.4中不使用crosstab()将多值爱好列转换为动态列的实现求助
PostgreSQL 9.4中不使用crosstab()将多值爱好列转换为动态列的实现求助
大家好,我现在遇到一个PostgreSQL 9.4的处理需求,想请教一下怎么实现:
我有一张存储用户和爱好的表,其中hobby列是用逗号分隔的多值内容,现在需要把这个列拆分成以每个独立爱好为列名的结构,每一行对应用户是否拥有该爱好(后续还扩展了关联value值的需求),而且要求不能使用crosstab()函数。
原始表结构与数据
首先是初始的表定义和数据:
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');
期望的结果
我想要得到的结果是这样的:每个独立的爱好成为一个列,用户有该爱好则对应列值为TRUE,没有则为FALSE,同时保留原始的name和hobby列,示例结构如下:
| Name | Hobby | Python | Monkey Bars | Trading | Fashion | ... |
|---|---|---|---|---|---|---|
| Rene | Python, Monkey Bars | TRUE | TRUE | FALSE | FALSE | ... |
| CJ | Trading, Python | TRUE | FALSE | TRUE | FALSE | ... |
| Herlinda | Fashion | FALSE | FALSE | FALSE | TRUE | ... |
后来我还扩展了需求,给每个爱好加上对应的value值,期望每个爱好列显示对应的value,没有则显示NULL或0。
我已经做的尝试
首先我把表拆成了符合1NF的结构,拆分出每个用户的单个爱好:
CREATE TABLE ws_bi.split_clm_Nor AS ( SELECT id, name, unnest(string_to_array(hobby, ', ')) AS Ivalues FROM ws_bi.split_clm ORDER BY id ) with data DISTRIBUTED BY (id);
更新后的带value的表结构和拆分语句如下:
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'); -- 拆分到1NF表 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);
之后我尝试参考JSON函数的方案来动态生成列,步骤如下:
- 先创建一个临时表结构,包含所有独立的爱好列:
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 ' ) ; EXECUTE( v_sqlstring ) ; END $$;
- 尝试用JSON函数构建行数据并展开:
DROP TABLE IF EXISTS ws_bi.tmpMoJson ; CREATE TABLE ws_bi.tmpMoJson AS ( SELECT name AS name ,(json_build_array( mivalues )) AS js_mivalues_arr ,json_populate_recordset ( NULL::ws_bi.tmpTblTyp2 , json_build_array( mivalues ) ) 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); -- 尝试展开列 SELECT name ,(ROW((jprs).*)::ws_bi.tmpTblTyp2).* FROM ws_bi.tmpMoJson ;
但是这个方案没有达到预期的效果,可能是我对PostgreSQL的JSON函数还不太熟悉,调整了好几次还是有问题。想请教一下大家,怎么修改这个方案,或者有没有其他不用crosstab()的方法来实现我的需求?
备注:内容来源于stack exchange,提问作者C'perota
相关产品推荐
相关产品推荐

