【问题标题】:Using prepared statement in stored function在存储函数中使用准备好的语句
【发布时间】:2012-10-03 11:20:26
【问题描述】:

我在数据库中有一个表:

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

好的,可能它不喜欢prepared statement中的外部变量,所以我把它改成了

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)

我在这里错过了什么?

EDIT我在顶部添加了相关的表结构。

【问题讨论】:

  • 显示的代码声明了一个带有 2 个参数的函数 fn_get_data(),但您使用 1 个参数调用它,因此实际调用的函数不是您显示的函数。在 psql 中尝试\df fn_get_data
  • @DanielVérité 你好?请仔细阅读问题。我的问题不在于调用该函数。我正在声明一个准备好的声明,由于某种原因,postgres 没有正确解释。
  • 阅读您的错误消息:prepare 抱怨以select * from store where n_status > 0 开头的查询,而您显示的代码甚至没有引用n_status。再说一遍:你没有执行你认为的代码。
  • @DanielVérité 好吧,也许我的问题不是很清楚。我用更多信息更新了它。您所说的完全无关紧要,尤其是考虑到更新的问题。我正在执行我认为的代码。我从一个完全正常工作的存储函数开始,我要做的就是提取一个重复的select,并使用一个准备好的语句代替它使用select

标签: postgresql prepared-statement plpgsql


【解决方案1】:

在我看来,动态 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 准备和缓存的,它会自动发生。

【讨论】:

  • 是的,看起来就是这样。正如我所说,我必须在一个循环中执行完全相同的 SQL,而 where 子句中只有一个参数不同。如果计划在执行之前为整个存储函数缓存,那么,我猜,使用准备好的语句是没有意义的。谢谢。
  • 啊哈!在postgres documentation 中找到这个注释:注释: PL/pgSQL EXECUTE 语句与PostgreSQL 服务器支持的EXECUTE SQL 语句无关。服务器的 EXECUTE 语句不能直接在 PL/pgSQL 函数中使用(也不需要)。
  • 我完全同意您通常不想做的事情,因为该函数已经准备好并缓存了它的计划,但在极少数情况下您确实想要这样做能够在函数中使用准备好的语句,这是一种方法。我在下面的回答显示了如何(并给出了原因)。 stackoverflow.com/a/27995157/2555692
  • pl/pgsql 中带有EXECUTE...format...USING 的动态查询是否也会自动准备好?按照我的理解,在动态查询的情况下,您必须使用EXECUTE...format...USING,否则您将面临风险。只有具有固定值的静态查询才会自动准备。再说一次,我可能错了。仍在搜索...
【解决方案2】:

有一种方法可以在函数中 EXECUTE 准备好的语句,但就像接受的答案所说的那样,您通常不希望在函数中执行此操作,因为函数已经存储了它的计划。

话虽如此,仍然存在需要在函数中使用准备好的语句的用例。我的用例是为不同的用户使用多个模式,其中模式包含名称相似的表,并且您希望使用相同的函数根据 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;

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

【讨论】:

  • 这是dont try this at home, we are professionals.的IT版本
【解决方案3】:

您可以在 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 是一个包含文本值的数组。 在表魔法值中,我是否存储了准备好的语句之类的东西。 select语句例如:

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

致以诚挚的问候,

洛克伯格曼

【讨论】:

  • 这将执行动态 SQL,有效地在循环的每次迭代中重新构建计划,这将比按原样执行 SQL 慢得多。这将完全扼杀准备好的声明的整个想法。我认为您错过了我的问题的重点。
  • postgresql 中的函数就像一个预处理语句。 Postgresql 自动准备语句。您可以帮助规划器将函数设置为稳定或不可变,以便优化执行。
  • @AleksG postgresql 中的函数本身就像一个准备好的语句。 Postgresql 自动准备语句。您可以帮助计划者将函数设置为稳定或不可变,以便优化执行。请在archives.postgresql.org/pgsql-sql/2006-06/msg00159.php 记笔记。此外,我还希望我向您展示“使用 ...”的语法,因为这对您的循环很有帮助。
【解决方案4】:

在 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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-11
    • 2018-02-21
    相关资源
    最近更新 更多