PostgreSQL structure of function that return a query - postgresql

Given a PostgreSQL function that returns a query:
CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
RETURNS TABLE (
txt text -- visible as OUT parameter inside and outside function
, cnt bigint
, ratio bigint) AS
$func$
BEGIN
RETURN QUERY
SELECT t.txt
, count(*) AS cnt -- column alias only visible inside
, (count(*) * 100) / _max_tokens -- I added brackets
FROM (
SELECT t.txt
FROM token t
WHERE t.chartype = 'ALPHABETIC'
LIMIT _max_tokens
) t
GROUP BY t.txt
ORDER BY cnt DESC; -- note the potential ambiguity
END
$func$ LANGUAGE plpgsql;
How can I retrieve the structure of this function? I mean, I know that this function will return the txt, cnt and ratio columns, but how can I make a query that returns these column names? I was trying to find these columns names on information_schema schema, but I couldn't.
The expected result of this hypothetical query would be something like this:
3 results found:
---------------------------------
?column_name? | ?function_name?
---------------------------------
txt word_frequency
cnt word_frequency
ratio word_frequency

This information is stored in pg_proc
SELECT unnest(p.proargnames) as column_name,
p.proname as function_name
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public'
AND p.proname = 'word_frequency'

Based on the answer of a_horse_with_no_name, I came with this final version:
SELECT
column_name,
function_name
FROM
(
SELECT
unnest(p.proargnames) as column_name,
unnest(p.proargmodes) as column_type,
p.proname as function_name
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE n.nspname = 'public'
AND p.proname = 'my_function'
) as temp_table
WHERE column_type = 't';
I simply omitted the arguments, returning only the columns that the function returns

Related

PgSQL function returning table and extra data computed in process

In PgSQL I make huge select, and then I want count it's size and apply some extra filters.
execute it twice sound dumm,
so I wrapped it in function
and then "cache" it and return union of filtered table and extra row at the end where in "id" column store size
with q as (select * from myFunc())
select * from q
where q.distance < 400
union all
select count(*) as id, null,null,null
from q
but it also doesn't look like proper solution...
and so the question: is in pg something like "generator function" or any other stuff that can properly solve this ?
postgreSQL 13
myFunc aka "selectItemsByRootTag"
CREATE OR REPLACE FUNCTION selectItemsByRootTag(
in tag_name VARCHAR(50)
)
RETURNS table(
id BIGINT,
name VARCHAR(50),
description TEXT,
/*info JSON,*/
distance INTEGER
)
AS $$
BEGIN
RETURN QUERY(
WITH RECURSIVE prod AS (
SELECT
tags.name, tags.id, tags.parent_tags
FROM
tags
WHERE tags.name = (tags_name)
UNION
SELECT c.name, c.id , c.parent_tags
FROM
tags as c
INNER JOIN prod as p
ON c.parent_tags = p.id
)
SELECT
points.id,
points.name,
points.description,
/*points.info,*/
points.distance
from points
left join tags on points.tag_id = tags.id
where tags.name in (select prod.name from prod)
);
END;
$$ LANGUAGE plpgsql;
as a result i want see maybe set of 2 table or generator function that yield some intermediate result not shure how exacltly it should look
demo
CREATE OR REPLACE FUNCTION pg_temp.selectitemsbyroottag(tag_name text, _distance numeric)
RETURNS TABLE(id bigint, name text, description text, distance numeric, count bigint)
LANGUAGE plpgsql
AS $function$
DECLARE _sql text;
BEGIN
_sql := $p1$WITH RECURSIVE prod AS (
SELECT
tags.name, tags.id, tags.parent_tags
FROM
tags
WHERE tags.name ilike '%$p1$ || tag_name || $p2$%'
UNION
SELECT c.name, c.id , c.parent_tags
FROM
tags as c
INNER JOIN prod as p
ON c.parent_tags = p.id
)
SELECT
points.id,
points.name,
points.description,
points.distance,
count(*) over ()
from points
left join tags on points.tag_id = tags.id
where tags.name in (select prod.name from prod)
and points.distance > $p2$ || _distance
;
raise notice '_sql: %', _sql;
return query execute _sql;
END;
$function$
You can call it throug following way
select * from pg_temp.selectItemsByRootTag('test',20);
select * from pg_temp.selectItemsByRootTag('test_8',20) with ORDINALITY;
The 1 way to call the function, will have a row of total count total number of rows. Second way call have number of rows plus a serial incremental number.
I also make where q.distance < 400 into function input argument.
selectItemsByRootTag('test',20); means that q.distance > 20 and tags.name ilike '%test%'.

