Bry*_*ley 5 postgresql aggregate pivot group-by
我有一个具有以下结构的数据库:
| 日期 | 角色 | 类型 | 期间 |
|---|---|---|---|
| 2022-04-16 | 护士 | 准备食材 | 45 |
| 2022-04-17 | 护士 | 打扫 | 30 |
| 2022-04-17 | 志愿者 | 打扫 | 20 |
| 2022-04-17 | 护士 | 准备食材 | 60 |
注意:我事先不知道“类型”列中的值,因为它们是由用户定义的。此外,可以有多行具有重叠的日期、角色和类型。
我正在使用一个图表库,希望将数据分组如下:
| 角色 | 准备食材 | 打扫 |
|---|---|---|
| 护士 | 105 | 30 |
| 志愿者 | 无效的 | 20 |
到目前为止,我可以使用以下查询对数据进行分组
select
role,
type,
sum(duration) as total_minutes
from work
group by role, type;
Run Code Online (Sandbox Code Playgroud)
| 角色 | 类型 | 总分钟数 |
|---|---|---|
| 护士 | 打扫 | 45 |
| 护士 | 准备食材 | 20 |
| 志愿者 | 打扫 | 15 |
| 志愿者 | 准备食材 | 43 |
如何“旋转”/“转置”数据,以便每一行代表一个角色,其中一列包含每种类型工作的分钟总和?
实际上,我想转置类似于 Pandas DataFrame.pivot_table函数的数据,但仅使用 SQL。
首先,您需要使用create extension tablefunc;命令安装 tablefunc 扩展,否则数据透视功能crosstab将无法工作。
即使读完这个答案后,仍然建议您阅读 PostgreSQL on crosstab 的官方文档。
至于如何做到这一点:
select *
from crosstab(
'select
role,
type,
sum(duration) as total_minutes
from work
group by role, type
order by type',
'select distinct type from work order by type'
) as ct(
role text,
"Cleaning" text,
"Food preparation" text
);
Run Code Online (Sandbox Code Playgroud)
注意order by两个查询中的显式子句,这是必须的,否则可能会错误地映射值,因为没有它的 SQL 不保证数据的顺序。
您必须type在别名中指定该列的每个可能的输出。
上面的一个更动态的版本(尽管无论如何都不完美):
create or replace function get_dynamic_transpose()
returns text
language plpgsql
as
$$
declare
v_output_columns text;
begin
select array_to_string(array_agg(distinct quote_ident(type) || ' ' || pg_typeof(type) || E' \n'),',','null')
into v_output_columns
from testing;
return format(
'select *
from crosstab(
''select
role,
type,
sum(duration) as total_minutes
from testing
group by role, type
order by type'',
''select distinct type from testing order by type''
) as ct(
role text,
%s
);', v_output_columns
);
end;
$$;
Run Code Online (Sandbox Code Playgroud)
该函数将返回您需要执行的查询以获得所需的结果。它将动态构建输出所需的可能列的列表。这个函数肯定可以变得更通用,就像这里所做的那样,但是要做到这一点并不是一个小工作量,因为 PostgreSQL 无法返回一个它事先不知道其定义的集合。
该函数还有另一个选项,而不是返回查询字符串,它可以返回一个 json 对象数组,每个对象代表一行,并且您可以在应用程序端将此 json 拆分为普通的行和列。如果这样的解决方案是可以接受的,那么这工作得很好:
create or replace function get_dynamic_transpose_jsonb()
returns jsonb
language plpgsql
as
$$
declare
v_output_columns text;
v_query text;
v_result jsonb;
begin
select array_to_string(array_agg(distinct quote_ident(type) || ' ' || pg_typeof(type) || E' \n'),',','null')
into v_output_columns
from testing;
v_query = format(
'select jsonb_agg(ct)
from crosstab(
''select
role,
type,
sum(duration) as total_minutes
from testing
group by role, type
order by type'',
''select distinct type from testing order by type''
) as ct(
role text,
%s
);', v_output_columns
);
execute v_query into v_result;
return v_result;
end;
$$;
Run Code Online (Sandbox Code Playgroud)
该函数的结果将类似于以下内容
[{"role": "Nurse", "Cleaning": "30", "Food preparation": null}, {"role": "Volunteer", "Cleaning": null, "Food preparation": "55"}]
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
23099 次 |
| 最近记录: |