Use the RETURNING
clause. You can combine it with RETURN QUERY ...
CREATE OR REPLACE FUNCTION upsert_t2(d text, sys int, val int, p int, inter bool)
RETURNS SETOF test_table AS
$func$
BEGIN
RETURN QUERY
UPDATE test_table t
SET description = d
,code = sys
,val_id = val
,provider_id = p
,connect = inter
WHERE t.code = sys
AND t.val_id = val
AND t.provider_id = p
RETURNING t.*;
IF FOUND THEN
RETURN;
END IF;
BEGIN
RETURN QUERY
INSERT INTO test_table (description, code, val_id, provider_id, connect)
VALUES (d, sys, val, p, inter)
RETURNING *;
EXCEPTION WHEN UNIQUE_VIOLATION THEN
END;
RETURN;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM upsert_t2(...)
Reply to comment
I would try to avoid updates completely that do not change anything. Also, I would look to a data-modifying CTE in a loop:
CREATE OR REPLACE FUNCTION upsert_cte(d text, sys int, val int, p int
, inter bool)
RETURNS SETOF test_table AS
$func$
BEGIN
LOOP
BEGIN
RETURN QUERY
WITH sel AS (
SELECT t.pk_col -- primary key column
FROM test_table t
WHERE t.code = sys
AND t.val_id = val
AND t.provider_id = p
FOR SHARE -- lock
)
, ins AS (
INSERT INTO test_table (description, code, val_id, provider_id, connect)
SELECT d, sys, val, p, inter
WHERE NOT EXISTS (SELECT 1 FROM sel) -- if not found
RETURNING *
)
, upd AS (
UPDATE test_table t
SET description = d
,code = sys
,val_id = val
,provider_id = p
,connect = inter
FROM sel
WHERE sel.pk_col = t.pk_col -- if found (possibly mult. rows)
AND t.description IS DISTINCT FROM d
,t.code IS DISTINCT FROM sys
,t.val_id IS DISTINCT FROM val
,t.provider_id IS DISTINCT FROM p
,t.connect IS DISTINCT FROM inter -- only if anything changes
RETURNING t.*
)
SELECT * FROM ins
UNION ALL
SELECT * FROM upd;
RETURN; -- No error occurred, exit loop
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- inserted in concurrent session
RAISE NOTICE 'It happened!'; -- hardly ever happens, keep looping
END;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Explanation and links in this related answer:
Is SELECT or INSERT in a function prone to race conditions?