How to resolve error: return columns doesn't match expected columns?

I write function in PostgreSQL. I am trying to fetch data from db by passing parameters in function .But i am getting error" ERROR: structure of query does not match function result type".
Here i created a function in which i am using union to get unique values.In first select stat. i amsetting value for account total that is used in below code.
Function:
CREATE OR REPLACE FUNCTION GetDeptListForViewModifyJointUsePercentages ( p_nInstID numeric,p_nDeptID numeric)
RETURNS Table(
res_ndept_id numeric,
res_salaryPercent character varying,
res_nclient_cpc_mapping_id numeric,
res_CPCCODE character varying,
res_sdept_name character varying,
res_sclient_dept_id character varying,
res_sAlternateJointUsePercentage character varying
)
AS $$
declare v_AccountTotal numeric(18,2);
BEGIN
RETURN QUERY
select SUM(CAST (coalesce(eam.npayroll_amt, null) AS numeric(18,2))) as AccountTotal
FROM Account acct
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = acct.naccount_id
AND acct.ninst_id =p_nInstID
where acct.ndept_id =p_nDeptID ;
SELECT *
FROM
(select dep.ndept_id ,( CASE
WHEN v_AccountTotal =0 THEN 0
ELSE Round(SUM(CAST(coalesce(eam.npayroll_amt, null) AS numeric(18,2)) * cac.npercentage_assigned/100) / v_AccountTotal *100,null)
END
) as salaryPercent
,cac.nclient_cpc_mapping_id,client.sclient_cpc AS CPCCODE,
dep.sdept_name,dep.sclient_dept_id,'NO' ASsAlternateJointUsePercentage
FROM account
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = account .naccount_id AND account .ninst_id=p_nInstID
INNER JOIN accountcpcmapping acm
ON acm.naccount_cpc_mapping_id = account.naccount_cpc_mapping_id
INNER JOIN cpcaccountcpcmapping as cac
ON cac.naccount_cpc_mapping_id=acm.naccount_cpc_mapping_id
INNER JOIN clientcostpoolcodes as client
ON client.nclient_cpc_mapping_id=cac.nclient_cpc_mapping_id
INNER JOIN mastercostpoolcodes
ON mastercostpoolcodes .nmaster_cpc_id= client.nmaster_cpc_id
INNER JOIN department as dep
ON account.ndept_id=dep.ndept_id and dep.balternate_jointuse_percentage=FALSE
where account.ndept_id =p_nDeptID
Group by dep.ndept_id,cac.nclient_cpc_mapping_id,client.sclient_cpc
,dep.sdept_name,dep.sclient_dept_id, dep.balternate_jointuse_percentage
UNION
SELECT Department_1.ndept_id, a_1.SumByCPC, clientcostpoolcodes.nclient_cpc_mapping_id, clientcostpoolcodes .sclient_cpc, Department_1.sdept_name, Department_1.sclient_dept_id , 'YES' AS sAlternateJointUsePercentage
FROM department AS Department_1 LEFT OUTER JOIN
mastercostpoolcodes RIGHT OUTER JOIN
clientcostpoolcodes RIGHT OUTER JOIN
(SELECT JointUseStatistics_1.nccp_code, SUM(JointUseStatistics_1.npercent) /
(SELECT SUM(jointusestatistics.npercent) AS SumByCPC
FROM jointusestatistics INNER JOIN
roomdepartmentmapping ON jointusestatistics.nroom_allocation_id = roomdepartmentmapping .nroom_allocation_id
WHERE (roomdepartmentmapping .ndept_id = Room_1.ndept_id)) * 100 AS SumByCPC,
Room_1.ndept_id
FROM jointusestatistics JointUseStatistics_1 INNER JOIN roomdepartmentmapping AS Room_1 ON JointUseStatistics_1.nroom_allocation_id = Room_1.nroom_allocation_id
GROUP BY JointUseStatistics_1.nccp_code, JointUseStatistics_1.npercent, Room_1.ndept_id) AS a_1 ON
clientcostpoolcodes .nclient_cpc_mapping_id = a_1.nccp_code ON mastercostpoolcodes .nmaster_cpc_id = clientcostpoolcodes .nmaster_cpc_id ON
Department_1.ndept_id = a_1.ndept_id
WHERE (Department_1.balternate_jointuse_percentage = 'TRUE')
AND (Department_1.ninst_id= p_nInstID)
)AS My
where (ndept_id= p_nDeptID )
ORDER BY My.sdept_name,My.CPCCODE ;
END;
$$ LANGUAGE plpgsql;
I think what you want is the first query to just retrieve the count, store that in your variable, then use that variable in the second query which is the actual source of the result of the function.
So the first query should not use return query but select .. into .. store put the result into the variable.
Then you can prefix the second query with return query to return its result:
CREATE OR REPLACE FUNCTION GetDeptListForViewModifyJointUsePercentages ( p_nInstID numeric,p_nDeptID numeric)
RETURNS Table(
res_ndept_id numeric,
res_salaryPercent character varying,
res_nclient_cpc_mapping_id numeric,
res_CPCCODE character varying,
res_sdept_name character varying,
res_sclient_dept_id character varying,
res_sAlternateJointUsePercentage character varying
)
AS $$
declare
v_AccountTotal numeric(18,2);
BEGIN
-- no RETURN query here!!
select SUM(CAST (coalesce(eam.npayroll_amt, null) AS numeric(18,2)))
into v_accounttotal --<<< store result in variable
FROM Account acct
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = acct.naccount_id
AND acct.ninst_id =p_nInstID
where acct.ndept_id = p_nDeptID;
-- this is the query that should be returned
RETURN QUERY
SELECT *
FROM (
select dep.ndept_id,
CASE WHEN v_AccountTotal = 0 THEN 0
ELSE Round(SUM(CAST(coalesce(eam.npayroll_amt, null) AS numeric(18,2)) * cac.npercentage_assigned/100) / v_AccountTotal *100,null)
END as salaryPercent,
cac.nclient_cpc_mapping_id,
client.sclient_cpc AS CPCCODE,
dep.sdept_name,
dep.sclient_dept_id,
'NO' AS sAlternateJointUsePercentage
FROM account
INNER JOIN employeeaccountmapping eam
ON eam.nacct_id = account .naccount_id AND account .ninst_id=p_nInstID
INNER JOIN accountcpcmapping acm
ON acm.naccount_cpc_mapping_id = account.naccount_cpc_mapping_id
INNER JOIN cpcaccountcpcmapping as cac
ON cac.naccount_cpc_mapping_id=acm.naccount_cpc_mapping_id
INNER JOIN clientcostpoolcodes as client
ON client.nclient_cpc_mapping_id=cac.nclient_cpc_mapping_id
INNER JOIN mastercostpoolcodes
ON mastercostpoolcodes .nmaster_cpc_id= client.nmaster_cpc_id
INNER JOIN department as dep
ON account.ndept_id=dep.ndept_id and dep.balternate_jointuse_percentage=FALSE
where account.ndept_id =p_nDeptID
Group by dep.ndept_id,cac.nclient_cpc_mapping_id,client.sclient_cpc
,dep.sdept_name,dep.sclient_dept_id, dep.balternate_jointuse_percentage
UNION
SELECT Department_1.ndept_id, a_1.SumByCPC, clientcostpoolcodes.nclient_cpc_mapping_id,
clientcostpoolcodes .sclient_cpc, Department_1.sdept_name, Department_1.sclient_dept_id,
'YES' AS sAlternateJointUsePercentage
FROM department AS Department_1
LEFT OUTER JOIN mastercostpoolcodes --<<< missing join condition !!
RIGHT OUTER JOIN clientcostpoolcodes --<<< missing join condition !!
RIGHT OUTER JOIN (
SELECT JointUseStatistics_1.nccp_code,
SUM(JointUseStatistics_1.npercent) /
(SELECT SUM(jointusestatistics.npercent) AS SumByCPC
FROM jointusestatistics
INNER JOIN roomdepartmentmapping ON jointusestatistics.nroom_allocation_id = roomdepartmentmapping .nroom_allocation_id
WHERE (roomdepartmentmapping .ndept_id = Room_1.ndept_id)) * 100 AS SumByCPC,
Room_1.ndept_id
FROM jointusestatistics JointUseStatistics_1
INNER JOIN roomdepartmentmapping AS Room_1 ON JointUseStatistics_1.nroom_allocation_id = Room_1.nroom_allocation_id
GROUP BY JointUseStatistics_1.nccp_code, JointUseStatistics_1.npercent, Room_1.ndept_id
) AS a_1 ON clientcostpoolcodes.nclient_cpc_mapping_id = a_1.nccp_code
ON mastercostpoolcodes.nmaster_cpc_id = clientcostpoolcodes.nmaster_cpc_id --<< that should be somewhere else
ON Department_1.ndept_id = a_1.ndept_id --<< ??????
WHERE (Department_1.balternate_jointuse_percentage = 'TRUE')
AND (Department_1.ninst_id= p_nInstID)
) AS My
where (ndept_id= p_nDeptID )
ORDER BY My.sdept_name,My.CPCCODE;
END;
$$ LANGUAGE plpgsql;
Note that there are several syntax errors in your second query because the join conditions are scattered around the query. I have tried to highlight them.
In Postgresql, function returns a single value. But in your example, you are trying to return multiple outputs in terms of number of rows and columns.
You can alternatively use a stored procedure and save the output in a temporary table. Later use that table for your use.

