在存储函数中使用预准备语句

时间:2012-10-03 11:20:27

标签: postgresql prepared-statement plpgsql

我在数据库中有一个表:

create table store (
    ...
    n_status        integer not null,
    t_tag           varchar(4)
    t_name          varchar,
    t_description   varchar,
    dt_modified     timestamp not null,
    ...
);

在我的存储函数中,我需要多次对该表执行相同的select

select * from store
where n_place_id = [different values]
and t_tag is not null
and n_status > 0
and (t_name ~* t_search or t_description ~* t_search)
order by dt_modified desc
limit n_max;

此处,t_searchn_max是存储函数的参数。我认为为此使用准备好的声明是有意义的,但我遇到了奇怪的问题。这就是我所拥有的:

create or replace function fn_get_data(t_search varchar, n_max integer)
  returns setof store as
$body$
declare
    resulter        store%rowtype;
    mid             integer;
begin
    prepare statement prep_stmt(integer) as
        select *
          from store
         where n_place_id = $1
           and (t_name ~* t_search or t_description ~* t_search)
      order by dt_modified
         limit n_max;

    for mid in
        (select n_place_id from ... where ...)
    loop
        for resulter in
            execute prep_stmt(mid)
        loop
            return next resulter;
        end loop;
    end loop;
end;$body$
  language 'plpgsql' volatile;

然而,当我实际使用

运行该功能时
select * from fn_get_data('', 30)

我收到此错误:

ERROR:  column "t_search" does not exist
LINE 3:   and (t_name ~* t_search or t_description ~* t_search)
                         ^
QUERY:  prepare prep_stmt(integer) as
        select * from store where n_status > 0 and t_tag is not null and n_museum = $1
        and (t_name ~* t_search or t_description ~* t_search)
        order by dt_modified desc limit maxres_free

好吧,也许它在预备语句中不喜欢外部变量,所以我将其改为

prepare prep_stmt(integer, varchar, integer) as
select * from store where n_status > 0 and t_tag is not null and n_museum = $1
and (t_name ~* $2 or t_description ~* $2)
order by dt_modified desc limit $3

...

for resulter in
    execute prep_stmt(mid, t_search, n_max)

...

这次我得到了一个不同的错误:

ERROR:  function prep_stmt(integer, character varying, integer) does not exist
LINE 1: SELECT prep_stmt(mid, t_search, n_max)
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
QUERY:  SELECT prep_stmt(mid, t_search, n_max)

我在这里缺少什么?

编辑我在顶部添加了相关的表格结构。

4 个答案:

答案 0 :(得分:6)

在我看来,动态SQL的PL / PgSQL EXECUTE胜过准备语句的常规SQL EXECUTE

代码:

create or replace function prep_test() returns void as $$
begin
    PREPARE do_something AS SELECT 1;
    EXECUTE do_something;
end;
$$ LANGUAGE 'plpgsql';

测试:

regress=# select prep_test(1);
ERROR:  column "do_something" does not exist
LINE 1: SELECT do_something
               ^
QUERY:  SELECT do_something
CONTEXT:  PL/pgSQL function "prep_test" line 4 at EXECUTE statement

在PL / PgSQL之外它运行良好:

regress=# EXECUTE do_something;
?column?
----------
        1
(1 row)

我不确定你如何在PL / PgSQL中执行预备语句。

出于兴趣,为什么您是否尝试在PL / PgSQL中使用预准备语句?无论如何,计划都会为PL / PgSQL准备和缓存,它会自动发生。

答案 1 :(得分:4)

有一种方法可以在函数中EXECUTE准备一个语句,但是就像接受的答案所说的那样,你通常不会在函数中这样做,因为函数已经存储了它的计划。 / p>

话虽如此,仍然存在需要在函数中使用预准备语句的用例。我的用例是当为不同的用户使用多个模式时,模式包含类似命名的表,并且您希望使用相同的函数根据search_path设置的内容访问其中一个表。在这种情况下,由于函数存储其计划的方式,在更改search_path后使用相同的函数会导致事务中断。我已经说过,这个问题有两个解决方案。第一种是使用EXECUTE '<Your query as a string here>'。但是对于大型查询来说这可能会非常难看,因此使用第二种方法的原因是PREPARE

因此,关于'为什么'你想要做到这一点的背景,这里是如何:

CREATE OR REPLACE FUNCTION prep_test()
  RETURNS void AS $$
BEGIN
  PREPARE do_something AS SELECT 1;
  EXECUTE 'EXECUTE do_something;';
END;
$$ LANGUAGE plpgsql;

尽管添加一些保护措施以防止它破坏可能符合您的最佳利益。类似的东西:

CREATE OR REPLACE FUNCTION prep_test()
  RETURNS void AS $$
BEGIN
  IF (SELECT count(*) FROM pg_prepared_statements WHERE name ilike 'do_something') > 0 THEN
    DEALLOCATE do_something;
  END IF;

  PREPARE do_something AS SELECT 1;
  EXECUTE 'EXECUTE do_something;';

  DEALLOCATE do_something;
END;
$$ LANGUAGE plpgsql;

同样,那些认为他们想要这样做的人,通常可能不应该这样做,但对于那些需要它的情况,你就是这样做的。

答案 2 :(得分:1)

你可以在PLPGSQL中使用这样的EXECUTE语句:

select magicvalue into str_execute from magicvalues where magickey = ar_requestData[2];
EXECUTE str_execute into str_label USING ar_requestData[3], ar_requestData[4]::boolean, ar_requestData[5]::int, ar_requestData[6];

这是我在我的应用程序中使用的代码。 ar_requestData是一个包含文本值的数组。 在表magicvalues我存储像准备语句之类的东西。 select语句例如是:

insert into classtypes(label, usenow, ranking, description) values($1,$2,$3,$4) returning label'

亲切的问候,

Loek Bergman

答案 3 :(得分:0)

plpgsql中不允许使用PREPARE语句。您可以拼接函数内的所有语句,然后使用动态执行。这是一个例子。

create or replace function sp_test(f_total int) returns void as $ytt$
declare v_sql text;
declare i int;
begin
  v_sql:='prepare ytt_s1 (int,timestamp) as select * from tbl1 where id = $1 and log_time = $2;';
  while i < f_total
  loop
    v_sql:=v_sql||'execute ytt_s1('||i||',now());';
    i := i + 1;
  end loop;
  v_sql:=v_sql||'deallocate ytt_s1;';
  execute v_sql;
end;
$ytt$ language plpgsql;