PostgreSQL: How to use SELECT statement within CASE expression in function? - postgresql

Can we able to use SELECT statement within CASE conditional expression in function?
I have tried the following function to accomplish the above task.
Example: I have a function which is used to display table containing some rows.
create or replace function test(n integer)
returns table (name text,city text) as
$body$
begin
case n when 1 then
select * from table1
when 2 then
select * from table2
when 3 then
select * from view1
end;
end;
$body$
language plpgsql;
--Calling function
select * from test(1);
/*have to show details of table1*/
select * from test(2);
/*have to show details of table2*/
select * from test(3);
/*have to display details of view1*/

Actually, there is a plpgsql CASE statement, not to be confused with the SQL CASE expression:
CREATE OR REPLACE function test(n integer)
RETURNS TABLE (name text, city text) AS
$func$
BEGIN
CASE n
WHEN 1 THEN
RETURN QUERY SELECT t.name, t.city FROM table1 t;
WHEN 2 THEN
RETURN QUERY SELECT t.foo, t.bar FROM table2 t;
WHEN 3 THEN
RETURN QUERY SELECT t.bar, t.bamm FROM view1 t;
END CASE;
END
$func$ LANGUAGE plpgsql;
If you declare your function as RETURNS TABLE (name text, city text), then your SELECT statements should have a column list with matching types.
If on the other hand you want to SELECT *, declare the function as RETURNS SETOF table1 accordingly.
When naming columns in the return type, those variable are visible in the function body. Be sure to table-qualify column names that would conflict with same names. So t.name instead of just name.
Column names from queries inside the function are not visible outside. Only the declared return type. So names don't have to match, just data types.
Either way, I suggest to simplify things:
CREATE OR REPLACE function test(n integer)
RETURNS SETOF table1 AS
$func$
SELECT * FROM table1 t WHERE n = 1
UNION ALL
SELECT * FROM table2 t WHERE n = 2
UNION ALL
SELECT * FROM view1 t WHERE n = 3;
$func$ LANGUAGE sql;
Same result. Just as fast. SQL or PL/pgSQL function is a matter of taste and some other details. PL/pgSQL is probably faster for stand-alone calls. SQL can more easily be nested.

For this you need an IF statement and RETURN QUERY, e.g.
create or replace function test(n integer)
returns table (name text,city text) as
$body$
begin
IF n = 1 THEN
RETURN QUERY select * from table1;
ELIF n = 2 THEN
RETURN QUERY select * from table2;
ELIF n = 3 THEN
RETURN QUERY select * from view1;
END IF;
end;
$body$
language plpgsql;
It's a very weird thing to want to do, though.

Related

Dynamically select column in PostgreSQL

I want to select a column from a table, with the column name being the result of a query like the following:
-- This query returns a single value
with x as (
select a from table1 where <condition>
)
-- my_function() yields a table
select x from my_function()
How do I do that?
Thank you very much.
You could write it in SQL with a temporary function:
CREATE FUNCTION pg_temp.tablefunc()
RETURNS SETOF my_function_result_type
LANGUAGE plpgsql AS
$$DECLARE
v_colname text;
BEGIN
SELECT a INTO v_colname
FROM table1
LIMIT 1;
RETURN QUERY EXECUTE
format(E'SELECT %I\n'
'FROM my_function()',
v_colname);
END;$$;
SELECT * FROM pg_temp.tablefunc();

PL/pgSQL: structure of query does not match function result type

