The approach you're using is unnecessarily complex - and very inefficient. Instead of the first function use:
create or replace function compute_pair_id_value(id bigint, value integer)
returns setof pair_id_value
as $$
SELECT $1, generate_series(0,$2);
$$
language sql;
or better, get rid of it entirely and write the whole operation like this:
-- Sample data creation:
CREATE TABLE my_obj(id bigint, obj_value integer);
insert into my_obj(id,obj_value) VALUES (1712437,2),(17000,5);
-- and the query:
SELECT id, generate_series(0,obj_value) FROM my_obj;
Resulting in:
regress=> SELECT id, generate_series(0,obj_value) FROM my_obj;
id | generate_series
---------+-----------------
1712437 | 0
1712437 | 1
1712437 | 2
17000 | 0
17000 | 1
17000 | 2
17000 | 3
17000 | 4
17000 | 5
(9 rows)
This exploits PostgreSQL's behaviour with set-returning functions called in the SELECT
list. Once PostgreSQL 9.3 comes out it can be replaced with a standards-compliant LATERAL
query.
Since it turns out your question was a simplified version of the real problem, let's tackle that. I'll work with the simplified compute_pair_id_value
above to avoid the hassle of plpython3. Here's how to do what you want:
SELECT (compute_pair_id_value(id,obj_value)).* FROM my_obj;
Result:
regress=> SELECT (compute_pair_id_value(id,obj_value)).* FROM my_obj;
id | value
---------+-------
1712437 | 0
1712437 | 1
1712437 | 2
17000 | 0
17000 | 1
17000 | 2
17000 | 3
17000 | 4
17000 | 5
(9 rows)
but again, be warned that compute_pair_id_value
will be called more than once. This is a limitation of PostgreSQL's query executor that can be avoided in 9.3 with LATERAL
support, but as far as I know you're stuck with it in 9.2 and below. Observe:
create or replace function compute_pair_id_value(id bigint, value integer)
returns setof pair_id_value
as $$
BEGIN
RAISE NOTICE 'compute_pair_id_value(%,%)',id,value;
RETURN QUERY SELECT $1, generate_series(0,$2);
END;
$$
language plpgsql;
output:
regress=> SELECT (compute_pair_id_value(id,obj_value)).* FROM my_obj;
NOTICE: compute_pair_id_value(1712437,2)
NOTICE: compute_pair_id_value(1712437,2)
NOTICE: compute_pair_id_value(17000,5)
NOTICE: compute_pair_id_value(17000,5)
id | value
---------+-------
1712437 | 0
1712437 | 1
1712437 | 2
17000 | 0
17000 | 1
17000 | 2
17000 | 3
17000 | 4
17000 | 5
(9 rows)
See how compute_pair_id_value
is called once per output column?
There is a workaround: Another layer of subquery to unpack the composite type result. See:
regress=> SELECT (val).* FROM (SELECT compute_pair_id_value(id,obj_value) FROM my_obj) x(val);
NOTICE: compute_pair_id_value(1712437,2)
NOTICE: compute_pair_id_value(17000,5)
id | value
---------+-------
1712437 | 0
1712437 | 1
1712437 | 2
17000 | 0
17000 | 1
17000 | 2
17000 | 3
17000 | 4
17000 | 5
(9 rows)
You can use the same technique in your code if you really must LOOP
over the results (it's slow to do that, so avoid it if you can).
As lambda_record
function returns RECORD
and doesn't have OUT
parameter, you must define the columns and types it will return when calling using an alias:
SELECT r.out1, r.out2
FROM lambda_record(...) AS r(out1 numeric, out2 numeric);
In this case r
is the table alias, and out1
/out2
the alias for the columns with the types defined (defining types is only necessary when it returns RECORD
and doesn't have OUT
parameters).
EDIT: even though it uses a RECORD
, lambda_record
still calls the function as it had only one column returned, using SELECT func_name(params...)
method:
sql := format(
'select %s(%s)',
foid::oid::regproc,
array_to_string(call_args, ', ')
);
raise debug 'sql=%', sql;
You could change that line to call it use SELECT * FROM func_name(params...)
:
sql := format(
'select * from %s(%s)',
foid::oid::regproc,
array_to_string(call_args, ', ')
);
raise debug 'sql=%', sql;
That way you should already have it return the columns correctly (even for functions with a single column), and now you can call as I showed in the beginning.
Best Answer
v1
is untyped and Postgres doesn't know what to do with that. At some point, either while defining the record or when returning it you have to specify the columns. Try specifying the column names in theSELECT
within an IF for different types. I put in dummy checks for the IF to be replaced with what you need to decide the row type.Alternatively you could also: