Returning a table from a cursor - postgresql

I have a function that returns a table of calculated values based on a customer id. I need to get values for all customers; I made a cursor but I can't make it return the set.
Customer table:
id name
---- ----
CN102 Dude
CN103 Guy
CN104 Mate
Function:
SELECT * FROM get_custom_fields('CN104');
name field_value
---- -----
POP 9
Z44 blue
POP 19
Please note there could be multiple rows with the same name.
This is my cursor:
CREATE OR REPLACE FUNCTION my_cursor ()
RETURNS SETOF RECORD AS $$
DECLARE
v_customer_rec RECORD;
v_pop RECORD;
BEGIN
FOR v_customer_rec IN SELECT ucn FROM customer LOOP
SELECT INTO v_pop field_value from get_custom_fields(v_customer_rec.ucn) where custom_field='POP';
RAISE NOTICE 'Customer % Value %', v_customer_rec.ucn,v_pop;
-- RETURN QUERY select field_value from get_custom_fields(v_customer_rec.ucn) where custom_field='POP';
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
This returns:
db=# select my_cursor();
NOTICE: Customer CN102 Value (5)
NOTICE: Customer CN103 Value (12)
NOTICE: Customer CN104 Value (9)
NOTICE: Customer CN104 Value (19)
my_cursor
-------------
(0 rows)
So I know it should work. But if use RETURN QUERY (as commented in the code) I get the following error:
ERROR: set-valued function called in context that cannot accept a set
CONTEXT: PL/pgSQL function "my_cursor" line 9 at RETURN QUERY
How can I make it return the values in a table or set?
I'm trying to get:
ucn field_value
----- -----------
CN102 5
CN103 12
CN104 9
CN104 19

Your (simplified) function could look like this:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS SETOF RECORD AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT ucn FROM customer
LOOP
RETURN QUERY
SELECT *
FROM get_custom_fields(_ucn)
WHERE name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
I am assuming data type text for ucn here.
But really, you should define the RETURN type, to avoid having to provide a column definition list with every call. If you just want the column field_value, and it is type text:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS SETOF text AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT ucn FROM customer
LOOP
RETURN QUERY
SELECT field_value
FROM get_custom_fields(_ucn)
WHERE name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
Or use RETURNS TABLE() for multiple columns per row:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS TABLE(ucn text, field_value text) AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT c.ucn FROM customer c
LOOP
RETURN QUERY
SELECT g.ucn, g.field_value
FROM get_custom_fields(_ucn) g
WHERE g.name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
Be aware that the columns of the RETURN type are visible in the function body. Table-qualify columns of the same name to avoid naming conflicts.

Related

Dynamically access RECORD value in pl/pgsql function

Why does accessing a value of the RECORD argument like this works:
CREATE OR REPLACE FUNCTION TT_GetVal1(a RECORD)
RETURNS text AS $$
DECLARE
BEGIN
RETURN a.val1::text;
END;
$$ LANGUAGE plpgsql VOLATILE;
SELECT TT_GetVal1(foo.*)
FROM (SELECT 1 id, 'a' val1) foo;
But not like this:
CREATE OR REPLACE FUNCTION TT_GetVal2(a RECORD)
RETURNS text AS $$
DECLARE
query text;
result text;
BEGIN
query = 'SELECT ($1).val1::text';
EXECUTE query INTO result USING a;
RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE;
SELECT TT_GetVal2(foo.*)
FROM (SELECT 1 id, 'a' val1) foo;
which returns:
ERROR: could not identify column "val1" in record data type
LINE 1: SELECT ($1).val1::text
How can I dynamically access RECORD values?
The RECORD only lives inside the plpgsql scope. The values are passed to the execute but not the column names.
You can - if the record is from a table - do something like this:
create table table1 (
id integer,
val1 text
);
CREATE OR REPLACE FUNCTION TT_GetVal2(a RECORD)
RETURNS text AS $$
DECLARE
query text;
result text;
BEGIN
query = 'SELECT ($1::text::table1).val1';
EXECUTE query INTO result USING a;
RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE;
SELECT TT_GetVal2(foo)
FROM (SELECT 1 id, 'a' val1) foo;
Best regards,
Bjarni

