尝试使用 RETURNING 和更新/插入从 plpgsql 函数返回时出错

Posted

技术标签:

【中文标题】尝试使用 RETURNING 和更新/插入从 plpgsql 函数返回时出错【英文标题】:Error when trying to return from plpgsql function using RETURNING with update/insert 【发布时间】:2013-11-20 00:15:43 【问题描述】:

我能够在我的 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

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

有人有什么想法吗?还是我在这里找错树了。

【问题讨论】:

只是为了解释错误消息,您要求 INSERT 返回数据,但没有提供任何要返回的变量。要么您需要该数据,然后将其分配给一个变量,要么您不需要它,然后删除 returning 子句。 【参考方案1】:

它可以像这样工作:

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 子句不会从函数返回。它们只是从函数内部的 SQL 语句返回一个值。使用 INTO 子句告诉 plpgsql 将其写入何处。

您可以DECLARE 一个变量foo 并使用... RETURNING INTO foo 和更高版本的RETURN foo;。但是由于您只想从函数中返回值,因此请走捷径并使用OUT parameter 开头。

函数row_to_json()返回数据类型json,所以变量(或OUT参数应该是匹配类型!

1234563李>

并发

在多用户环境中,插入和更新通常容易出现竞争条件。您可能希望使用数据修改 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 函数中。但还有更多……考虑这些相关问题:

Is SELECT or INSERT in a function prone to race conditions? Atomic UPDATE .. SELECT in Postgres Deadlocks in PostgreSQL when running UPDATE

【讨论】:

感谢您的回复,非常详细。几个 cmets:由于我用于数据库访问的驱动程序似乎不支持 OUT 参数,看来我将不得不使用您的 RETURNING INTO 变量并返回它的建议。 我不确定在这种特殊情况下我是否必须过多担心并发性,但我不知道您提出的解决方案,所以谢谢! @ifross:您只需要运行一次创建函数脚本。如果您的驱动程序不是最新的,请执行原始 SQL。 OUT 参数是在很久以前的 Postgres 8.1 中引入的。

以上是关于尝试使用 RETURNING 和更新/插入从 plpgsql 函数返回时出错的主要内容,如果未能解决你的问题,请参考以下文章

防止 NHibernate 将 Returning 子句添加到生成的插入语句中

如何从具有 RETURNING 子句的动态 SQL 返回集合

使用 PL/SQL 在触发器中中止插入/更新操作

如何在 DQL 中使用 RETURNING 更新查询

使用 RETURNING INTO 子句和 BULK COLLECT 时如何返回整行

如何从另一个 php 文件中使用 PostgreSql 的 CURRVAL / RETURNING?