尝试使用 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 INSERT
或 UPDATE
语句中的 RETURNING
子句不会从函数返回。它们只是从函数内部的 SQL 语句返回一个值。使用 INTO
子句告诉 plpgsql 将其写入何处。
您可以DECLARE
一个变量foo
并使用... RETURNING INTO foo
和更高版本的RETURN foo;
。但是由于您只想从函数中返回值,因此请走捷径并使用OUT
parameter 开头。
函数row_to_json()
返回数据类型json
,所以变量(或OUT
参数应该是匹配类型!
并发
在多用户环境中,插入和更新通常容易出现竞争条件。您可能希望使用数据修改 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 返回集合