json - 尝试使用带有更新/插入的 RETURNING 从 plpgsql 函数返回时出错

标签 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

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

有人有什么想法吗?或者我在这里叫错了树。

最佳答案

它可以这样工作:

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 中的 RETURNING 子句 INSERTUPDATE语句不会从函数返回。它们只是从函数内部的 SQL 语句返回一个值。使用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 函数中。但还有更多......考虑这些相关问题:

关于json - 尝试使用带有更新/插入的 RETURNING 从 plpgsql 函数返回时出错,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20084953/

相关文章:

sql - 连接两个表但删除另一个表中已存在的行

php - mySQL PHP UPDATE文件位置获取三

c# - C#中如何获取JSON中的值?

java - 从 jsonobject 中删除行

ruby-on-rails - Rails 5 迁移数据类型问题

sql - PostgreSQL 触发器函数获取堆栈并且不更新表

sql - 重命名 PostgreSQL 中的多个列

mysql - cakePHP 更新表字段

json - 解码 JSON 映射,其中 key 是非内置类型

c# - ASP.NET Core 6 - 如何在不反序列化的情况下通过 MapPost() 获取 JSON 输入?