Выполнить динамический перекрестный запрос

я реализовал эту функцию в моей базе данных Postgres: http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/

вот функция:

create or replace function xtab (tablename varchar, rowc varchar, colc varchar, cellc varchar, celldatatype varchar) returns varchar language plpgsql as $$
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
    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 ct (
 '||rowc||' varchar,'||columnlist||'
 );';
    return dynsql2;
end
$$;

Итак, теперь я могу вызвать функцию:

select xtab('globalpayments','month','currency','(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)','text');

который возвращает (потому что возвращаемый тип функции-varchar):

select * from crosstab (
   'select month,currency,(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)
    from globalpayments
    group by 1,2
    order by 1,2'
 , 'select distinct currency
    from globalpayments
    order by 1'
   )  as ct ( month varchar,CAD text,EUR text,GBP text,USD text );

как я могу заставить эту функцию не только генерировать код для динамической перекрестной таблицы, но и выполнить результат? То есть, результат, когда я вручную копирую / вставляю / выполняю, таков. Но я хочу, чтобы он выполнялся без этого дополнительного шага: функция должна собрать динамический запрос и выполнить:

query result

изменить 1

эта функция приближается, но мне нужно, чтобы она возвращала больше, чем просто первый столбец первой записи

взято из: есть ли способ, чтобы выполнить запрос внутри строкового значения (например, eval) в PostgreSQL?

create or replace function eval( sql  text ) returns text as $$
declare
  as_txt  text;
begin
  if  sql is null  then  return null ;  end if ;
  execute  sql  into  as_txt ;
  return  as_txt ;
end;
$$ language plpgsql

использование: select * from eval($$select * from analytics limit 1$$)

он просто возвращает первый столбец первой записи :
eval
----
2015

когда фактический результат выглядит так:

Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000

2 ответов


то, что вы просите невозможно. SQL-строго типизированный язык. Функции PostgreSQL должны объявить возвращаемый тип (RETURNS ..) в времени создание.

ограниченный способ обойти это с помощью полиморфных функций. Если вы можете указать тип возврата в времени функции вызов. Но это не очевидно из вашего вопрос.

вы можете возврат полностью динамического результата с анонимными записями. Но тогда вам необходимо предоставить список определений столбцов с каждым вызовом. И откуда вы знаете о возвращенных столбцах? Поймать 22.

существуют различные обходные пути, в зависимости от того, что вам нужно, или может работать. Поскольку все ваши данные столбцы, похоже, имеют один и тот же тип данных, я предлагаю вернуть массив: text[]. Или вы можете вернуть тип документа, например hstore или json. По теме:

но может быть проще просто использовать два вызова: 1: пусть Postgres построит запрос. 2: Выполнить и получить возвращенные строки.


я бы не использовал функцию от Эрика Миникеля, представленную в вашем вопросе на всех. Это небезопасно против SQL-инъекции посредством злонамеренно искаженных идентификаторов. Использовать format() для построения строк запроса, если не выполняется устаревшая версия старше Postgres 9.1.

более короткая и чистая реализация может выглядеть так:

CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
                              , _expr text  -- still vulnerable to SQL injection!
                              , _type regtype)
  RETURNS text AS
$func$
DECLARE
   _cat_list text;
   _col_list text;
BEGIN

-- generate categories for xtab param and col definition list    
EXECUTE format(
 $$SELECT string_agg(quote_literal(x.cat), '), (')
        , string_agg(quote_ident  (x.cat), %L)
   FROM  (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
 , ' ' || _type || ', ', _cat, _tbl)
INTO  _cat_list, _col_list;

-- generate query string
RETURN format(
'SELECT * FROM crosstab(
   $q$SELECT %I, %I, %s
      FROM   %I
      GROUP  BY 1, 2  -- only works if the 3rd column is an aggregate expression
      ORDER  BY 1, 2$q$
 , $c$VALUES (%5$s)$c$
   ) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr  -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type
);

END
$func$ LANGUAGE plpgsql;

такой же вызов функции как ваша первоначальная версия. Функция crosstab() обеспечивается дополнительным модулем tablefunc, который должен быть установлен. Основы:

это безопасно обрабатывает имена столбцов и таблиц. Обратите внимание на использование типы идентификаторов объектов regclass и regtype. Также работает для имен со схемой.

, это не совсем безопасный при передаче строки для выполнения в виде выражения (_expr - cellc в исходном запросе). Этот вид ввода по своей сути небезопасен для SQL-инъекции и никогда не должен подвергаться общему общественный.

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

все еще не может вернуть полностью динамические типы строк, так как это строго невозможно.


Не совсем невозможно, вы все равно можете выполнить его (из запроса выполнить строку и вернуть запись SETOF.

затем вы должны указать формат записи возврата. Причина в этом случае заключается в том, что планировщик должен знать формат, прежде чем он может принимать определенные решения (материализация приходит на ум).

поэтому в этом случае вы должны выполнить запрос, вернуть строки и вернуть запись SETOF.

например, мы могли бы сделать что-то подобное с помощью функции-оболочки, но та же логика может быть сложена в вашу функцию:

CREATE OR REPLACE FUNCTION crosstab_wrapper
(tablename varchar, rowc varchar, colc varchar, 
 cellc varchar, celldatatype varchar) 
returns setof record language plpgsql as $$
    DECLARE outrow record;
    BEGIN
       FOR outrow IN EXECUTE xtab(, , , , )
       LOOP
           RETURN NEXT outrow
       END LOOP;
    END;
 $$;

затем вы предоставляете структуру записи при вызове функции так же, как и в перекрестной таблице. Затем, когда вы все запрос, вам придется предоставить структуру записи (как (тип col1, тип col2 и т. д.), Как вы делаете с connectby.