Postgres动态列标题(来自另一个表)

时间:2010-11-05 08:58:10

标签: sql postgresql pivot

只是一个愚蠢的例子:

  

表A:

     
      
  1.   
  2. 面包
  3.   
  4. 干酪
  5.         

    表B(当它们被吃掉时):

         
        
    1. 蛋|日期
    2.   
    3. 面包|日期
    4.   
    5. 蛋|日期
    6.   
    7. 奶酪|日期
    8.   
    9. 面包|日期
    10.   

对于统计数据,我需要按照每种食物类型的每个日期进行统计,如下所示:

表统计:

             egg   |   bread   |   cheese

date1         2          1            0    

date2         6          4            2

date3         2          0            0

我需要报表中的列标题是动态的(如果添加了新标题,则会自动显示)。

知道如何在postgres中制作这个吗?

感谢。

5 个答案:

答案 0 :(得分:3)

基于答案Postgres dynamic column headers (from another table)(Eric Vallabh Minikel的工作)我改进了功能,使其更加灵活方便。我认为它也可能对其他人有用,特别是因为它只依赖于pg / plsql并且不需要安装扩展,因为其他的erics工作派生(即plpython)。 Testet的赔率为9.3.5,但也至少应该达到9.2。

改进:

  • 处理包含空格的透视列名称
  • 处理多行标题列
  • 处理透视单元格中的聚合函数以及非聚合透视单元格(如果基础表/视图已经聚合,则最后一个参数可能是'sum(cellval)'以及'cellval')
  • 自动检测数据透视表单元格的数据类型(无需再将其传递给函数)

用途:

SELECT get_crosstab_statement('table_to_pivot', ARRAY['rowname' [, <other_row_header_columns_as_well>], 'colname', 'max(cellval)');

代码:

CREATE OR REPLACE FUNCTION get_crosstab_statement(tablename character varying, row_header_columns character varying[], pivot_headers_column character varying, pivot_values character varying)
  RETURNS character varying AS
$BODY$
--returns the sql statement to use for pivoting the table
--based on: http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/
--based on: https://stackoverflow.com/questions/4104508/postgres-dynamic-column-headers-from-another-table
--based on: http://www.postgresonline.com/journal/categories/24-tablefunc

DECLARE
    arrayname CONSTANT character varying := 'r';

    row_headers_simple character varying;
    row_headers_quoted character varying;
    row_headers_castdown character varying;
    row_headers_castup character varying;
    row_header_count smallint;
    row_header record;

    pivot_values_columnname character varying;
    pivot_values_datatype character varying;
    pivot_headers_definition character varying;
    pivot_headers_simple character varying;

    sql_row_headers character varying;
    sql_pivot_headers character varying;
    sql_crosstab_result character varying;

BEGIN
    -- 1. create row header definitions
    row_headers_simple   :=         array_to_string(row_header_columns, ', ');
    row_headers_quoted   := '''' || array_to_string(row_header_columns, ''', ''') || '''';
    row_headers_castdown :=         array_to_string(row_header_columns, '::text, ') || '::text';

    row_header_count     := 0;
    sql_row_headers      := 'SELECT column_name, data_type
                            FROM information_schema.columns
                            WHERE table_name = ''' || tablename || ''' AND column_name IN (' || row_headers_quoted || ')';
    FOR row_header IN EXECUTE sql_row_headers LOOP
        row_header_count := row_header_count + 1;
        row_headers_castup := COALESCE(row_headers_castup || ', ', '') || arrayname || '[' || row_header_count || ']::' || row_header.data_type || ' AS ' || row_header.column_name;
    END LOOP;

    -- 2. retrieve basic column name in case an aggregate function is used
    SELECT coalesce(substring(pivot_values FROM '.*\((.*)\)'), pivot_values)
    INTO pivot_values_columnname;

    -- 3. retrieve pivot values datatype
    SELECT data_type
    FROM information_schema.columns
    WHERE table_name = tablename AND column_name = pivot_values_columnname
    INTO pivot_values_datatype;

    -- 4. retrieve list of pivot column names.
    sql_pivot_headers := 'SELECT string_agg(DISTINCT quote_ident(' || pivot_headers_column || '), '', '' ORDER BY quote_ident(' || pivot_headers_column || ')) as names, string_agg(DISTINCT quote_ident(' || pivot_headers_column || ') || '' ' || pivot_values_datatype || ''', '', '' ORDER BY quote_ident(' || pivot_headers_column || ') || '' ' || pivot_values_datatype || ''') as definitions FROM ' || tablename || ';';
    EXECUTE sql_pivot_headers INTO pivot_headers_simple, pivot_headers_definition;

    -- 5. set up the crosstab query
    sql_crosstab_result := 'SELECT  ' || replace (row_headers_castup || ', ' || pivot_headers_simple, ', ', ',
        ') || '
FROM    crosstab (
        ''SELECT ARRAY[' || row_headers_castdown || '] AS ' || arrayname || ', ' || pivot_headers_column || ', ' || pivot_values || '
        FROM ' || tablename || '
        GROUP BY ' || row_headers_simple || ', ' || pivot_headers_column || (CASE pivot_values_columnname=pivot_values WHEN true THEN ', ' || pivot_values ELSE '' END) || '
        ORDER BY ' || row_headers_simple || '''
    ,
        ''SELECT DISTINCT ' || pivot_headers_column || '
        FROM ' || tablename || '
        ORDER BY ' || pivot_headers_column || '''
    ) AS newtable (
        ' || arrayname || ' varchar[]' || ',
        ' || replace(pivot_headers_definition, ', ', ',
        ') || '
    );';

    RETURN sql_crosstab_result;
 END

 $BODY$
  LANGUAGE plpgsql STABLE
  COST 100;

答案 1 :(得分:2)

我遇到了同样的问题,并找到了另一种解决方案。我想在这里征求意见。

我们的想法是动态地为crosstab创建“输出类型”字符串。 plpgsql函数不能返回最终结果,因为该函数需要具有静态返回类型(我们没有)或返回setof record,因此没有优势原始的crosstab函数。因此,我的函数将输出交叉表保存在视图中。同样,尚未采用跨表格式的“pivot”数据输入表取自现有视图或表格。

使用就像这样,使用你的例子(我添加了“fat”字段来说明排序功能):

CREATE TABLE food_fat (name character varying(20) NOT NULL, fat integer);
CREATE TABLE eaten (food character varying(20) NOT NULL, day date NOT NULL);
-- This view will be formatted as cross-table.
-- ORDER BY is important, otherwise crosstab won't merge rows
CREATE TEMPORARY VIEW mymeals AS 
       SELECT day,food,COUNT(*) AS meals FROM eaten 
       GROUP BY day, food ORDER BY day;
SELECT auto_crosstab_ordered('mymeals_cross',
      'mymeals', 'day', 'food', 'meals',  -- what table to convert to cross-table
      'food_fat', 'name', 'fat');         -- where to take the columns from

最后一个语句创建一个如下所示的视图mymeals_cross

SELECT * FROM mymeals_cross;
    day     | bread | cheese | eggs 
------------+-------+--------+------
 2012-06-01 |     3 |      3 |    2
 2012-06-02 |     2 |      1 |    3
(2 rows)

我的实现来了:

-- FUNCTION get_col_type(tab, col)
-- returns the data type of column <col> in table <tab> as string
DROP FUNCTION get_col_type(TEXT, TEXT);
CREATE FUNCTION get_col_type(tab TEXT, col TEXT, OUT ret TEXT)
AS $BODY$ BEGIN
EXECUTE $f$
   SELECT atttypid::regtype::text
   FROM pg_catalog.pg_attribute
   WHERE attrelid='$f$||quote_ident(tab)||$f$'::regclass 
   AND attname='$f$||quote_ident(col)||$f$'
   $f$ INTO ret;
END;
$BODY$ LANGUAGE plpgsql;

-- FUNCTION get_crosstab_type(tab, row, val, cattab, catcol, catord)
--
-- This function generates the output type expression for the crosstab(text, text)
-- function from the PostgreSQL tablefunc module when the "categories" 
-- (cross table column labels) can be looked up in some view or table.
--
-- See auto_crosstab below for parameters
DROP FUNCTION get_crosstab_type(TEXT, TEXT, TEXT, TEXT, TEXT, TEXT);
CREATE FUNCTION get_crosstab_type(tab TEXT, rw TEXT, val TEXT, cattab TEXT, 
       catcol TEXT, catord TEXT, OUT ret TEXT)
AS $BODY$ BEGIN
EXECUTE $f$
    SELECT '"$f$||quote_ident(rw)||$f$" $f$
                   ||get_col_type(quote_ident(tab), quote_ident(rw))||$f$'
           || string_agg(',"'||_values._v||'" $f$
                   ||get_col_type(quote_ident(tab), quote_ident(val))||$f$')
    FROM (SELECT DISTINCT ON(_t.$f$||quote_ident(catord)||$f$) _t.$f$||quote_ident(catcol)||$f$ AS _v
          FROM $f$||quote_ident(cattab)||$f$ _t
          ORDER BY _t.$f$||quote_ident(catord)||$f$) _values
    $f$ INTO ret;
END; $BODY$ LANGUAGE plpgsql;

-- FUNCTION auto_crosstab_ordered(view_name, tab, row, cat, val, cattab, catcol, catord)
--
-- This function creates a VIEW containing a cross-table of input table.
-- It fetches the column names of the cross table ("categories") from
-- another table.
--
-- view_name - name of VIEW to be created
-- tab - input table. This table / view must have 3 columns:
--   "row", "category", "value".
-- row - column name of the "row" column
-- cat - column name of the "category" column
-- val - column name of the "value" column
-- cattab - another table holding the possible categories
-- catcol - column name in cattab to use as column label in the cross table
-- catord - column name in cattab to sort columns in the cross table
DROP FUNCTION auto_crosstab_ordered(TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT);
CREATE FUNCTION auto_crosstab_ordered(view_name TEXT, tab TEXT, rw TEXT,
    cat TEXT, val TEXT, cattab TEXT, catcol TEXT, catord TEXT) RETURNS void
AS $BODY$ BEGIN
EXECUTE $f$
CREATE VIEW $f$||quote_ident(view_name)||$f$ AS
  SELECT * FROM crosstab(
     'SELECT $f$||quote_ident(rw)||$f$,
         $f$||quote_ident(cat)||$f$,
         $f$||quote_ident(val)||$f$
     FROM $f$||quote_ident(tab)||$f$',
     'SELECT DISTINCT ON($f$||quote_ident(catord)||$f$) $f$||quote_ident(catcol)||$f$
      FROM $f$||quote_ident(cattab)||$f$ m 
      ORDER BY $f$||quote_ident(catord)||$f$'
     ) AS ($f$||get_crosstab_type(tab, rw, val, cattab, catcol, catord)||$f$)$f$;
END; $BODY$ LANGUAGE plpgsql;

-- FUNCTION auto_crosstab(view_name, tab, row, cat, val)
--
-- This function creates a VIEW containing a cross-table of input table.
-- It fetches the column names of the cross table ("categories") from
-- DISTINCT values of the 2nd column of the input table.
--
-- view_name - name of VIEW to be created
-- tab - input table. This table / view must have 3 columns:
--   "row", "category", "value".
-- row - column name of the "row" column
-- cat - column name of the "category" column
-- val - column name of the "value" column

DROP FUNCTION auto_crosstab(TEXT, TEXT, TEXT, TEXT, TEXT);
CREATE FUNCTION auto_crosstab(view_name TEXT, tab TEXT, rw TEXT, cat TEXT, val TEXT) RETURNS void
AS $$ BEGIN
    PERFORM auto_crosstab_ordered(view_name, tab, rw, cat, val, tab, cat, cat);
END; $$ LANGUAGE plpgsql;

答案 2 :(得分:1)

代码运行正常。您将收到输出作为动态查询。

CREATE OR REPLACE FUNCTION public.pivotcode(tablename character varying, rowc character varying, colc character varying, cellc character varying, celldatatype character varying)

RETURNS character varying AS

$BODY$

declare

    dynsql1 varchar;
    dynsql2 varchar;
    columnlist varchar;

begin

    -- 1. retrieve list of column names.

    dynsql1 = 'select string_agg(distinct ''_''||'||colc||'||'' '||celldatatype||''','','' order by ''_''||'||colc||'||'' '||celldatatype||''') from '||tablename||';';
    execute dynsql1 into columnlist;

    -- 2. set up the crosstab query

    --create temp table temp as

    dynsql2 = 'select * from crosstab ( ''select '||rowc||','||colc||','||cellc||' from '||tablename||' group by 1,2 order by 1,2'', ''select distinct '||colc||' from '||tablename||' order by 1'' ) as newtable ( '||rowc||' varchar,'||columnlist||' );';

    return dynsql2;

 end

 $BODY$

  LANGUAGE plpgsql VOLATILE

  COST 100;

答案 3 :(得分:0)

这是旋转数据的一个示例 - 在postgreSQL 9中有一个交叉表函数可以执行此操作:http://www.postgresql.org/docs/current/static/tablefunc.html

答案 4 :(得分:0)

我在下面的文章中解决了这个问题:

http://okbob.blogspot.com/2008/08/using-cursors-for-generating-cross.html

简而言之,我使用的函数为查询中的每个结果动态创建下一个查询的值,并返回作为refcursor所需的结果。这解决了我的sql结果部分,现在我需要弄清楚java部分,但这与问题没有多大联系:)