PostgreSQL: Pass value from an array and do union

WITH temp AS (select * from t1 where c1 = 'string1')
select 'string1' as col1, t2.col2, temp.col3 from t2 inner join temp on t2.c2 = temp.c2 where t2.some_col like 'string1%'
union
WITH temp AS (select * from t1 where c1 = 'string2')
select 'string2' as col1, t2.col2, temp.col3 from t2 inner join temp on t2.c2 = temp.c2 where t2.some_col like 'string2%'
...
Above is just an example of a PostgreSQL query I am trying to run. It's a union of two completely similar queries. They only use different values for matching string1 and string2.
I have about 20 such queries that I want to do a union on. They only differ by the variable I want to use for comparison such as string1
How can I use such array of values ['string1', 'string2', 'string3', .., 'string20'], run a query on each variable from this array and union them?
What about a old fashioned plpgsql?
CREATE OR REPLACE FUNCTION get_all_foo(arr varchar[]) RETURNS TABLE (col1 TEXT, col2 TEXT, col3 TEXT) AS
$BODY$
DECLARE
arr_value varchar;
generated_query varchar := '';
array_index smallint := 1;
array_length smallint;
BEGIN
array_length := array_length(arr, 1);
FOREACH arr_value IN ARRAY arr LOOP
generated_query := generated_query || format(' (WITH temp AS (select * from t1 where c1 = %L) '
'select %L as col1, t2.col2, temp.col3 from t2 inner join temp on t2.c2 = temp.c2 where t2.some_col like ''%s%%'')', arr_value, arr_value, arr_value);
IF array_index < array_length THEN
generated_query := generated_query || ' UNION ';
END IF;
array_index := array_index+1;
END LOOP;
RAISE DEBUG 'Generated query: %', generated_query;
RETURN QUERY EXECUTE generated_query;
END
$BODY$
LANGUAGE plpgsql;
--Uncomment to see generated query
--SET client_min_messages = DEBUG;
SELECT * FROM get_all_foo(array['string1', 'string2', 'string3', 'string4', 'string5']);
select c1 as col1, t2.col2, temp.col3
from
(select col2, c2 from t2 where
some_col like 'string1%' or some_col like 'string2%' or <other strings in the similar fashion>) t2
inner join
(select c1,c2,col3 from t1 where c1 in ('string1', 'string2', <other strings in the similar fashion>)) temp
on t2.c2 = temp.c2;
WITH temp AS (
select *
from t1
where c1 = any(array['string1','string2','string3']))
select distinct
temp.c1 as col1, t2.col2, temp.col3
from t2 inner join
temp on (t2.c2 = temp.c2 and t2.some_col like temp.c1||'%')