I have a table
create table abac_object (
inbound abac_attribute,
outbound abac_attribute,
created_at timestamptz not null default now(),
primary key (inbound, outbound)
);
abac_attribute is my custom type that is defined like
create type abac_attribute as (
value text,
key text,
namespace_id uuid
);
I try to create a number of functions that work over the table
create function abac_object_list_1(_attr abac_attribute)
returns setof abac_object as $$
select *
from abac_object
where outbound = _attr
$$ language sql stable;
create function abac_object_list_2(_attr1 abac_attribute, _attr2 abac_attribute)
returns setof abac_object as $$
select t1.*
from abac_object as t1
inner join abac_object as t2 on t1.inbound = t2.inbound
where
t1.outbound = _attr1
and t2.outbound = _attr2
$$ language sql stable;
create function abac_object_list_3(_attr1 abac_attribute, _attr2 abac_attribute, _attr3 abac_attribute)
returns setof abac_object as $$
select t1.*
from abac_object as t1
inner join abac_object as t2 on t1.inbound = t2.inbound
inner join abac_object as t3 on t1.inbound = t3.inbound
where
t1.outbound = _attr1
and t2.outbound = _attr2
and t3.outbound = _attr3
$$ language sql stable;
create function abac_object_list(_attrs abac_attribute[], _offset integer, _limit integer)
returns setof abac_attribute as $$
begin
case array_length(_attrs, 1)
when 1 then return query
select t.inbound
from abac_object_list_1(_attrs[1]) as t
order by t.created_at
offset _offset
limit _limit;
when 2 then return query
select t.inbound
from abac_object_list_2(_attrs[1], _attrs[2]) as t
order by t.created_at
offset _offset
limit _limit;
when 3 then return query
select t.inbound
from abac_object_list_3(_attrs[1], _attrs[2], _attrs[3]) as t
order by t.created_at
offset _offset
limit _limit;
else raise exception 'bad argument' using detail = 'array length should be less or equal to 3';
end case;
end
$$ language plpgsql stable;
abac_object_list_1 works fine
select *
from abac_object_list_1(('apple', 'type', '00000000-0000-1000-a000-000000000010') ::abac_attribute);
However with abac_object_list I get the following error
select *
from abac_object_list(array [('apple', 'type', '00000000-0000-1000-a000-000000000010')] :: abac_attribute[], 0, 100);
[42804] ERROR: structure of query does not match function result type Detail: Returned type abac_attribute does not match expected type text in column 1. Where: PL/pgSQL function abac_object_list(abac_attribute[],integer,integer) line 4 at RETURN QUERY
I can't understand where text type comes from.
These functions worked before but then I added created_at column to be able to add explicit ORDER BY clause. And now I can't rewrite functions correctly.
Also maybe there is a better (or more idiomatic) way to define these function.
Could you please help me to figure it out?
Thank you.
P. S.
Well, it seems to work if I write
return query select (t.inbound).*
But I am not sure if it is a right approach.
Also I wonder what is better to use return table or return setof.

Merge multiple result tables and perform final query on result

