需求将下列表格相同id的name拼接起来输出成一列
id | Name |
1 | peter |
1 | lily |
2 | john |
转化后效果:
id | Name |
1 | peter;lily |
2 | john; |
实现方式使用 array_to_string 和 array_agg 函数,具体语句如下:
string_agg(expression, delimiter) 把表达式变成一个数组
string_agg(expression, delimiter) 直接把一个表达式变成字符串
select id, array_to_string( array_agg(Name), ';' ) from table group by id
补充:Postgresql实现动态的行转列
问题
在数据处理中,常遇到行转列的问题,比如有如下的问题:
有这样的一张表
"Student_score"表:
姓名 | 课程 | 分数 |
---|---|---|
张三 | 数学 | 83 |
张三 | 物理 | 93 |
张三 | 语文 | 80 |
李四 | 语文 | 74 |
李四 | 数学 | 84 |
李四 | 物理 | 94 |
我们想要得到像这样的一张表:
姓名 | 数学 | 物理 | 语文 |
---|---|---|---|
李四 | 84 | 94 | 74 |
张三 | 83 | 93 | 80 |
当数据量比较少时,我们可以在Excel中使用数据透视表pivot table的功能实现这个需求,但当数据量较大,或者我们还需要在数据库中进行后续的数据处理时,使用数据透视表就显得不那么高效。
下面,介绍如何在Postgresql中实现数据的行转列。
静态写法
当我们要转换的值字段是数值型时,我们可以用SUM()函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
CREATE TABLE Student_score(姓名 varchar , 课程 varchar , 分数 int );
INSERT INTO Student_score VALUES ( '张三' , '数学' ,83);
INSERT INTO Student_score VALUES ( '张三' , '物理' ,93);
INSERT INTO Student_score VALUES ( '张三' , '语文' ,80);
INSERT INTO Student_score VALUES ( '李四' , '语文' ,74);
INSERT INTO Student_score VALUES ( '李四' , '数学' ,84);
INSERT INTO Student_score VALUES ( '李四' , '物理' ,94);
select 姓名
, sum ( case 课程 when '数学' then 分数 end ) as 数学
, sum ( case 课程 when '物理' then 分数 end ) as 物理
, sum ( case 课程 when '语文' then 分数 end ) as 语文
from Student_score
GROUP BY 1
|
当我们要转换的值字段是字符型时,比如我们的表是这样的:
"Student_grade"表:
姓名 | 数学 | 物理 | 语文 |
---|---|---|---|
张三 | 优 | 良 | 及格 |
李四 | 良 | 优 | 及格 |
我们可以用string_agg()函数:
1
2
3
4
5
6
7
|
CREATE TABLE Student_grade(姓名 varchar , 课程 varchar , 等级 varchar );
INSERT INTO Student_grade VALUES ( '张三' , '数学' , '优' );
INSERT INTO Student_grade VALUES ( '张三' , '物理' , '良' );
INSERT INTO Student_grade VALUES ( '张三' , '语文' , '及格' );
INSERT INTO Student_grade VALUES ( '李四' , '语文' , '及格' );
INSERT INTO Student_grade VALUES ( '李四' , '数学' , '良' );
INSERT INTO Student_grade VALUES ( '李四' , '物理' , '优' );
|
select 姓名
1
2
3
4
5
|
,string_agg(( case 课程 when '数学' then 等级 end ), '' ) as 数学
,string_agg(( case 课程 when '物理' then 等级 end ), '' ) as 物理
,string_agg(( case 课程 when '语文' then 等级 end ), '' ) as 语文
from Student_grade
GROUP BY 1
|
内置函数(半动态)
Postgresql内置了tablefunc可实现pivot table的功能。
语法:
1
2
3
4
5
6
|
SELECT *
FROM crosstab(
'select row_name,cat,value
from table
order by 1,2' )
AS (row_name type, category_1 type, category_2 type, category_3 type, ...);
|
例如:
1
2
3
4
5
6
|
SELECT *
FROM crosstab(
'select 姓名,课程,分数
from Student_score
order by 1,2' )
AS (姓名 varchar , 数学 int , 物理 int , 语文 int );
|
需要注意的是crosstab( text sql) 中的sql语句必须按顺序返回row_name, category , value,并且必须声明输出的各列的列名和数据类型。当原表中的cat列有很多不同的值,那我们将会得到一个有很多列的表,并且我们需要手动声明每个列的列名及数据类型,显然这种体验非常不友好。那有没有更好的方式呢,我们可以通过手动建立存储过程(函数)实现。
自建函数(动态)
动态的行转列我们通过plpgsql实现,大致的思路如下:
判断value字段的数据类型,如果是数值型,则转入2.,否则转入3.
对cat列中的每个distinct值使用sum(case when),转成列
对cat列中的每个distinct值使用string_agg(case when),转成列
实现代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
CREATE or REPLACE FUNCTION
long_to_wide(
table_name VARCHAR ,
row_name VARCHAR ,
cat VARCHAR ,
value_field VARCHAR )
returns void as
$$
/*
table_name : 表名
row_name : 行名字段
cat : 转换为列名的字段
value_field : 转换为值的字段
*/
DECLARE v_sql text;
arow record;
value_type VARCHAR ;
BEGIN
v_sql= '
drop table if exists temp_table;
CREATE TABLE temp_table as
SELECT distinct ' ||cat|| ' as col from ' ||table_name|| '
order by ' ||cat;
execute v_sql;
v_sql= '
SELECT t.typname AS type
FROM pg_class c
,pg_attribute a
,pg_type t
WHERE c.relname = lower(' '' ||table_name|| '' ')
and a.attnum > 0
and a.attrelid = c.oid
and a.atttypid = t.oid
and a.attname=' '' ||value_field|| '' '
ORDER BY a.attnum
' ;
execute v_sql into value_type; --获取值字段的数据类型
v_sql= 'select ' ||row_name;
IF value_type in ( 'numeric' , 'int8' , 'int4' , 'int' ) --判断值字段是否是数值型
THEN
FOR arow in ( SELECT col FROM temp_table) loop
v_sql=v_sql|| '
,sum(case ' ||cat|| ' when ' '' ||arow.col|| '' ' then ' ||value_field|| ' else 0 end) ' ||cat|| '_' ||arow.col;
end loop;
ELSE
FOR arow in ( SELECT col FROM temp_table) loop
v_sql=v_sql|| '
,string_agg((case ' ||cat|| ' when ' '' ||arow.col|| '' ' then ' ||value_field|| ' else ' '' ' end),' '' ') ' ||cat|| '_' ||arow.col;
end loop;
END IF;
v_sql= '
drop table if exists ' ||table_name|| '_wide;
CREATE TABLE ' ||table_name|| '_wide as
' ||v_sql|| '
from ' ||table_name|| '
group by ' ||row_name|| ';
drop table if exists temp_table
' ;
execute v_sql;
end ;
$$ LANGUAGE plpgsql;
|
调用示例:
SELECT long_to_wide('Student_grade', '姓名','课程', '等级')
生成的表名为Student_grade_wide
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。如有错误或未考虑完全的地方,望不吝赐教。
原文链接:https://www.cnblogs.com/karl-F/p/9182566.html