I work on the following test table:
select * from test;
id | broker | affiliate | leads
----+---------+-----------+-------
1 | broker1 | aff1 | 1
2 | broker1 | aff2 | 2
3 | broker2 | aff2 | 4
(3 rows)
I want to create a function which will accept group by
column name and where
predicate. Here is a working example of the function:
DROP FUNCTION report2(TEXT,TEXT);
CREATE OR REPLACE FUNCTION report2(
group_by_column_name TEXT,
sql_where TEXT
)
RETURNS RECORD
AS $$
DECLARE
query TEXT;
result_row RECORD;
BEGIN
query := 'SELECT ';
IF group_by_column_name <> '' THEN
query := query || group_by_column_name || ', ';
END IF;
query := query || 'sum(leads) FROM test';
IF sql_where <> '' THEN
query := query || ' WHERE ' || sql_where;
END IF;
IF group_by_column_name <> '' THEN
query := query || ' GROUP BY(' || group_by_column_name || ')';
END IF;
RAISE NOTICE 'query: %;', query;
EXECUTE query INTO result_row;
RETURN result_row;
END
$$ LANGUAGE 'plpgsql';
and it’s usage:
select report2('broker', '');
NOTICE: query: SELECT broker, sum(leads) FROM test GROUP BY(broker);
report2
-------------
(broker1,3)
(1 row)
I’m trying to get not only a first row of the query but all the rows (obviously).
I tried the following:
DROP FUNCTION report2(TEXT,TEXT);
CREATE OR REPLACE FUNCTION report2(
group_by_column_name TEXT,
sql_where TEXT
)
RETURNS SETOF RECORD
AS $$
DECLARE
query TEXT;
result_row RECORD;
BEGIN
query := 'SELECT ';
IF group_by_column_name <> '' THEN
query := query || group_by_column_name || ', ';
END IF;
query := query || 'sum(leads) FROM test';
IF sql_where <> '' THEN
query := query || ' WHERE ' || sql_where;
END IF;
IF group_by_column_name <> '' THEN
query := query || ' GROUP BY(' || group_by_column_name || ')';
END IF;
RAISE NOTICE 'query: %;', query;
-- Does not worK:
-- FOR result_row IN EXECUTE query
-- LOOP
-- RETURN NEXT result_row;
-- END LOOP;
-- Does not worK:
-- RETURN QUERY EXECUTE query;
END
$$ LANGUAGE 'plpgsql';
In both cases, with FOR
or RETURN QUERY
, the error occurs:
NOTICE: query: SELECT broker, sum(leads) FROM test GROUP BY(broker);
ERROR: materialize mode required, but it is not allowed in this context
CONTEXT: PL/pgSQL function report2(text,text) line 21 at RETURN NEXT
From the [docs][1]:
If multiple rows are returned, only the first will be assigned to the INTO variable(s).
So, how is it possible to get the entire results?
[1]: https://(https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN)
2
Answers
I would return
setof json
instead ofrecord
because the number of result rows varies andjson
is more usable thanrecord
. So here is the function:You’re using a set-returning function in the select list. It’s possible for SRF with return types that are either clearly specified or predictable, but your set-of-record-returning monstrosity doesn’t qualify.
Technically, the function you presented is perfectly correct, fine and usable – you can call it, just not the way you tried. Look: (demo at db<>fiddle)
You can even get it to pack those back up into records:
You can even build that into the function if you use the
row()
constructor to prevent unpacking:And then you can openly say it’s returning just records (the non-of-your-business type):
I have very little idea what it is you’re trying to do, why and why this way, but polymorphic types would probably help, packing things into a
jsonb
suggested by @Stefanov.sm might be easier if you don’t mind wrapping and unwrapping, casting and re-casting things back and forth. PL/pgSQL manual will definitely help.