I'm wrighting a stored function and I need to have table-variable like in MSSQL. I need to have possibility to insert/read/delete records from this table and use it in queries.
Temp tables are not suitable for me, because if I run this function simultaneously several times - same temp table will be used in all calls and I'll receive total mess there. Wrapping the body of function in transaction is not an option for me - it will block other users.
So what can I use instead ?
Here is simplified version of what I'm doing:
CREATE OR REPLACE FUNCTION public.DoSomeCoolStuff(iterations integer)
RETURNS Table(
Id1 uuid,
Id1 uuid,
Id1 uuid) AS $$
#variable_conflict use_column
BEGIN
CREATE TEMP TABLE intermediate_table (Id uuid) ON COMMIT DROP;
INSERT INTO intermediate_table Select ...complex query
...and some more temp tables
FOR i IN 1..iterations
LOOP
... Some calculations...
DELETE FROMM intermediate_table WHERE...previous calculations...
...
END LOOP;
RETURN QUERY
SELECT * FROM intermediate_table INNER JOIN Products On ...
END; $$
LANGUAGE plpgsql;
When I call this function multiple times simultaneously - postgres says
relation "intermediate_table" already exists
So I assume, that all instances of function calls see the same temp table.
All queries are executed by one user - our backend server.
The relation "intermediate_table" already exists
error could be because you're not explicitly dropping it and keep a transaction open between calls, avoiding the on commit drop
event, or there's something else in your search_path
that's already using that name.
If you want to keep it around change the CREATE
to CREATE..IF NOT EXISTS
then wipe it at the end with TRUNCATE
or DELETE
. If not, it's good practice to explicitly DROP TABLE intermediate_table;
without trusting the caller will stick to one call per transaction.
If you had a problem droppping or wiping it right at the end of the function because you need return
stuff from the table first, note that return query
and return next
don't immediately terminate the function, so you can do your cleanup after that. Quoting the doc:
individual items to return are specified by a sequence of
RETURN NEXT
orRETURN QUERY
commands, and then a finalRETURN
command with no argument is used to indicate that the function has finished executing.
Your other concerns:
Temp tables are not suitable for me, because if I run this function simultaneously several times - same temp table will be used in all calls and I'll receive total mess there. Wrapping the body of function in transaction is not an option for me - it will block other users.
One user in one session can't run the function "simultaneously several times", only in sequence. Each concurrent user that calls the function in parallel, from their own session, will get a separate temp table of their own, so they will neither block nor interfere with each other.
Even if you call this function multiple times in a single query:
select *
from public.DoSomeCoolStuff(1) as call1
cross join public.DoSomeCoolStuff(2) as call2
cross join public.DoSomeCoolStuff(3) as call3;
they will get evaluated in sequence, unless you somehow convince Postgres the function is parallel safe
(despite it being clearly not the case), and the planner finds it a good idea to evaluate these calls in parallel. Clean it up at the end, and subsequent calls will be able to re-use it without a problem.
One scenario you need to be worried about is calling it recursively: each nested call will see rows from the upper call levels. In that case, you can simply add a column to uniquely identify your calls to avoid the interference: demo at db<>fiddle
CREATE OR REPLACE FUNCTION public.DoSomeCoolStuff(iterations integer)
RETURNS Table(
Id1 uuid,
Id1 uuid,
Id1 uuid) AS $f$
#variable_conflict use_column
DECLARE v_call_id uuid:=gen_random_uuid();
BEGIN
CREATE TEMP TABLE IF NOT EXISTS intermediate_table
(call_id uuid, Id uuid) ON COMMIT DROP;
INSERT INTO intermediate_table SELECT v_call_id, ...complex query
...and some more temp tables
FOR i IN 1..iterations
LOOP
... Some calculations...
DELETE FROM intermediate_table
WHERE call_id = v_call_id
AND ...previous calculations...
...
END LOOP;
RETURN QUERY
WITH wipe_and_return AS (
DELETE FROM intermediate_table
WHERE call_id = v_call_id
RETURNING *)
SELECT *
FROM wipe_and_return
INNER JOIN Products
...
END; $f$
LANGUAGE plpgsql;