执行动态交叉表查询
Execute a dynamic crosstab query
我在我的 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');
其中returns(因为函数的return类型是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 );
如何让这个函数不仅能生成动态交叉表的代码,还能执行结果?即,我手动 copy/paste/execute 时的结果是这样的。但我希望它在没有额外步骤的情况下执行:函数应 assemble 动态查询 和 执行它:
编辑 1
这个函数很接近,但我需要它 return 不仅仅是第一条记录的第一列
取自:Are there any way to execute a query inside the string value (like eval) in 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$$)
但是它只是 return 第一条记录的第一列:
eval
----
2015
当实际结果如下所示时:
Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000
你求的不可能。 SQL 是一种严格类型的语言。 PostgreSQL 函数需要在 创建 的 时声明一个 return 类型 (RETURNS ..
)。
一个有限的解决方法是使用多态函数。如果您可以在函数 调用 的 时提供 return 类型。但这从你的问题中看不出来。
- Refactor a PL/pgSQL function to return the output of various SELECT queries
您 可以 return 具有匿名记录的完全动态结果。但是随后您需要在每次调用时提供一个列定义列表。您是如何知道 returned 列的?赶上 22.
有多种解决方法,具体取决于您的需要或可以使用的内容。由于您所有的数据列似乎共享相同的数据类型,我建议 return 一个 array: text[]
。或者您可以 return 文档类型,例如 hstore
或 json
。相关:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
但只使用两个调用可能更简单: 1:让 Postgres 构建查询。 2:执行并检索 returned 行。
- Selecting multiple max() values using a single SQL statement
我根本不会使用 Eric Minikel 在您的问题中提出的功能 。通过恶意格式错误的标识符来防止 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
LANGUAGE plpgsql 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$;
与原始版本相同的函数调用。函数 crosstab()
由必须安装的附加模块 tablefunc
提供。基础知识:
- PostgreSQL Crosstab Query
这可以安全地处理列和 table 名称。请注意对象标识符类型 regclass
和 regtype
的使用。也适用于模式限定名称。
- Table name as a PostgreSQL function parameter
但是,不完全安全 当你传递一个字符串作为表达式执行时(_expr
- cellc
在您的原始查询中)。这种输入在本质上对 SQL 注入是不安全的,永远不应该暴露给一般的 public.
两个类别列表只扫描table一次一次,应该会快一点。
仍然不能 return 完全动态的行类型,因为那是绝对不可能的。
并非完全不可能,您仍然可以执行它(从查询执行字符串和 return SETOF RECORD。
然后你必须指定return记录格式。在这种情况下,原因是规划器需要知道 return 格式才能做出某些决定(想到物化)。
所以在这种情况下,您将执行查询、return 行和 return SETOF RECORD。
例如,我们可以使用包装函数执行类似的操作,但可以将相同的逻辑折叠到您的函数中:
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;
$$;
然后在调用函数时提供记录结构,就像处理交叉表一样。
然后,当您进行所有查询时,您必须像使用 connectby 一样提供记录结构(如(col1 类型、col2 类型等)。
我在我的 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');
其中returns(因为函数的return类型是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 );
如何让这个函数不仅能生成动态交叉表的代码,还能执行结果?即,我手动 copy/paste/execute 时的结果是这样的。但我希望它在没有额外步骤的情况下执行:函数应 assemble 动态查询 和 执行它:
编辑 1
这个函数很接近,但我需要它 return 不仅仅是第一条记录的第一列
取自:Are there any way to execute a query inside the string value (like eval) in 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$$)
但是它只是 return 第一条记录的第一列:
eval
----
2015
当实际结果如下所示时:
Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000
你求的不可能。 SQL 是一种严格类型的语言。 PostgreSQL 函数需要在 创建 的 时声明一个 return 类型 (RETURNS ..
)。
一个有限的解决方法是使用多态函数。如果您可以在函数 调用 的 时提供 return 类型。但这从你的问题中看不出来。
- Refactor a PL/pgSQL function to return the output of various SELECT queries
您 可以 return 具有匿名记录的完全动态结果。但是随后您需要在每次调用时提供一个列定义列表。您是如何知道 returned 列的?赶上 22.
有多种解决方法,具体取决于您的需要或可以使用的内容。由于您所有的数据列似乎共享相同的数据类型,我建议 return 一个 array: text[]
。或者您可以 return 文档类型,例如 hstore
或 json
。相关:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
但只使用两个调用可能更简单: 1:让 Postgres 构建查询。 2:执行并检索 returned 行。
- Selecting multiple max() values using a single SQL statement
我根本不会使用 Eric Minikel 在您的问题中提出的功能 。通过恶意格式错误的标识符来防止 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
LANGUAGE plpgsql 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$;
与原始版本相同的函数调用。函数 crosstab()
由必须安装的附加模块 tablefunc
提供。基础知识:
- PostgreSQL Crosstab Query
这可以安全地处理列和 table 名称。请注意对象标识符类型 regclass
和 regtype
的使用。也适用于模式限定名称。
- Table name as a PostgreSQL function parameter
但是,不完全安全 当你传递一个字符串作为表达式执行时(_expr
- cellc
在您的原始查询中)。这种输入在本质上对 SQL 注入是不安全的,永远不应该暴露给一般的 public.
两个类别列表只扫描table一次一次,应该会快一点。
仍然不能 return 完全动态的行类型,因为那是绝对不可能的。
并非完全不可能,您仍然可以执行它(从查询执行字符串和 return SETOF RECORD。
然后你必须指定return记录格式。在这种情况下,原因是规划器需要知道 return 格式才能做出某些决定(想到物化)。
所以在这种情况下,您将执行查询、return 行和 return SETOF RECORD。
例如,我们可以使用包装函数执行类似的操作,但可以将相同的逻辑折叠到您的函数中:
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;
$$;
然后在调用函数时提供记录结构,就像处理交叉表一样。 然后,当您进行所有查询时,您必须像使用 connectby 一样提供记录结构(如(col1 类型、col2 类型等)。