When assigning my variable _variable
, I need to check whether the query returned any rows. "No row" is converted to null, but I want to keep the previous value instead in this case. While an actual null value should be assigned.
I use the special PL/pgSQL variable FOUND
:
do $$
declare
_variable bigint = 100;
_tmp bigint;
begin
drop table if exists tTemp;
create temp table tTemp(id bigint, value bigint);
insert into tTemp(id, value) values (1,1), (2, null);
_tmp := _variable; -- backup value
/* -- first variant - doesn't work
_variable := (select value from tTemp where id = -1);
raise notice '%', FOUND; -- FOUND is true (?!)
*/
-- second variant
select value
into _variable
from tTemp
where id = -1;
raise notice '%', FOUND; -- FOUND is false
-- roll back assignment if no row was found
if not FOUND then
_variable := _tmp;
end if;
end;
$$;
Why is FOUND
true
in the first variant?
Is there a simpler way to avoid overwriting _variable
with null?
COALESCE
would also catch an actual null value, but I only want to catch "no row" - which is indistinguishable after being converted to null.
Why is
FOUND
true
in the first variant?
A plain assignment with :=
or =
does not set the special variable FOUND
.
The manual lists all events that set FOUND
.
In your first variant FOUND
still reflects the result from the preceding INSERT
command.
GET DIAGNOSTICS
catches more cases (incl. nested SELECT
). But "no row" converted to a null value also counts as "1 row processed", so you get a row count of 1 regardless. See:
There is a hybrid form for assignments, that sets the DIAGNOSTIC
value as desired: 0 for "no row". (But still no effect on FOUND
.)
_var := value FROM tbl WHERE id = -1;
See:
You could work with that. But rather stick to the cleaner SELECT INTO
. Or consider this ...
Is there a simpler way to avoid overwriting _variable with null?
Yes, you can use the UNION ALL
"hack".
It's almost, but not quite, the same as COALESCE
. This variant distinguishes between a genuine null value and "no row":
DO
$do$
DECLARE
_var int := 100;
BEGIN
SELECT value FROM tbl WHERE id = -1
UNION ALL
SELECT _var
INTO _var;
RAISE NOTICE '%', _var;
END
$do$;
SELECT ... INTO
(without STRICT
) assigns values from the first returned row (discarding the rest). If the payload query returns no row, the second leg of the UNION ALL
query kicks in: SELECT _var
.
Voilá.
Strictly speaking, there is no formal guarantee without ORDER BY
. See:
So if you are going to bet your neck on this:
DO
$do$
DECLARE
_var int := 100;
BEGIN
SELECT value, 1 AS rnk FROM tbl WHERE id = -1
UNION ALL
SELECT _var, 2
ORDER BY rnk
INTO _var;
RAISE NOTICE '%', _var;
END
$do$;
Additional columns are discarded in this assignment, just like additional rows.