PLSQL 函数如何 return 更新...返回查询的结果?

How can a PLSQL function return a result of an UPDATED...RETURNING query?

我有一个函数需要:

  1. 调用另一个函数
  2. 然后是return更新语句的结果
  3. 原子性不会失败

函数看起来像这样:

CREATE OR REPLACE FUNCTION bal_update_balances(_acct Accounts, _amount INT, _currency INT)
  RETURNS SETOF Balances AS $$
DECLARE
  --   rv SETOF Balances;
BEGIN
  -- STEP 0: initialize balances (make sure they are present in the DB)
  PERFORM bal_initialize_balances(_acct, _currency);

  -- STEP 1: update & return balances
  RETURN QUERY (
    UPDATE Balances
    SET amount   = amount + _amount,
    updated_at = now()
    WHERE id IN (SELECT id
    FROM bal_get_balances(_acct, _currency))
    RETURNING *
  );
END;
$$ LANGUAGE plpgsql;

到目前为止,我已经失败了:

  1. 如果我尝试将 UPDATE..RETURNING 的输出放入一个变量 (rv),然后 return 该变量:
    1. 变量声明失败(rv SETOF Balances 失败 invalid type name "SETOF Balances"
    2. 然后如何把UPDATE..RETURNING的输出变成rv也是个好问题
  2. 如果我尝试做一个 RETURN QUERY 然后它会失败 syntax error at or near "UPDATE"
  3. 如果我尝试将更新语句包装到 select 语句(SELECT * FROM (UPDATE ... RETURNING))中,那么编译器会抱怨嵌套更新语句的 SET 部分(syntax error at or near "SET")
  4. 如果我重写整个事情以迭代 bal_get_balances(_acct, _currency)) 输出(SETOF Balances)并执行 RETURN NEXT 那么问题 1-2 再次出现,我该如何放置输出UPDATE..RETURNING 到 psql 变量?

一般的想法是,我可以 运行 来自主要软件(Ruby,但无关紧要)的这个功能,从交易中作为某种模拟,然后取决于某种复杂的主应用程序中的业务逻辑决定是通过交易,还是根据余额的确切状态将其丢弃——这是迄今为止我想到的将复杂且非常动态的应用程序业务逻辑与 ACID 要求结合起来的唯一方法。

在循环中使用 record 类型的变量:

declare
    r record;
begin
    -- ...
    for r in 
        update balances
        set updated_at = now()
        -- ...
        returning *
    loop
        return next r;
    end loop;
create table t(i serial primary key, x int);
insert into t(x) select random()*10 from generate_series(1,10);

create function f() returns setof t language plpgsql as $$
declare
  r t[];
begin
  -- How to get result into the variable and return it
  with a as (update t set x = x*2 where i > 5 returning *)
    select array_agg(a.*) into r from a;
  return query select * from unnest(r);

  -- How to return result of update (just remove parenthesizes around it)
  return query
    update t set x = x*2 where i > 5 returning *;
end
$$;

select * from f();

http://rextester.com/OUZM15941

只需用 CTE 包装它,如下所示:

t=# CREATE OR REPLACE FUNCTION s161()
  RETURNS SETOF s151 AS $$
DECLARE
BEGIN
  RETURN QUERY (
    with u as (UPDATE s151
    SET t= t||'***'
    RETURNING *
    )
    select * from u
  );
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION
Time: 1.109 ms
t=# begin;
BEGIN
Time: 0.117 ms
t=# select * from s161() limit 3;
       t
---------------
 s141***
 events***
 tg_rep_que***
(3 rows)