I'm looking to create a Postgres function that caches the results of a long-running query so it only needs to be executed once per transaction:
CREATE OR REPLACE FUNCTION get_records (
_state VARCHAR(10)
)
RETURNS TABLE (
id UUID
) AS
$$
DECLARE
_temptable VARCHAR;
BEGIN
_temptable := FORMAT('temp_state_%s', _state);
IF NOT EXISTS(SELECT 1 FROM pg_tables WHERE tablename = _temptable) THEN
EXECUTE FORMAT('CREATE TEMPORARY TABLE %I (id UUID NOT NULL, PRIMARY KEY(_uid)) ON COMMIT DROP', _temptable);
EXECUTE FORMAT('INSERT INTO %I SELECT id FROM very_complex_nested_query', _temptable);
EXECUTE FORMAT('ANALYZE %I', _temptable);
END IF;
RETURN QUERY EXECUTE FORMAT('SELECT id FROM %I', _temptable);
END;
$$
LANGUAGE 'plpgsql';
Now, I can run all of my queries and join this function:
SELECT mt.*
FROM my_table1 mt
INNER JOIN get_records('new') nr ON mt.id = nr.id
SELECT mt.*
FROM my_table2 mt
INNER JOIN get_records('new') nr ON mt.id = nr.id
SELECT mt.*
FROM my_table3 mt
INNER JOIN get_records('new') nr ON mt.id = nr.id
-- ... many more
I've got a whole pile of these, without guarantees on which one will run first or in what order.
This works pretty well, except that the primary key index on the temporary table isn't used.
How can I return a "table" from a Postgres function, instead of just the results of a query?
- I'm using a function to build the temporary table instead of a materialized view to get around the "where clause doesn't get pushed into view that contains aggregation" issue.
- I could create the temporary table, then refer to it directly in all the queries, but it would mean having to build in some sort of blocking mechanism to ensure the queries aren't executed too early, and the tool I'm using doesn't support such mechanisms very well.
sql
function. A query using that asql
function can be optimized far better then the blackbox that a PL/pgSQL presents to the optimizer.language sq
instead oflanguage plpgsql
- but if you want to index it, you need to create an index on the temp table.