递归PostgreSQL函数失败,并且“游标已在使用中”消息

时间:2017-02-03 00:50:35

标签: sql postgresql recursion cursor postgresql-9.6

我有一个递归的PL / PgSQL函数,它使用绑定的参数化游标,如下所示:

create or replace function test_cursor(rec boolean) returns void as $$
declare
    cur cursor(a int) for select * from generate_series(1,a);
begin
    for c in cur(3) loop
        if rec then
            perform test_cursor(false);
        end if;
    end loop;
end;
$$ language plpgsql;

当函数以递归方式调用自身时,它会报告错误:

select test_cursor(true)

Code: 42P03, ErrorMessage: cursor "cur" already in use

显然,我的光标范围不仅限于单个函数调用。在谷歌搜索解决方法之后,我在邮件列表档案中找到了这个message,其中提到未绑定的游标没有这个限制,即:

declare
     mycursor refcursor;
begin
     open mycursor for ...;
end;

但是我看不出如何参数化未绑定的游标。另外,我不能将for...loop与未绑定的游标一起使用:

-- 42601: cursor FOR loop must use a bound cursor variable
create or replace function test_cursor(rec boolean) returns void as $$
declare
    cur refcursor;
begin
    open cur for select * from generate_series(1,3);
    for c in cur loop
        if rec then
            perform test_cursor(false);
        end if;
    end loop;
    close cur;
end;
$$ language plpgsql;

有人可以提出另一种方法吗?

PS。我正在移植大量使用递归和参数化游标的Oracle存储过程。转换似乎很简单,直到我用全局范围的游标来解决这个问题。

1 个答案:

答案 0 :(得分:3)

我刚刚找到了一个对我来说很奇怪的解决方法,但似乎仍然有用。我不确定它是否有任何缺点,但在我的情况下,唯一的选择是手动重写相当多的代码,所以我想我会尝试一下。

解决方案不是限制打开游标的范围,而是随机化其公共可见名称(门户名称),以便每次重新输入我的函数时,我都会得到一个新的门户名称:

create or replace function test_cursor(rec boolean default true) returns void as $$
declare
    cur cursor(a int) for select * from generate_series(1,a);
begin
    -- assign a random string as a portal name
    -- before iterating over the cursor
    cur := random_portal_name();

    for c in cur(3) loop
        if rec then
            perform test_cursor(false);
        end if;
    end loop;
end;
$$ language plpgsql;

有很多方法可以获得随机字符串:从序列中获取下一个值,生成UUID,仅举几例。我自己写了一个辅助函数,为此目的创建一个临时(会话范围)序列:

create or replace function random_portal_name() returns varchar as $$
begin
    create temp sequence if not exists portal_names;
    return 'portal$' || nextval('portal_names');
end;
$$ language plpgsql;