How to combine custiom defined variables and display them as records of a table in postgres

I'm a beginner in plpgsql and working on a project which requires me to write a function that returns two variables in the form of 2 columns (res,Result). I've done a quite a bit of searching but didn't find answer for the same. The reference to my code is below
CREATE OR REPLACE FUNCTION propID(character varying)
RETURNS SETOF RECORD AS $val$
DECLARE
t_row record;
res BOOLEAN;
result character varying;
value record;
BEGIN
FOR t_row IN SELECT property_id FROM property_table WHERE ward_id::TEXT = $1 LOOP
RAISE NOTICE 'Analyzing %', t_row;
res := false; -- here i'm going to replace this value with a function whos return type is boolean in future
result := t_row.property_id;
return next result; --here i want to return 2 variables (res,result) in the form of two columns (id,value)
END LOOP;
END;
$val$
language plpgsql;
Any help on the above query would be very much appreciated.
Assuming that property_id and ward_id are integers you can achieve your goal in a simple query like this:
select some_function_returning_boolean(property_id), property_id
from property_table
where ward_id = 1; -- input parameter
If you absolutely need a function, it can be an SQL function like
create or replace function prop_id(integer)
returns table (res boolean, id int) language sql
as $$
select some_function_returning_boolean(property_id), property_id
from property_table
where ward_id = $1
$$;
In a plpgsql function you should use return query:
create or replace function prop_id(integer)
returns table (res boolean, id int) language plpgsql
as $$
begin
return query
select some_function_returning_boolean(property_id), property_id
from property_table
where ward_id = $1;
end
$$;

Query is not working in Postgresql function [duplicate]

I have 30 state wise data tables. Table name like aa_shg_detail, ab_shg_detail, ac_shg_detail.
I have also main state table in which state short names and state codes are stored. I have created 2 postgresql functions getTableName(Code text) and getDataByTable().
In the first function I pass the state code so it fetches the state short name and short name concat with _shg_detail String and prepare full table name and return it. Example: If I pass state code 2 the query fetch state short name based on state code 2 from the state's main table. The state short name is 'ab' for state code 2 so after concat state short name with _shg_detail first function return ab_shg_detail table name.
Second function gets the table name from first function and fetch data from that table. But I am getting error in the second function.
CREATE OR REPLACE FUNCTION getTableName(code text)
RETURNS text
AS $$
select concat(lower(state_short_name), '_shg_detail') from main_state where state_code = code))
$$
LANGUAGE sql;
CREATE OR REPLACE FUNCTION getDataByTable()
RETURNS text AS $$
DECLARE
tablename text;
BEGIN
tablename := gettablename('2');
RETURN (select shg_code from tablename);
END;
$$ LANGUAGE plpgsql;
When I execute a second function select getDataByTable() then I am getting this error every time:
ERROR: relation "tablename" does not exist
LINE 1: SELECT (select shg_code from tableName)
You need dynamic SQL for that:
CREATE OR REPLACE FUNCTION getDataByTable()
RETURNS text AS $$
DECLARE
tablename text;
l_result text;
BEGIN
tablename := gettablename('2');
execute format('select shg_code from %I', tablename)
into l_result;
RETURN l_result;
END;
$$ LANGUAGE plpgsql;
The %I placeholder of the format() function properly deals with quoting of identifiers if needed.

Postgresql query across different tables with dynamic query

