具有不同 return 类型(和不同的内部查询)的 PL/pgSQL 函数
A PL/pgSQL function with varying return type (and varying inner query)
数据
假设我有以下数据:
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
函数,它有 2 个参数:tbl(取值 my_data1
或 my_data2
)和 order_by(可以是 id
或 val
或空值)。该函数应从 tbl 中指定的 table 中获取所有行,并按 order_by 中指定的列对它们进行排序。
下面是我找到的 2 个解决方案(另请参阅 sqlfiddle)。 问题是哪一个更可取,是否有更好的解决方案。
使用温度的解决方案table
我想出了以下解决方法:
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;
此解决方案是否有任何负面影响,例如创建临时 table 成本高吗?
另一个解决方案(使用pg_typeof)
我还阅读了一篇很棒的 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');
与使用温度的方法相比,此方法是否有任何优势 table?
我对第一个解决方案有两条评论。
首先,在 format()
函数中使用或 %I
或 quote_ident()
,不能同时使用。比较:
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
在这种特殊情况下,第二种解决方案更好。
临时 table 不是特别昂贵,但仍然没有必要。
table 中的数据越多,成本就越重要。
如果您有创建临时 table 的好替代方法,请使用它。
此外,在某些情况下,需要在事务中包含函数调用和 select 查询会有点麻烦。
第二种解决方案很聪明,非常适合手头的任务。
数据
假设我有以下数据:
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
函数,它有 2 个参数:tbl(取值 my_data1
或 my_data2
)和 order_by(可以是 id
或 val
或空值)。该函数应从 tbl 中指定的 table 中获取所有行,并按 order_by 中指定的列对它们进行排序。
下面是我找到的 2 个解决方案(另请参阅 sqlfiddle)。 问题是哪一个更可取,是否有更好的解决方案。
使用温度的解决方案table
我想出了以下解决方法:
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;
此解决方案是否有任何负面影响,例如创建临时 table 成本高吗?
另一个解决方案(使用pg_typeof)
我还阅读了一篇很棒的 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');
与使用温度的方法相比,此方法是否有任何优势 table?
我对第一个解决方案有两条评论。
首先,在 format()
函数中使用或 %I
或 quote_ident()
,不能同时使用。比较:
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
在这种特殊情况下,第二种解决方案更好。 临时 table 不是特别昂贵,但仍然没有必要。 table 中的数据越多,成本就越重要。 如果您有创建临时 table 的好替代方法,请使用它。 此外,在某些情况下,需要在事务中包含函数调用和 select 查询会有点麻烦。
第二种解决方案很聪明,非常适合手头的任务。