使用交叉表和计数的数据透视表

spa*_*kle 2 postgresql pivot-table dynamic-sql crosstab postgres-crosstab

我必须显示这样的表格:

发表 没送到 没收到
2021年 10 86 75
2021年 二月 13 36 96
2021年 行进 49 7 61
2021年 四月 3 21 72

使用此查询生成的原始数据:

SELECT 
    year,
    TO_CHAR( creation_date, 'Month') AS month,
    marking,
    COUNT(*) AS count 
FROM invoices
GROUP BY 1,2,3
Run Code Online (Sandbox Code Playgroud)

我尝试过使用crosstab()但出现错误:

SELECT * FROM crosstab('
    SELECT 
        year,
        TO_CHAR( creation_date, ''Month'') AS month,
        marking,
        COUNT(*) AS count 
    FROM invoices
    GROUP BY 1,2,3
') AS ct(year text, month text, marking text)
Run Code Online (Sandbox Code Playgroud)

我不想手动输入所有标记值,因为它们很多。

ERROR:  invalid source data SQL statement
DETAIL:  The provided SQL must return 3 columns: rowid, category, and values.
Run Code Online (Sandbox Code Playgroud)

Edo*_*ard 5

1. 具有有限marking值列表的静态解决方案:

SELECT year
     , TO_CHAR( creation_date, 'Month') AS month
     , COUNT(*) FILTER (WHERE marking = 'Delivered') AS Delivered
     , COUNT(*) FILTER (WHERE marking = 'Not delivered') AS "Not delivered"
     , COUNT(*) FILTER (WHERE marking = 'Not Received') AS "Not Received"
FROM invoices
GROUP BY 1,2
Run Code Online (Sandbox Code Playgroud)

2. 具有大量marking值的完整动态解决方案:

该提案是ABcrosstab中提出的解决方案的替代解决方案。

这里提出的解决方案只需要一个composite type可以动态创建的专用解决方案,然后它依赖于jsonb类型和标准函数:

从您的查询开始,该查询计算每年、每月和marking值的行数:

  • 使用该jsonb_object_agg函数,结果行首先按年和月聚合到与值 对应的jsonb对象中,并且其jsonb keysmarkingjsonb values 与计数相对应的
  • 然后使用该函数和专用复合类型将结果jsonb对象转换为记录。jsonb_populate_record

首先,我们动态创建一个composite type对应于有序值列表的marking值:

CREATE OR REPLACE PROCEDURE create_composite_type() LANGUAGE plpgsql AS $$
DECLARE
  column_list text ;
BEGIN
  SELECT string_agg(DISTINCT quote_ident(marking) || ' bigint', ',' ORDER BY quote_ident(marking) || ' bigint' ASC)
    INTO column_list
    FROM invoices ;
  
  EXECUTE 'DROP TYPE IF EXISTS composite_type' ;
  EXECUTE 'CREATE TYPE composite_type AS (' || column_list || ')' ;
END ;
$$ ;

CALL create_composite_type() ;
Run Code Online (Sandbox Code Playgroud)

然后以下查询提供了预期结果:

SELECT a.year
     , TO_CHAR(a.year_month, 'Month') AS month
     , (jsonb_populate_record( null :: composite_type
                             , jsonb_object_agg(a.marking, a.count)
                             )
       ).*
 FROM
    ( SELECT year
           , date_trunc('month', creation_date) AS year_month
           , marking
           , count(*) AS count
        FROM invoices AS v
       GROUP BY 1,2,3
    ) AS a
GROUP BY 1,2
ORDER BY month
Run Code Online (Sandbox Code Playgroud)

显然,如果值列表可能随时间变化,那么您必须在执行查询之前marking调用该过程。create_composite_type()如果您不更新composite_type,查询仍然可以工作(没有错误!),但一些旧的标记值可能已过时(不再使用),并且查询结果中可能会丢失一些新的标记值(不显示为列) 。

请参阅dbfiddle中的完整演示。


归档时间:

查看次数:

6937 次

最近记录:

3 年,7 月 前