Get complex output type of function that returns record in postgresql

I want to write nice and detailed report on functions in my postgresql database.
I built the following query:
SELECT routine_name, data_type, proargnames
FROM information_schema.routines
join pg_catalog.pg_proc on pg_catalog.pg_proc.proname = information_schema.routines.routine_name
WHERE specific_schema = 'public'
ORDER BY routine_name;
It works as it should (basically returns me what I want it to: function name, output data type and input data type) except one thing:
I have relatively complicated functions and many of them return record.
The thing is, data_type returns me record as well for such functions, while I want detailed list of function output types.
For instance, I have something like this in one of my functions:
RETURNS TABLE("Res" integer, "Output" character varying) AS
How can I make query above (or, perhaps, a new query, if it will solve the problem) return something like
integer, character varying instead of record for such functions?
I am using postgresql 9.2
Thanks in advance!
The RECORD returned value is evaluated at runtime, there is no way that the information can be retrieved this way.
BUT, if RETURNS TABLE("Res" integer, "Output" character varying) AS is used, there is a solution.
The test functions I used:
-- first function, uses RETURNS TABLE
CREATE FUNCTION test_ret(a TEXT, b TEXT)
RETURNS TABLE("Res" integer, "Output" character varying) AS $$
DECLARE
ret RECORD;
BEGIN
-- test
END;$$ LANGUAGE plpgsql;
-- second function, test some edge cases
-- same name as above, returns simple integer
CREATE FUNCTION test_ret(a TEXT)
RETURNS INTEGER AS $$
DECLARE
ret RECORD;
BEGIN
-- test
END;$$ LANGUAGE plpgsql;
How to retrieve this function return datatype is easy as it's stored into pg_catalog.pg_proc.proallargtypes, the problem is that this is an array of OID. We must unnest this thing and join it to pg_catalog.pg_types.oid.
-- edit: add support for function not returning tables, thx Tommaso Di Bucchianico
WITH pg_proc_with_unnested_proallargtypes AS (
SELECT
pg_catalog.pg_proc.oid,
pg_catalog.pg_proc.proname,
CASE WHEN proallargtypes IS NOT NULL THEN unnest(proallargtypes) ELSE null END AS proallargtype
FROM pg_catalog.pg_proc
JOIN pg_catalog.pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_catalog.pg_namespace.oid
WHERE pg_catalog.pg_namespace.nspname = 'public'
),
pg_proc_with_proallargtypes_names AS (
SELECT
pg_proc_with_unnested_proallargtypes.oid,
pg_proc_with_unnested_proallargtypes.proname,
array_agg(pg_catalog.pg_type.typname) AS proallargtypes
FROM pg_proc_with_unnested_proallargtypes
LEFT JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = proallargtype
GROUP BY
pg_proc_with_unnested_proallargtypes.oid,
pg_proc_with_unnested_proallargtypes.proname
)
SELECT
information_schema.routines.specific_name,
information_schema.routines.routine_name,
information_schema.routines.routine_schema,
information_schema.routines.data_type,
pg_proc_with_proallargtypes_names.proallargtypes
FROM information_schema.routines
-- we can declare many function with the same name and schema as long as arg types are different
-- This is the only right way to join pg_catalog.pg_proc and information_schema.routines, sadly
JOIN pg_proc_with_proallargtypes_names
ON pg_proc_with_proallargtypes_names.proname || '_' || pg_proc_with_proallargtypes_names.oid = information_schema.routines.specific_name
;
Any refactoring is welcome :)
Here is the result:
specific_name | routine_name | routine_schema | data_type | proallargtypes
----------------+--------------+----------------+-----------+--------------------------
test_ret_16633 | test_ret | public | record | {text,text,int4,varchar}
test_ret_16635 | test_ret | public | integer | {NULL}
(2 rows)
EDIT
Identification of input and output arguments is not trivial, here is my solution for pg 9.2
-- https://gist.github.com/subssn21/e9e121f6fd5ff50f688d
-- Allow us to use array_remove in pg < 9.3
CREATE OR REPLACE FUNCTION array_remove(a ANYARRAY, e ANYELEMENT)
RETURNS ANYARRAY AS $$
BEGIN
RETURN array(SELECT x FROM unnest(a) x WHERE x <> e);
END;
$$ LANGUAGE plpgsql;
-- edit: add support for function not returning tables, thx Tommaso Di Bucchianico
WITH pg_proc_with_unnested_proallargtypes AS (
SELECT
pg_catalog.pg_proc.oid,
pg_catalog.pg_proc.proname,
pg_catalog.pg_proc.proargmodes,
CASE WHEN proallargtypes IS NOT NULL THEN unnest(proallargtypes) ELSE null END AS proallargtype
FROM pg_catalog.pg_proc
JOIN pg_catalog.pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_catalog.pg_namespace.oid
WHERE pg_catalog.pg_namespace.nspname = 'public'
),
pg_proc_with_unnested_proallargtypes_names_and_mode AS (
SELECT
pg_proc_with_unnested_proallargtypes.oid,
pg_proc_with_unnested_proallargtypes.proname,
pg_catalog.pg_type.typname,
-- we can't unnest multiple array of same length the way we expect in pg 9.2
-- just retrieve each mode manually using type row_number
pg_proc_with_unnested_proallargtypes.proargmodes[row_number() OVER w] AS proargmode
FROM pg_proc_with_unnested_proallargtypes
LEFT JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = proallargtype
WINDOW w AS (PARTITION BY pg_proc_with_unnested_proallargtypes.proname)
),
pg_proc_with_input_and_output_type_names AS (
SELECT
pg_proc_with_unnested_proallargtypes_names_and_mode.oid,
pg_proc_with_unnested_proallargtypes_names_and_mode.proname,
array_agg(pg_proc_with_unnested_proallargtypes_names_and_mode.typname) AS proallargtypes,
-- we should use FILTER, but that's not available in pg 9.2 :(
array_remove(array_agg(
-- see documentation for proargmodes here: http://www.postgresql.org/docs/9.2/static/catalog-pg-proc.html
CASE WHEN pg_proc_with_unnested_proallargtypes_names_and_mode.proargmode = ANY(ARRAY['i', 'b', 'v'])
THEN pg_proc_with_unnested_proallargtypes_names_and_mode.typname
ELSE NULL END
), NULL) AS proinputargtypes,
array_remove(array_agg(
-- see documentation for proargmodes here: http://www.postgresql.org/docs/9.2/static/catalog-pg-proc.html
CASE WHEN pg_proc_with_unnested_proallargtypes_names_and_mode.proargmode = ANY(ARRAY['o', 'b', 't'])
THEN pg_proc_with_unnested_proallargtypes_names_and_mode.typname
ELSE NULL END
), NULL) AS prooutputargtypes
FROM pg_proc_with_unnested_proallargtypes_names_and_mode
GROUP BY
pg_proc_with_unnested_proallargtypes_names_and_mode.oid,
pg_proc_with_unnested_proallargtypes_names_and_mode.proname
)
SELECT
*
FROM pg_proc_with_input_and_output_type_names
;
And here is my sample output:
oid | proname | proallargtypes | proinputargtypes | prooutputargtypes
-------+--------------+--------------------------+------------------+-------------------
16633 | test_ret | {text,text,int4,varchar} | {text,text} | {int4,varchar}
16634 | array_remove | {NULL} | {} | {}
16635 | test_ret | {NULL} | {} | {}
(3 rows)
Hope that helps :)
Answer, provided by Clément Prévost, is detailed and educative and therefore I marked it as best, while, however, after I executed suggested script I ended up with empty (filled only by {}) proinputargtypes and prooutputargtypes columnes on my machine. So I conducted a little research on my own, using hints that I learned from the answer above, and wrote following query:
WITH pg_proc_with_unhandled_proallargtypes AS (
SELECT
pg_catalog.pg_proc.oid,
pg_catalog.pg_proc.proname,
pg_catalog.pg_proc.proargmodes,
CASE WHEN proallargtypes IS NOT NULL THEN cast(proallargtypes AS text) ELSE NULL END AS proallargtype,
CASE WHEN array_agg(proargtypes) IS NOT NULL THEN replace(string_agg(proargtypes::text, ','), ' ', ',') ELSE NULL END AS proargtype
FROM pg_catalog.pg_proc
JOIN pg_catalog.pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_catalog.pg_namespace.oid
WHERE pg_catalog.pg_namespace.nspname = 'public'
GROUP BY pg_catalog.pg_proc.oid, pg_catalog.pg_proc.proname, pg_catalog.pg_proc.proargmodes, proallargtype
),
pg_proc_with_unnested_proallargtypes AS(
SELECT proname, CASE WHEN char_length(proargtype) =0 THEN NULL
ELSE ('{' || proargtype || '}')::oid[] end AS inp,
CASE WHEN proallargtype is NULL THEN NULL ELSE replace(proallargtype, proargtype || ',', '')::oid[] end AS output
FROM pg_proc_with_unhandled_proallargtypes
),
smth_input AS(
SELECT proname, unnest(inp) AS inp FROM pg_proc_with_unnested_proallargtypes
),
smth_output AS(
SELECT proname, unnest(output) AS output FROM pg_proc_with_unnested_proallargtypes
),
input_unnested AS(
SELECT proname, string_agg(pg_catalog.pg_type.typname::text, ',') AS fin_input FROM smth_input
JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = inp
GROUP BY proname
),
output_unnested AS (
SELECT proname, string_agg(pg_catalog.pg_type.typname::text, ',') AS fin_output FROM smth_output
JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = output
GROUP BY proname
)
SELECT input_unnested.proname, fin_input, CASE WHEN fin_output IS NOT NULl THEN fin_output ELSE information_schema.routines.data_type end AS fin_output
FROM input_unnested
LEFT JOIN output_unnested ON input_unnested.proname = output_unnested.proname
JOIN information_schema.routines ON information_schema.routines.routine_name = input_unnested.proname
It might be inefficient a bit and I probably used too much explicict type casting, but it worked.

