2010-11-05 4 views
1

Juste un exemple stupide:Postgres têtes de colonnes dynamiques (d'une autre table)

Table A:

  1. eggs
  2. bread
  3. cheese

Table B (when they are eaten):

  1. Egg | date
  2. Bread | date
  3. Egg | date
  4. cheese | date
  5. Bread | date

Pour purpouses statistiques, je dois avoir des statistiques par jour selon le type de nourriture dans un regard comme celui-ci:

Tableau Statistiques :

   egg | bread | cheese 

date1   2   1   0  

date2   6   4   2 

date3   2   0   0 

J'ai besoin les en-têtes de colonnes pour être dynamique dans le rapport (si de nouveaux sont ajoutés, il devrait apparaître automatiquement).

Une idée comment faire cela dans postgres?

Merci.

Répondre

0

Voici un exemple de pivotant données - dans postgreSQL 9 il y a une fonction crosstab pour ce faire: http://www.postgresql.org/docs/current/static/tablefunc.html

+0

Cela m'a donné un indice qui existe quelque chose, merci. Un autre problème est que ce n'est pas entièrement dynamique, ex. SELECT * FROM crosstab ('...') AS ct (nom de ligne, texte de catégorie 1, texte de catégorie 2); J'ai encore besoin d'entrer manuellement ce qui est à l'intérieur de AS ct (......). Je vais essayer de chercher plus pour une solution sur ce ... – Daniel

0

Je l'ai résolu folowing cet article:

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

En bref, J'ai utilisé la fonction qui pour chaque résultat dans une requête crée dynamiquement les valeurs pour la requête suivante, et renvoie le résultat nécessaire en tant que refcursor. Cela a résolu ma partie de résultat sql, maintenant j'ai besoin de comprendre la partie java, mais ce n'est pas connecté beaucoup à la question :)

2

Je suis tombé sur le même problème, et j'ai trouvé une solution alternative. Je voudrais demander des commentaires ici.

L'idée est de créer dynamiquement la chaîne "type de sortie" pour crosstab. Le résultat final ne peut pas être renvoyé par une fonction plpgsql, car cette fonction doit avoir un type de retour statique (que nous n'avons pas) ou renvoyer setof record, sans avoir d'avantage par rapport à la fonction crosstab d'origine. Par conséquent, ma fonction enregistre le tableau croisé de sortie dans une vue. De même, la table d'entrée des données "pivot" qui ne sont pas encore au format table croisée est prise à partir d'une vue ou d'une table existante.

L'usage serait comme ça, en utilisant votre exemple (j'ai ajouté le « gras » champ pour illustrer la fonction de tri):

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 

La dernière déclaration crée une vue mymeals_cross qui ressemble à ceci:

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

vient ici ma mise en œuvre:

-- 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

Le code i s fonctionne bien. vous recevrez une sortie en tant que requête dynamique.

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

fonction de la réponse Postgres dynamic column headers (from another table) (le travail d'Eric Vallabh Minikel) i amélioré la fonction d'être plus souple et pratique. Je pense que cela pourrait être utile pour d'autres aussi, d'autant plus qu'il ne repose que sur pg/plsql et qu'il n'a pas besoin d'installation d'extensions comme le font d'autres dérivations d'erics (ie, plpython). Testet avec 9.3.5 mais devrait également travailler au moins jusqu'à 9.2.

Améliorations:

  • accord avec les noms de colonnes pivotées contenant des espaces
  • accord avec plusieurs colonnes d'en-tête de rangée
  • accord avec la fonction d'agrégat dans la cellule de pivotement ainsi que la cellule de pivotement non agrégé (dernier paramètre peut être 'sum (cellval)' ainsi que 'cellval' dans le cas où la table/vue sous-jacente fait déjà l'agrégation)
  • auto détecter le type de données de la cellule pivot (pas besoin de passer à la fonction minerai)

useage:

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

code:

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; 
+0

J'essaie de créer un tableau croisé dynamique basé sur les en-têtes Date (datatype: date), à ​​savoir: Produit, 2016-01-31, 2016-02-29 iPhone, 200000, 400 000 Android, 150000, 350000 Cependant, j'obtiens cette erreur: 'function quote_ident (date) n'existe pas, des idées? –

+0

Salut nospam, pourriez-vous également ajouter un paramètre pour ajouter des éléments à la clause where, et si aucun paramètre n'utilise la valeur par défaut 'where 1 = 1'? –