Выполнить динамический перекрестный запрос
я реализовал эту функцию в моей базе данных 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 );
как я могу заставить эту функцию не только генерировать код для динамической перекрестной таблицы, но и выполнить результат? То есть, результат, когда я вручную копирую / вставляю / выполняю, таков. Но я хочу, чтобы он выполнялся без этого дополнительного шага: функция должна собрать динамический запрос и выполнить:
изменить 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.