I have a function returning table, which accumulates output of multiple calls to another function returning table. I would like to perform final query on built table before returning result. Currently I implemented this as two functions, one accumulating and one performing final query, which is ugly:
CREATE OR REPLACE FUNCTION func_accu(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
GATEWAY_ID integer;
BEGIN
FOR GATEWAY_ID IN
execute format(
'SELECT id FROM %1$I.gateway WHERE location_id=%2$L'
, SCHEMA_CUSTOMER, LOCATION_ID)
LOOP
RETURN QUERY execute format(
'SELECT * FROM get_available_networks_gw(%1$L, %2$L)'
, GATEWAY_ID, SCHEMA_CUSTOMER);
END LOOP;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION func_query(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
BEGIN
RETURN QUERY execute format('
SELECT networkid, max(count) FROM func_accu(%2$L, %1$L) GROUP BY networkid;'
, SCHEMA_CUSTOMER, LOCATION_ID);
END;
$$ LANGUAGE plpgsql;
How can this be done in single function, elegantly?
Both functions simplified and merged, also supplying value parameters in the USING clause:
CREATE OR REPLACE FUNCTION pg_temp.func_accu(_location_id integer, schema_customer text)
RETURNS TABLE(networkid integer, count bigint) AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT f.networkid, max(f.ct)
FROM %I.gateway g
, get_available_networks_gw(g.id, $1) f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1'
, _schema_customer)
USING _schema_customer, _location_id;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM func_accu(123, 'my_schema');
Related:
Dynamically access column value in record
I am using alias names for the columns returned by the function (f(networkid, ct)) to be sure because you did not disclose the return type of get_available_networks_gw(). You can use the column names of the return type directly.
The comma (,) in the FROM clause is short syntax for CROSS JOIN LATERAL .... Requires Postgres 9.3 or later.
What is the difference between LATERAL and a subquery in PostgreSQL?
Or you could run this query instead of the function:
SELECT f.networkid, max(f.ct)
FROM myschema.gateway g, get_available_networks_gw(g.id, 'my_schema') f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1;

PostgreSQL: How to display only selected columns of a single table within CASE expression in function?

Example: I am passing two parameters to function namely n(case number) and tname(table name), and want to display rows accordingly.
--Table "testing"
create table testing
(
rollno integer,
fname text,
lname text,
age integer,
branch text,
phno integer,
email text,
address text,
city text,
state text,
country text
)
--Rows insertion
insert into testing values(1,'aaa','bbb',25,'CS',1234567890,'abc#gmail.com','sector1','xyz','zyx','yxz');
insert into testing values(2,'zzz','xxx',25,'EE',987654321,'zzz#gmail.com','sector2','uvw','wvu','vuw');
--Function "f1()"
create or replace function f1(n integer,tname varchar)/*n for case number and tname for table name */
returns setof tname as
$body$
begin
case n
when 1 then
return query execute format ($$ select rollno,fname from %I $$,tname);
when 2 then
return query execute format ($$ select lname,age,branch from %I $$,tname);
when 3 then
return query execute format ($$ select phno,email,address,city,country from %I $$,tname);
end case;
end
$body$
language plpgsql;
--Function calling
select * from f1(1,'testing');
/*Show only case "1" select query result*/
select * from f1(2,'testing');
/*Show only case "2" select query result*/
select * from f1(3,'testing');
/*Show only case "3" select query result*/
While Craig is correct that return types cannot be dynamic in function declarations, there is a way around this with polymorphic types. This is surprisingly simple and would actually work flawlessly:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM '|| pg_typeof(_tbl_type);
END
$func$ LANGUAGE plpgsql;
Call (important!):
SELECT rollno,fname FROM data_of(NULL::testing);
SELECT * FROM data_of(NULL::my_schema.my_table);
SELECT * FROM data_of(NULL::my_custom_type);
What you need is a well-known type. For every table there is a well-known type automatically. But you can create any type, cast NULL to it and pass it to the function. This way you can build exactly what you have in your question ...
Related answer with a lot more details:
Refactor a PL/pgSQL function to return the output of various SELECT queries

How to return multiple rows from PL/pgSQL function?

I have spent good amount of time trying to figure it out and I haven't been able to resolve it. So, I need your help please.
I am trying to write a PL/pgSQL function that returns multiple rows. The function I wrote is shown below. But it is not working.
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY SELECT department_id into result_record.visits
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields;
It is returning this error:
ERROR: RETURN cannot have a parameter in function returning set;
use RETURN NEXT at or near "QUERY"
After fixing the bugs #Pavel pointed out, also define your return type properly, or you have to provide a column definition list with every call.
This call:
SELECT * FROM get_object_fields()
... assumes that Postgres knows how to expand *. Since you are returning anonymous records, you get an exception:
ERROR: a column definition list is required for functions returning "record"
One way (of several) to fix this is with RETURNS TABLE (Postgres 8.4+):
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS TABLE (department_id int) AS
$func$
BEGIN
RETURN QUERY
SELECT department_id
FROM fact_department_daily
WHERE report_date = '2013-06-07';
END
$func$ LANGUAGE plpgsql;
Works for SQL functions just the same.
Related:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
I see more bugs:
first, a SET RETURNING FUNCTIONS call has following syntax
SELECT * FROM get_object_fields()
second - RETURN QUERY forwards query result to output directly. You cannot store this result to variable - it is not possible ever in PostgreSQL now.
BEGIN
RETURN QUERY SELECT ....; -- result is forwarded to output directly
RETURN; -- there will not be any next result, finish execution
END;
third - these simple functions is better to implement in SQL languages
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD AS $$
SELECT department_id WHERE ...
$$ LANGUAGE sql STABLE;
Here's one way
drop function if exists get_test_type();
drop type if exists test_comp;
drop type if exists test_type;
drop type if exists test_person;
create type test_type as (
foo int,
bar int
);
create type test_person as (
first_name text,
last_name text
);
create type test_comp as
(
prop_a test_type[],
prop_b test_person[]
);
create or replace function get_test_type()
returns test_comp
as $$
declare
a test_type[];
b test_person[];
x test_comp;
begin
a := array(
select row (m.message_id, m.message_id)
from message m
);
-- alternative 'strongly typed'
b := array[
row('Bob', 'Jones')::test_person,
row('Mike', 'Reid')::test_person
]::test_person[];
-- alternative 'loosely typed'
b := array[
row('Bob', 'Jones'),
row('Mike', 'Reid')
];
-- using a select
b := array (
select row ('Jake', 'Scott')
union all
select row ('Suraksha', 'Setty')
);
x := row(a, b);
return x;
end;
$$
language 'plpgsql' stable;
select * from get_test_type();
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS table (department_id integer)
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY
SELECT department_id
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields()