Динамические заголовки столбцов Postgres (из другой таблицы) - PullRequest
1 голос
/ 05 ноября 2010

Просто глупый пример:

Таблица A:

  1. яйца
  2. хлеб
  3. сыр

Таблица B (когда они съедены):

  1. Яйцо | дата
  2. Хлеб | дата
  3. Яйцо | дата
  4. сыр | дата
  5. Хлеб | дата

Для статистических целей мне нужно иметь статистику по дате для каждого типа пищи в следующем виде:

Таблица статистики:

             egg   |   bread   |   cheese

date1         2          1            0    

date2         6          4            2

date3         2          0            0

Мне нужно, чтобы заголовки столбцов были динамическими в отчете (если добавляются новые, они должны автоматически появляться).

Есть идеи, как сделать это в postgres?

Спасибо.

Ответы [ 5 ]

3 голосов
/ 22 января 2015

на основе ответа Динамические заголовки столбцов Postgres (из другой таблицы) (работа Эрика Валлабха Миникеля) Я улучшил функцию, чтобы сделать ее более гибкой и удобной. Я думаю, что это может быть полезно и для других, тем более, что оно опирается только на pg / plsql и не требует установки расширений, как это делают другие версии erics (т.е. plpython). Тестирование с 9.3.5, но также должно работать как минимум до 9.2.

Улучшения:

  • иметь дело с именами поворотных столбцов, содержащих пробелы
  • иметь дело с несколько столбцов заголовка строки
  • имеет дело с агрегатной функцией в сводной ячейке, а также с неагрегированной сводной ячейкой (последний параметр может быть «sum (cellval)», а также «cellval» в случае, если базовая таблица / представление уже агрегирует)
  • автоопределение типа данных сводной ячейки (больше не нужно передавать ее функции)

Useage:

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: /2377576/dinamicheskie-zagolovki-stolbtsov-postgres-iz-drugoi-tablitsy
--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;
2 голосов
/ 12 ноября 2012

Я столкнулся с той же проблемой и нашел альтернативное решение. Я хотел бы попросить комментарии здесь.

Идея состоит в том, чтобы создать строку типа вывода для crosstab динамически. Конечный результат не может быть возвращен функцией plpgsql, потому что эта функция должна либо иметь статический тип возвращаемого значения (которого у нас нет), либо возвращать setof record, таким образом, не имея преимущества над исходным crosstab функция. Поэтому моя функция сохраняет выходную кросс-таблицу в виде. Аналогично, входная таблица «сводных» данных, еще не в формате кросс-таблицы, берется из существующего представления или таблицы.

Использование было бы таким, используя ваш пример (я добавил поле "толстый", чтобы проиллюстрировать функцию сортировки):

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;
1 голос
/ 28 марта 2014

Код работает нормально.вы получите вывод в виде динамического запроса.

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;
0 голосов
/ 09 ноября 2010

Я решил это следующим образом:

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

Короче говоря, я использовал функцию, которая для каждого результата в запросе динамически создает значения для следующего запроса и возвращаетРезультат нужен как прекурсор.Это решило мою часть результата sql, теперь мне нужно выяснить Java-часть, но это не сильно связано с вопросом:)

0 голосов
/ 05 ноября 2010

Это пример поворота данных - в postgreSQL 9 для этого есть функция кросс-таблицы: http://www.postgresql.org/docs/current/static/tablefunc.html

...