尝试使用带有update / insert的RETURNING从plpgsql函数返回时出错

时间:2013-11-20 00:15:44

标签: json postgresql sql-update plpgsql sql-insert

我可以在Postgresql 9.3数据库中创建以下函数:

create or replace function upsert_expense(
        p_id integer,
        p_amount integer,
        p_payer_id integer,
        p_category category_t,
        p_description text)
    returns text as
$$
begin
    if p_id = 0 then
        insert into expenses(amount, payer_id, category, description)
            values (p_amount, p_payer_id, p_category, p_description)
                returning row_to_json(expenses.*);
    end if;
    update expenses set
            amount=p_amount,
            payer_id=p_payer_id,
            category=p_category,
            description=p_description
        where id=p_id
            returning row_to_json(expenses.*);
end;
$$
language plpgsql;

当我尝试调用该函数时会出现问题:

select upsert_expense(1,1,1,'groceries','description');

我收到以下错误:

ERROR:  query has no destination for result data

我认为这是因为“return”关键字的使用不当,我可能会通过使用“pg_get_serial_sequence”得到相同的结果,但我觉得使用“return”会更优雅。

有没有人有任何想法?或者我在这里咆哮错误的树。

1 个答案:

答案 0 :(得分:3)

它可以像这样工作:

CREATE OR REPLACE FUNCTION upsert_expense(
       p_id integer
      ,p_amount integer
      ,p_payer_id integer
      ,p_category category_t
      ,p_description text
      ,OUT p_expenses json)
-- RETURNS json  -- not needed, since we use OUT parameter
AS 
$func$
BEGIN
IF p_id = 0 THEN
   INSERT INTO expenses(amount, payer_id, category, description)
   VALUES (p_amount, p_payer_id, p_category, p_description)
   RETURNING row_to_json(expenses.*)
   INTO   p_expenses;
ELSE
   UPDATE expenses
   SET    amount = p_amount
         ,payer_id = p_payer_id
         ,category = p_category
         ,description = p_description
   WHERE id = p_id
   RETURNING row_to_json(expenses.*)
   INTO   p_expenses;
END IF;

-- No RETURN needed since we use OUT parameter

END
$func$  LANGUAGE plpgsql;

重点

  • SQL INSERTUPDATE语句中的RETURNING子句不会从函数返回。它们只是从函数中的里面返回一个值。使用INTO子句告诉plpgsql将其写入的位置。

  • 您可以DECLARE变量foo并使用... RETURNING INTO foo及更高版本RETURN foo;。但是,由于您只想从函数中返回值,请使用快捷方式并使用OUT parameter开头。

  • 函数row_to_json()返回数据类型json,因此变量(或OUT参数应为匹配类型!

  • 由于您实际上没有从具有SQL RETURNING子句的函数返回,因此需要重新调整IF逻辑,以免每次都运行UPDATE

并发

在多用户环境中,插入和更新通常容易出现竞争条件。您可能希望使用数据修改CTE并可能锁定或更多。

Data-modifying CTE

这与您的函数大致相同,不同之处在于它根据匹配id的存在来决定是自动更新还是插入,同时使用id = 0显式指示您的函数:

WITH values(id, amount, payer_id, category, description) AS (
   SELECT 1, 2, 3, 'some_val'::category_t, 'some text'  -- your values here
   )
, upd AS (
   UPDATE expenses e
   SET    amount = v.amount
         ,payer_id = v.payer_id
         ,category = v.category
         ,description = v.description
   FROM   values v
   WHERE  e.id = v.id
   RETURNING row_to_json(e.*)
   )
, ins AS (
   INSERT INTO expenses
         (amount, payer_id, category, description)
   SELECT amount, payer_id, category, description -- add id?
   FROM   values
   WHERE  NOT EXISTS (SELECT 1 FROM upd) -- no row found in update
   RETURNING row_to_json(expenses.*)
   )
SELECT * FROM upd
UNION ALL
SELECT * FROM ins; -- one row guaranteed

最大限度地缩短比赛条件的时间段。您也可以将其包装到SQL或plpgsql函数中。但还有更多......请考虑以下相关问题: