Wrap PostgreSQL functions in another to conditionally combine results - sql

I have a function get_oversight(int) that returns a single column:
person_id
----------
100
101
102
103
104
And another function get_unfiltered_responsibility(int) that returns the same structure:
person_id
----------
100
103
104
I need a 3rd function that evaluates and returns a subset of the above. Here's some pseudo code:
def function get_responsibility(person_id int):
oversight = get_oversight(person_id)
unfiltered_responsibility = get_responsibility(person_id)
if number_of_records(unfiltered_responsibility) == 0:
return oversight
else
return intersection(unfiltered_responsibility, oversight)
# only ids from unfiltered_responsibility that are ALSO IN oversight
What would that 3rd function look like? (using v9.6)

Assuming that both functions never return duplicates. Else you'll have to define exactly how to deal with those.
In a plpgsql function you can conveniently use the special variable FOUND
CREATE OR REPLACE FUNCTION get_combo_plpgsql(int)
RETURNS TABLE(person_id int) LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY
SELECT *
FROM get_oversight($1)
JOIN get_unfiltered_responsibility($1) USING (person_id);
IF NOT FOUND THEN
RETURN QUERY
SELECT * FROM get_oversight($1);
END IF;
END
$func$;
This only works on the assumption that get_unfiltered_responsibility() always returns a subset of get_oversight(), like your sample data seems to suggest. Then, if the join returns no rows, we can conclude that get_unfiltered_responsibility() came up empty.
Related:
Why is IS NOT NULL false when checking a row type?
Alternatively, this CTE wrapped in a simple SQL function works in any case, subset or not (can be a plpgsql function too, if needed):
CREATE OR REPLACE FUNCTION get_combo_sql(int)
RETURNS TABLE(person_id int) LANGUAGE sql AS
$func$
WITH cte AS (SELECT * FROM get_unfiltered_responsibility($1))
SELECT *
FROM get_oversight($1) o
WHERE EXISTS (
SELECT FROM cte
WHERE person_id = o.person_id
)
OR NOT EXISTS (TABLE cte)
$func$;
Related:
Is there a shortcut for SELECT * FROM?
db<>fiddle here

Related

How to add column inside postgres function without saving it to the db?

I have a postgres DB and I want to create a function that returns a copy of my table with a new column that has a value of 1 if its id is inside the array(idds[]) that the function gets as an input.
In the code below I've try to create a temporary table(chosen) that have id if it's in the idds array and to manually add the isChosen column that obviously doesn't work...
CREATE OR REPLACE FUNCTION public.getTableFromArray(idds integer[])
RETURNS table(
id INTEGER,
isChosen INTEGER
)
LANGUAGE 'plpgsql'
AS $BODY$
begin
with chosen AS(SELECT id,isChosen=1 FROM table1 WHERE ARRAY[table1.id] <# idds)
return query
SELECT id FROM table1 LEFT JOIN chosen ON table1.id=chosen.id;
end;
$BODY$;
Or, with a lot less noise, a proper boolean output column, and without the unhelpful CaMeL case identifiers in a plain SQL function:
CREATE OR REPLACE FUNCTION public.get_table_from_array(idds integer[])
RETURNS TABLE(id int, is_chosen bool)
LANGUAGE sql AS
'SELECT t.id, t.id = ANY(idds) FROM table1 t';
Might as well just run the SQL command directly, though:
SELECT id, id = ANY('{1,2,3}'::int[]) AS is_chosen FROM table1;
you can use this query instead :
select * , case when ARRAY[table1.id] <# idds then 1 else 0 end as choosen FROM table1;
so:
CREATE OR REPLACE FUNCTION public.getTableFromArray(idds integer[])
RETURNS table(
id INTEGER,
isChosen INTEGER
)
LANGUAGE 'plpgsql'
AS $BODY$
begin
return query
select id , case when ARRAY[table1.id] <# idds then 1 else 0 end as isChosen FROM table1;
end;
$BODY$;

How return dynamic number of columns in function?

In PostgreSQL 11 database I have table with 6 column. Next function return static number of defined columns.
CREATE FUNCTION CALCULATION(INTEGER)
RETURNS TABLE(
ORGANIZATION_ID INT4,
ORGANIZATION_NAME VARCHAR,
ORGANIZATION_RANG INT4,
PARENT_ORGANIZATION_ID INT4,
PARENT_ORGANIZATION_NAME VARCHAR,
PARENT_ORGANIZATION_RANG INT4
) AS $$
SELECT * FROM ANALYTICS;
$$ LANGUAGE SQL;
How can I make an SQL function in Postgres 11 which return a result set with dynamic number of columns according to a parameter passed in?
For example if I call SELECT * FROM CALCULATION(2);, function return first 2 columns.
If this is not possible with an SQL function, is it possible with a PL/pgSQL function?
This is possible for RECORD returning functions.
CREATE FUNCTION calculation(how_many integer) RETURNS SETOF RECORD
LANGUAGE plpgsql
AS $fff$
BEGIN
IF how_many = 1
THEN RETURN QUERY SELECT 'foo'::text;
ELSIF how_many = 2
THEN RETURN QUERY SELECT 'foo'::text, 'bar'::text;
END IF;
END;
$fff$
;
And now you can do:
jbet=> SELECT * FROM calculation(1) AS f(first_col text);
first_col
-----------
foo
(1 row)
jbet=> SELECT * FROM calculation(2) AS f(first_col text, second_col text);
first_col | second_col
-----------+------------
foo | bar
(1 row)
The very serious downside is that each time you call the function you have to define set of returned columns, so I don't think you'll find this answer useful : )
Anyway, Postgresql needs to know returned type of each SELECT before it runs the query, so one or other way you have to define the columns.
JSON return value could be a reasonable answer if you just want the data and don't care if there are separate columns or not.
Backing up a step, why not use a standard select to get the columns you want from your set-returning function?
select organization_name,
organization_rang,
parent_organization_name,
parent_organization_rang
from calculation();
That's easy to follow and flexible. I'm guessing that you've written a simplified example and have a good reason for what you're asking...but I figured I'd double-check.

Input table for PL/pgSQL function

I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function

Retrieving more than one value from the function

TableA
Id imge
-- ----
1 1.jpeg
2 2.jpeg
1 1.jpeg
1 1.jpeg
o/p needed
id image
------------
1 1.jpeg
1.jpeg
1.jpeg
I created a function,
create or replace function(vid in integer,vimg out varchar) returns setof record as
$$
declare
im varchar;
begin
select image into im from tablea wher id=$1;
return query
select im;
$$
end;
plpgsql
But it's not working. I need to retrieve the images without using the arrays and loops.
You are declaring your function as setof record meaning that it will return any number of rows spanning. You need to redeclare the function and change internal select's to match returning type.
Or I'm wrong and I just miss what you are trying to do.
I think simple function like this is better to write in language sql instead of plpgsql:
create or replace function func(vid in integer)
returns table(vimg varchar)
as
$$
select imge from tablea where id=$1;
$$ language sql;
Anyway, to return multiple records from function your can return either table or setof record.
sql fiddle demo
You might be looking for GROUP_CONCAT()
SELECT GROUP_CONCAT(imge) as images
FROM TableA GROUP BY Id;
Oh I missed. You were in PostgreSQL, huh?
No worries. There is an equivalent for group_concat in PostgreSQL: array_agg
SELECT id, array_agg(imge)
FROM TableA GROUP BY Id;

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo