假设我有以下数据:
create temp table my_data1 (
id serial, val text
);
create temp table my_data2 (
id serial, val int
);
insert into my_data1(id, val)
values (default, 'a'), (default, 'c'), (default, 'd'), (default, 'b');
insert into my_data2(id, val)
values (default, 1), (default, 3), (default, 4), (default, 2);
我想写一个plpgsql
函数,它有两个参数: tbl (取值my_data1
或my_data2
)和 order_by (可以是id
或val
或null)。该函数应从 tbl 中指定的表中获取所有行,并按 order_by 中指定的列对它们进行排序。
下面我找到了2个解决方案(另见sqlfiddle)。 问题是哪一个更可取,如果有更好的解决方案。
我提出了以下解决方法:
create function my_work(tbl text, order_by text default null)
returns text as
$my_work$
declare
q text;
begin
q := 'select * from ' || quote_ident(tbl);
if order_by is not null then
q := q || ' order by ' || quote_ident(order_by);
end if;
return q;
end
$my_work$ language plpgsql;
create function my_fetch(_query text, into_table text)
returns void as
$my_fetch$
begin
execute format($$
create temp table %I
on commit drop
as %s
$$, quote_ident(into_table), _query);
end
$my_fetch$ language plpgsql;
然后仍然执行以下行(最好用'begin / commit'包围):
select my_fetch(my_work('my_data1','id'), 'my_tmp');
select * from my_tmp;
此解决方案是否存在任何负面影响,例如正在创建临时表costy?
我还阅读了很多关于动态查询的各种方法的post。根据那里提到的选项,以下似乎是我的情况的最佳解决方案:
create or replace function not_my_work(_tbl_type anyelement, order_by text default null)
returns setof anyelement as
$func$
declare
q text;
begin
q := format('
select *
from %s
', pg_typeof(_tbl_type));
if order_by is not null then
q := q || ' order by ' || quote_ident(order_by);
end if;
return query execute q;
end
$func$ language plpgsql;
select not_my_work(null::my_data1, 'id');
这种方法与使用临时表的方法相比有什么优势吗?
答案 0 :(得分:1)
我对第一个解决方案有两点评论。
首先,在%I
函数中使用或quote_ident()
或format()
,而不是两者。比较:
with q(s) as (
values ('abba'), ('ABBA')
)
select
quote_ident(s) ok1,
format('%I', s) ok2,
format('%I', quote_ident(s)) bad_idea
from q;
ok1 | ok2 | bad_idea
--------+--------+------------
abba | abba | abba
"ABBA" | "ABBA" | """ABBA"""
(2 rows)
其次,您不需要两个功能:
create or replace function my_select(into_table text, tbl text, order_by text default null)
returns void as $function$
declare
q text;
begin
q := 'select * from ' || quote_ident(tbl);
if order_by is not null then
q := q || ' order by ' || order_by;
end if;
execute format($$
create temp table %I
on commit drop
as %s
$$, into_table, q);
end
$function$ language plpgsql;
begin;
select my_select('my_tmp', 'my_data1', 'id');
select * from my_tmp;
commit;
BEGIN
my_select
-----------
(1 row)
id | val
----+-----
1 | a
2 | c
3 | d
4 | b
(4 rows)
COMMIT
在这种特殊情况下,第二种解决方案更好。 临时桌不是特别昂贵,但仍然没有必要。 表中的数据越多,成本就越重要。 如果您有一个很好的替代方法来创建临时表,请使用它。 此外,在某些情况下,在事务中包含函数调用和select查询的需要可能有点麻烦。
第二种解决方案很聪明,非常适合手头的任务。