Dynamically generate columns for crosstab in PostgreSQL

后端 未结 4 527
慢半拍i
慢半拍i 2020-11-27 05:12

I am trying to create crosstab queries in PostgreSQL such that it automatically generates the crosstab columns instead of hardcoding it. I have wri

4条回答
  •  清酒与你
    2020-11-27 05:31

    @erwin-brandstetter: The return type of the function isn't an issue if you're always returning a JSON type with the converted results.

    Here is the function I came up with:

    CREATE OR REPLACE FUNCTION report.test(
        i_start_date TIMESTAMPTZ,
        i_end_date TIMESTAMPTZ,
        i_interval INT
        ) RETURNS TABLE (
        tab JSON
        ) AS $ab$
    DECLARE
        _key_id TEXT;
        _text_op TEXT = '';
        _ret JSON;
    BEGIN
        -- SELECT DISTINCT for query results
        FOR _key_id IN
        SELECT DISTINCT at_name
          FROM report.company_data_date cd 
          JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id 
          JOIN report.amount_types at ON cda.amount_type_id  = at.id 
         WHERE date_start BETWEEN i_start_date AND i_end_date
           AND interval_type_id = i_interval
        LOOP
        -- build function_call with datatype of column
            IF char_length(_text_op) > 1 THEN
                _text_op := _text_op || ', ' || _key_id || ' NUMERIC(20,2)';
            ELSE
                _text_op := _text_op || _key_id || ' NUMERIC(20,2)';
            END IF;
        END LOOP;
        -- build query with parameter filters
        RETURN QUERY
        EXECUTE '
            SELECT array_to_json(array_agg(row_to_json(t)))
              FROM (
            SELECT * FROM crosstab(''SELECT date_start, at.at_name,  cda.amount ct 
              FROM report.company_data_date cd 
              JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id 
              JOIN report.amount_types at ON cda.amount_type_id  = at.id 
             WHERE date_start between $$' || i_start_date::TEXT || '$$ AND $$' || i_end_date::TEXT || '$$ 
               AND interval_type_id = ' || i_interval::TEXT || ' ORDER BY date_start'') 
                AS ct (date_start timestamptz, ' || _text_op || ')
                 ) t;';
    END;
    $ab$ LANGUAGE 'plpgsql';
    

    So, when you run it, you get the dynamic results in JSON, and you don't need to know how many values were pivoted:

    select * from report.test(now()- '1 week'::interval, now(), 1);
                                                                                                                         tab                                                                                                                      
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     [{"date_start":"2015-07-27T08:40:01.277556-04:00","burn_rate":0.00,"monthly_revenue":5800.00,"cash_balance":0.00},{"date_start":"2015-07-27T08:50:02.458868-04:00","burn_rate":34000.00,"monthly_revenue":15800.00,"cash_balance":24000.00}]
    (1 row)
    

    Edit: If you have mixed datatypes in your crosstab, you can add logic to look it up for each column with something like this:

      SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) AS data_type 
        FROM pg_attribute a 
        JOIN pg_class b ON (a.attrelid = b.relfilenode) 
        JOIN pg_catalog.pg_namespace n ON n.oid = b.relnamespace 
       WHERE n.nspname = $$schema_name$$ AND b.relname = $$table_name$$ and a.attstattarget = -1;"
    

提交回复
热议问题