Is it possible to discover the column types from a Postgres function?

I'm working on a utility that is using templates to generate a data access layer against a Postgres database. As part of this I'm trying to dynamically discover the return types of the stored procedures. This is easy enough in simple cases where a single standard type is returned, but I'm struggling when it comes to it returning a user defined type.
I'd appreciate if someone could provide the necessary SQL to return this data.
Thanks
Mark
I appreciate the answers that I have so far, which effectively boil to to the following SQL
SELECT p.proname, t.typname, p,proretset
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
INNER JOIN pg_type t ON p.prorettype = t.oid
WHERE n.nspname = 'public'
--and proname = 'foo'
ORDER BY proname;
This will return the name of the return types. However I still need to decompose the type into the properties that make it up when it returns a user defined type.
In the case that a function returns a record I don't think there is any way to discover its return structure other than calling the function and examining its return values.
psql meta commands are an easy shortcut to finding information schema stuff.
try test=# \d? to find introspection info.
then psql -E -c '\df' will show the sql behind the show function command:
d$ psql -E -c '\df+'
********* QUERY **********
SELECT n.nspname as "Schema",
p.proname as "Name",
pg_catalog.pg_get_function_result(p.oid) as "Result data type",
pg_catalog.pg_get_function_arguments(p.oid) as "Argument data types",
CASE
WHEN p.proisagg THEN 'agg'
WHEN p.proiswindow THEN 'window'
WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger'
ELSE 'normal'
END as "Type",
CASE
WHEN p.provolatile = 'i' THEN 'immutable'
WHEN p.provolatile = 's' THEN 'stable'
WHEN p.provolatile = 'v' THEN 'volatile'
END as "Volatility",
pg_catalog.pg_get_userbyid(p.proowner) as "Owner",
l.lanname as "Language",
p.prosrc as "Source code",
pg_catalog.obj_description(p.oid, 'pg_proc') as "Description"
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang
WHERE pg_catalog.pg_function_is_visible(p.oid)
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
ORDER BY 1, 2, 4;
**************************
In your case the would be what you want:
pg_catalog.pg_get_function_result(p.oid) as "Result data type",
This query will list the stored procedures with the types.
SELECT proname, proargnames as arguments,
oidvectortypes(proargtypes) as arguments_type,
t.typname as return_type,prosrc as source
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_proc p ON pronamespace = n.oid
JOIN pg_type t ON p.prorettype = t.oid
WHERE nspname = 'public'
You can always filter by proname.
Just to get started:
SELECT
*
FROM
pg_proc
JOIN pg_type ON pg_type.oid = ANY(proallargtypes)
WHERE
proname = 'foo';
Are you looking for this?
SELECT proname,
pg_get_function_result(oid)
FROM pg_proc
WHERE proname = 'foo';
if the function returns a record then the type is not known until runtime, demonstrated with:
create or replace function func() returns record language plpgsql immutable as $$
declare
r record;
q record;
begin
select 10, 'hello' into r;
select 11, 'hello', 'helloagain' into q;
if random()>0.5 then
return r;
else
return q;
end if;
end;$$;
in other words, you can't know the type until after you call the function. Once you have called the function, you could dynamically determine information about the record by passing it into a C-language function as referenced here
Thanks for the help guys, I'm think JackPDouglas is correct and that since functions that return record sets can be polymorphic that there's no way to find out the return type definition.
However here's the SQL I was looking for to get the definition of a function that returns a composite type:
SELECT t.typname, attname, a.typname
from pg_type t
JOIN pg_class on (reltype = t.oid)
JOIN pg_attribute on (attrelid = pg_class.oid)
JOIN pg_type a on (atttypid = a.oid)
WHERE t.typname = (
SELECT t.typname
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
INNER JOIN pg_type t ON p.prorettype = t.oid
WHERE n.nspname = 'public'
and proname = 'foo'
ORDER BY proname
);