我可以在 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
子句INSERT
或UPDATE
语句不会从函数返回。它们只是从函数内部的 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/