PL / pgSQL函数具有不同的返回类型(和不同的内部查询)

时间:2016-01-31 19:43:18

标签: sql performance postgresql plpgsql dynamic-sql

数据

假设我有以下数据:

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_data1my_data2)和 order_by (可以是idval或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?

另一种解决方案(使用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');

这种方法与使用临时表的方法相比有什么优势吗?

1 个答案:

答案 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查询的需要可能有点麻烦。

第二种解决方案很聪明,非常适合手头的任务。