I'm trying to get a customer id which can be placed in one of ten different tables. I don't want to hard code those table names to find it so I tried postgresql function as follows.
create or replace FUNCTION test() RETURNS SETOF RECORD AS $$
DECLARE
rec record;
BEGIN
select id from schema.table_0201_0228 limit 1 into rec;
return next rec;
select id from schema.table_0301_0331 limit 1 into rec;
return next rec;
END $$ language plpgsql;
select * from test() as (id int)
As I'm not familiar with postgresql function usage, how can I improve the code to replace 'schema.table1' with a variable, loop each table and return the result?
NOTE: table names may change overtime. For example, table_0201_0228 and table_0301_0331 are for February and March respectively.
You need dynamic SQL for that:
create or replace FUNCTION test(p_schema text)
RETURNS table(id int)
AS $$
DECLARE
l_tab record;
l_sql text;
BEGIN
for l_tab in (select schemaname, tablename
from pg_tables
where schemaname = p_schema)
loop
l_sql := format('select id from %I.%I limit 1', l_tab.schemaname, l_tab.tablename);
return query execute l_sql;
end loop;
END $$
language plpgsql;
I made the schema name a parameter, but of course you can hard-code it. As the function is defined as returns table there is no need to specify the column name when using it:
select *
from test('some_schema');

How to return no records found from a stored procedure

Is it possible to have a stored procedure behave exactly like a regular select query when no records are found, or is this a driver issue.
For example, with go, a query that returns no rows will return an sql.ErrNoRows error. However, this will not:
create table emptytable(id int);
create function selectany() returns emptytable as $$
DECLARE
_out emptytable;
BEGIN
SELECT * INTO emptytable FROM emptytable limit 1;
RETURN _out;
END;
$$ LANGUAGE PLPGSQL;
I have tried SELECT INTO STRICT, and while that raises a "query returned no rows" error, it is not the same as a non-stored procedure query. Neither is raising NO_DATA_FOUND.
If I understand your requirements correctly:
Return one or no row from a function and allow to do more with the returned row (if any).
Test table:
CREATE TABLE emptytable(id int, txt text); -- multiple columns
To return one or no complete table row:
CREATE OR REPLACE FUNCTION selectany_all()
RETURNS SETOF emptytable AS
$func$
DECLARE
_out emptytable;
BEGIN
FOR _out IN
SELECT * FROM emptytable LIMIT 1
LOOP
-- do something with _out before returning
RAISE NOTICE 'before: %', _out;
RETURN NEXT _out;
-- or do something with _out after returning row
RAISE NOTICE 'after: %', _out;
END LOOP;
END
$func$ LANGUAGE plpgsql;
For a more flexible approach: return arbitrary columns:
CREATE OR REPLACE FUNCTION selectany_any()
RETURNS TABLE (id int, txt text) AS
$func$
BEGIN
FOR id, txt IN
SELECT e.id, e.txt FROM emptytable e LIMIT 1
LOOP
-- do something with id and text before returning
RAISE NOTICE 'before: %, %', id, txt;
RETURN NEXT;
-- or do something with id and text after returning row
RAISE NOTICE 'after: %, %', id, txt;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Note, the LOOP is never entered if there is no row. Accordingly you will get no NOTICE from my test code.
Both functions work for n rows returned as well, LIMIT 1 is just for this particular request.
Closely related, wtih more explanation:
Return multiple fields as a record in PostgreSQL with PL/pgSQL
2.5 options:
1a) If you just need to return a query, you can use SETOF and RETURN QUERY
1b) or just use language SQL as #ClodoaldoNeto, which returns a query natively using sql's SELECT stmt
2) If you need to process the result in the procedure, you must use SETOF and RETURN NEXT, ensuring you check IF FOUND THEN RETURN; (note lack of NEXT, which if given will act as a single blank row is returned)
Ideally, I'd like to not use SETOF for procedures known to return exactly none or 1 rows, but it seems SETOF is required to get a procedure to query like an sql statement from the app and have drivers recognize NO ROWS RETURNED
Examples below:
create table emptytable(id int);
create function selectany() returns setof emptytable as $$
DECLARE
_out emptytable;
BEGIN
SELECT * INTO _out FROM emptytable limit 1;
IF FOUND THEN
RETURN _out;
END IF;
RETURN;
END;
$$ LANGUAGE PLPGSQL;
create function selectany_rq() returns setof emptytable as $$
BEGIN
RETURN QUERY SELECT * INTO _out FROM emptytable limit 1;
END;
$$ LANGUAGE PLPGSQL;
As suggested in the comments do return setof emptytable
create function selectany()
returns setof emptytable as $$
select *
from emptytable
limit 1
;
$$ language sql;
Plain sql can do that