Postgres stored procedure/function - postgresql

New to Stored Procedures , have a requirement where I need to execute multiple queries inside stored procedure and return results. I would like to know whether this is possible or not ..
Ex :
Query 1 returns a list of userid ..
Select userid from user where username = ?
For each userid from the above query , I need to execute three different queries like
Query 2 select session_details from session where userid = ?
Query 3 select location from location where userid = ?
The return value should be a collection of , session_details and location.
Is this possible,can you provide some hints?

You can loop through query results like so:
FOR id IN Select userid from user where username = ?
LOOP
...
END LOOP;

As #Fahad Anjum says in his comment, its better if you can do it in a query. But if that's not posible, you have tree posibilities to achive what you want.
SETOF
TABLE
refcursor
1. SETOF
You can return a set of values. The set can be an existing table, a temporal table, or a TYPE you define.
TYPE example:
-- In your case the type could be (userid integer, session integer, location text)
CREATE TYPE tester AS (id integer);
-- The pl returns a SETOF the created type.
CREATE OR REPLACE FUNCTION test() RETURNS SETOF tester
AS $$
BEGIN
RETURN QUERY SELECT generate_series(1, 3) as id;
END;
$$ LANGUAGE plpgsql
-- Then, you get the set by selecting the PL as if it were a table.
SELECT * FROM test();
Table and Temp Table examples:
-- Create a temporal table o a regular table:
CREATE TEMP TABLE test_table(id integer);
-- or CREATE TABLE test_table(id integer);
-- or use an existing table in your schema(s);
-- The pl returns a SETOF the table you need
CREATE OR REPLACE FUNCTION test() RETURNS SETOF test_table
AS $$
BEGIN
RETURN QUERY SELECT generate_series(1, 3) as id;
END;
$$ LANGUAGE plpgsql
-- Then, you get the set by selecting the PL as if it were a table.
SELECT * FROM test();
-- NOTE: Since you are only returning a SETOF the table,
-- you don't insert any data into the table.
-- So, if you select the 'temp' table you won't see any changes.
SELECT * FROM test_table
2. TABLE
A PL can return a table, it would be similar to create a temporal table and then return a SETOF, but, in this case you declare de 'temp' table on the 'returns' sentence of the PL.
-- Next to TABLE you define the columns of the table the PL will return
CREATE OR REPLACE FUNCTION test() RETURNS TABLE (id integer)
AS $$
BEGIN
RETURN QUERY SELECT generate_series(1, 3) as id;
END;
$$ LANGUAGE plpgsql
-- As the other examples, you select the PL to get the data.
SELECT * FROM test();
3. refcursor
This one is the more complex solution. You return a cursor, not the actual data. If you need 'dynamic' values for your returning set, this is the solution.
But since you need static data, you won't need this option.
The use of any of these ways depends on any specific case, if you use regularly the userid,session,location in different ways and PLs, it would be better to Use the SETOF with a type.
If you have a table that has the userid,session,location columns, it's better to return a SETOF table.
If you just use the userid,session,location for one case, then it would be better to use a 'RETURNS TABLE' approach.
If you need to return a dynamic set you would have to use cursors... but that solution is really more advanced.

Based solely on your example, here's probably the easiest way to do it:
CREATE FUNCTION my_func(user_id INTEGER)
RETURNS TABLE (userid INTEGER, session INTEGER, location TEXT) AS
$$
SELECT u.userid, s.session, l.location
FROM -- etc... your query here
$$
LANGUAGE SQL STABLE;
Addressing comment:
That's a bit of a different question. One question is how to return multiple records containing multiple fields in a stored procedure. One way is as above.
The other question is how to write a query that gets data from multiple tables. Again, there are many ways to do it. One way is (again, based on my interpretation of your requirements in the examples):
SELECT userid
, ARRAY_AGG(SELECT session_details FROM session s WHERE s.userid = u.userid)
, ARRAY_AGG(SELECT l.location FROM location l WHERE l.userid = u.userid)
FROM user u
WHERE username = user_name
This will return one record containing the user_id, an array of session_details for that user, and an array of locations for that user.
Then the function can be changed to:
CREATE FUNCTION my_func(user_name TEXT, OUT userid INTEGER, OUT session_details TEXT[], OUT locations TEXT[])
AS $$
SELECT userid
, ARRAY(SELECT session_details FROM session s WHERE s.userid = u.userid)
, ARRAY(SELECT l.location FROM location l WHERE l.userid = u.userid)
FROM user u
WHERE username = user_name;
$$ LANGUAGE SQL STABLE;

Related

possible to have multiple temp tables per postgres function?

i'm trying to find the total number of contacts who ARE in some lists, but who are NOT in others. basically i want to get the set difference between these two groups of contacts.
here's my function. i'm using supabase. this function works perfectly via the supabase browser sql editor
CREATE or replace FUNCTION get_net_num_contacts(to_list_ids bigint[], not_to_list_ids bigint[])
returns bigint
AS $$
declare
net_num_contacts bigint;
BEGIN
CREATE TEMP TABLE IF NOT EXISTS t1 AS
select distinct list_members.contact_id from list_members where list_members.list_id = ANY(to_list_ids);
CREATE TEMP TABLE IF NOT EXISTS t2 AS
select distinct list_members.contact_id from list_members where list_members.list_id = ANY(not_to_list_ids);
CREATE TEMP TABLE IF NOT EXISTS t3 AS
select t1.contact_id from t1 except select t2.contact_id from t2;
SELECT count(*) from t3 into net_num_contacts;
return net_num_contacts;
END;
$$ LANGUAGE plpgsql;
but when i save this to my db and try running it in my web app with an .rpc call, it always returns 0.
i did some tinkering and found that the function, loaded into supabase via my filesystem (supabase start), started returning empty sets any time i tried to create 2 or more temp tables.
this works great:
CREATE or replace FUNCTION demo_works(to_list_ids bigint[], not_to_list_ids bigint[])
returns setof bigint
AS $$
declare
net_num_contacts bigint;
BEGIN
CREATE TEMP TABLE IF NOT EXISTS t1 AS
select distinct list_members.contact_id from list_members where list_members.list_id = ANY(to_list_ids);
return query select * from t1;
END;
$$ LANGUAGE plpgsql;
doesn't work, always returns an empty array:
CREATE or replace FUNCTION demo_doesnt_work(to_list_ids bigint[], not_to_list_ids bigint[])
returns setof bigint
AS $$
declare
net_num_contacts bigint;
BEGIN
CREATE TEMP TABLE IF NOT EXISTS t1 AS
select distinct list_members.contact_id from list_members where list_members.list_id = ANY(to_list_ids);
CREATE TEMP TABLE IF NOT EXISTS t2 AS
select distinct list_members.contact_id from list_members where list_members.list_id = ANY(not_to_list_ids);
return query select * from t1;
END;
$$ LANGUAGE plpgsql;
i've seen hints online that maybe it's understood that you can't have multiple temp tables in postgres (i lost the link), and indications that some systems dont allow temp tables in functions at all.
so i'm confused as to why this works in the supabase sql editor but not after being saved into my db. and hoping to gain clarity on the limitations here, or maybe i'm doing something irrelevant wrong.
and if anyone has insight on how to properly accomplish what i'm trying to do, that would be great too
update
here's my successful alternative function for getting the set difference between these two groups of contacts, if useful for anyone. and open to feedback. but i'm still very curious about why my initial attempt didn't usually work.
CREATE or replace FUNCTION get_net_num_contacts(to_list_ids bigint[], not_to_list_ids bigint[])
returns bigint
AS $$
declare
net_num_contacts bigint;
BEGIN
WITH r0 as
(
WITH r1 AS
(
select distinct contact_id from list_members where list_id = ANY(to_list_ids)
)
select contact_id from r1 except
select contact_id from list_members where list_id = ANY(not_to_list_ids)
)
select count(*) from r0 into net_num_contacts;
return net_num_contacts;
END;
$$ LANGUAGE plpgsql;

Query on Return Statement - PostgreSQL

I have this question, I was doing some migration from SQL Server to PostgreSQL 12.
The scenario, I am trying to accomplish:
The function should have a RETURN Statement, be it with SETOF 'tableType' or RETURN TABLE ( some number of columns )
The body starts with a count of records, if there is no record found based on input parameters, then simply Return Zero (0), else, return the entire set of record defined in the RETURN Statement.
The Equivalent part in SQL Server or Oracle is: They can just put a SELECT Statement inside a Procedure to accomplish this. But, its a kind of difficult in case of PostgreSQL.
Any suggestion, please.
What I could accomplish still now - If no record found, it will simply return NULL, may be using PERFORM, or may be selecting NULL as column name for the returning tableType columns.
I hope I am clear !
What I want is something like -
============================================================
CREATE OR REPLACE FUNCTION public.get_some_data(
id integer)
RETURNS TABLE ( id_1 integer, name character varying )
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
p_id alias for $1;
v_cnt integer:=0;
BEGIN
SELECT COUNT(1) FROM public.exampleTable e
WHERE id::integer = e.id::integer;
IF v_cnt= 0 THEN
SELECT 0;
ELSE
SELECT
a.id, a.name
public.exampleTable a
where a.id = p_id;
END;
$BODY$;
If you just want to return a set of a single table, using returns setof some_table is indeed the easiest way. The most basic SQL function to do that would be:
create function get_data()
returns setof some_table
as
$$
select *
from some_table;
$$
language sql;
PL/pgSQL isn't really necessary to put a SELECT statement into a function, but if you need to do other things, you need to use RETURN QUERY in a PL/pgSQL function:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
end;
$$
language plpgsql;
A function as exactly one return type. You can't have a function that sometimes returns an integer and sometimes returns thousands of rows with a dozen columns.
The only thing you could do, if you insist on returning something is something like this:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
if not found then
return query
select (null::some_table).*;
end if;
end;
$$
language plpgsql;
But I would consider the above an extremely ugly and confusing (not to say stupid) solution. I certainly wouldn't let that pass through a code review.
The caller of the function can test if something was returned in the same way I implemented that ugly hack: check the found variable after using the function.
One more hack to get as close as possible to what you want. But I will repeat what others have told you: You cannot do what you want directly. Just because MS SQL Server lets you get away poor coding does not mean Postgres is obligated to do so. As the link by #a_horse_with_no_name implies converting code is easy, once you migrate how you think about the problem in the first place. The closest you can get is return a tuple with a 0 id. The following is one way.
create or replace function public.get_some_data(
p_id integer)
returns table ( id integer, name character varying )
language plpgsql
as $$
declare
v_at_least_one boolean = false;
v_exp_rec record;
begin
for v_exp_rec in
select a.id, a.name
from public.exampletable a
where a.id = p_id
union all
select 0,null
loop
if v_exp_rec.id::integer > 0
or (v_exp_rec.id::integer = 0 and not v_at_least_one)
then
id = v_exp_rec.id;
name = v_exp_rec.name;
return next;
v_at_least_one = true;
end if;
end loop ;
return;
end
$$;
But that is still just a hack and assumes there in not valid row with id=0. A much better approach would by for the calling routing to check what the function returns (it has to do that in one way or another anyway) and let the function just return the data found instead of making up data. That is that mindset shift. Doing that you can reduce this function to a simple select statement:
create or replace function public.get_some_data2(
p_id integer)
returns table ( id integer, name character varying )
language sql strict
as $$
select a.id, a.name
from public.exampletable a
where a.id = p_id;
$$;
Or one of the other solutions offered.

Is it possible to write a postgres function that will handle a many to many join?

I have a job table. I have an industries table. Jobs and industries have a many to many relationship via a join table called industriesjobs. Both tables have uuid is their primary key. My question is two fold. Firstly is it feasible to write two functions to insert data like this? If this is feasible then my second question is how do I express an array of the uuid column type. I'm unsure of the syntax.
CREATE OR REPLACE FUNCTION linkJobToIndustries(jobId uuid, industiresId uuid[]) RETURNS void AS $$
DECLARE
industryId uuid[];
BEGIN
FOREACH industryId SLICE 1 IN ARRAY industriesId LOOP
INSERT INTO industriesjobs (industry_id, job_id) VALUES (industryId, jobId);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION insertJobWithIndistries(orginsation varchar, title varchar, addressId uuid, industryIds uuid[]) RETURNS uuid AS $$
DECLARE
jobId uuid;
BEGIN
INSERT INTO jobs ("organisation", "title", "address_id") VALUES (orginsation, title, addressId) RETURNING id INTO jobId;
SELECT JobbaLinkJobToIndustries(jobId, industryIds);
END;
$$ LANGUAGE plpgsql;
SELECT jobId FROM insertJobWithIndistries(
'Acme Inc'::varchar,
'Bomb Tester'::varchar,
'0030cfb3-1a03-4c5a-9afa-6b69376abe2e',
{ 19c2e0ee-acd5-48b2-9fac-077ad4d49b19, 21f8ffb7-e155-4c8f-acf0-9e991325784, 28c18acd-99ba-46ac-a2dc-59ce952eecf2 }
);
Thanks in advance.
Key to a solution are the function unnest() to (per documentation):
expand an array to a set of rows
And a data-modifying CTE.
A simple query does the job:
WITH ins_job AS (
INSERT INTO jobs (organisation, title, address_id)
SELECT 'Acme Inc', 'Bomb Tester', '0030cfb3-1a03-4c5a-9afa-6b69376abe2e' -- job-data here
RETURNING id
)
INSERT INTO industriesjobs (industry_id, job_id)
SELECT indid, id
FROM ins_job i -- that's a single row, so a CROSS JOIN is OK
, unnest('{19c2e0ee-acd5-48b2-9fac-077ad4d49b19
, 21f8ffb7-e155-4c8f-acf0-9e9913257845
, 28c18acd-99ba-46ac-a2dc-59ce952eecf2}'::uuid[]) indid; -- industry IDs here
Also demonstrating proper syntax for an array of uuid. (White space between elements and separators is irrelevant while not inside double-quotes.)
One of your UUIDs was one character short:
21f8ffb7-e155-4c8f-acf0-9e991325784
Must be something like:
21f8ffb7-e155-4c8f-acf0-9e9913257845 -- one more character
If you need functions, you do that, too:
CREATE OR REPLACE FUNCTION link_job_to_industries(_jobid uuid, _indids uuid[])
RETURNS void AS
$func$
INSERT INTO industriesjobs (industry_id, job_id)
SELECT _indid, _jobid
FROM unnest(_indids) _indid;
$func$ LANGUAGE sql;
Etc.
Related:
Insert data in 3 tables at a time using Postgres
How to insert multiple rows using a function in PostgreSQL

PL/pgSQL: Inserted data not available when function returns

I have an interesting problem that me and my collegue troubles for some time now.
I have a PL/pgSQL function in a PostgreSQL-8.3 (sorry for that old version, I can't change that) that does the following four things:
Get a new serial (ID) from a sequence
Insert a couple of records into a table with that serial
Send a notify signal that an insertion took place
Return the ID to the caller.
Simplified function:
CREATE OR REPLACE FUNCTION add_entry(_user_name text, _visible_attr integer[])
RETURNS bigint AS
$BODY$
DECLARE
user_name text := '#' || $1;
user_id bigint;
BEGIN
-- get the ID from the sequence
SELECT nextval('my_sequence') INTO user_id;
-- insert the name (and some other data not shown here) 5x
FOR item IN 1..5
LOOP
INSERT INTO mytable
(id,index,username,visible)
VALUES (user_id,item,user_name,$2[item]);
END LOOP;
-- send notify that an insertion took place
notify my_notify;
RETURN user_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
So, my collegue called this function from his application. He gets the returned ID and uses another thread (DB pooling) in his application to call a function which shall return the data previously inserted with that ID. However, this doesn't work the first time. Only with the second request he is able to select the data. It seems as if that INSERT isn't finished while the function already returns?!
We checked mutiple times, the data will be inserted into the table correctly but somehow it is not available as fast as the return value (the ID from the sequence) is available! Why is that so?
Update: wrong assumption
I examined further and reduced the example to a simple query which really shows the problem:
select * from mytable where id = (select add_entry('MyTestUser'));
This query returns no rows. But if I do that in two seperate steps I can select the data which I inserted with the add_entry function.
I have no clue what I'm doing wrong or how I could speed up the insertion...
From the 8.3 manual
In effect, a SELECT query sees a snapshot of the database as of the instant that that query begins to run
Since the update is done in the select itself the inserted row will not be seen.
http://www.postgresql.org/docs/8.3/static/tutorial-transactions.html
Change the function to return setof mytable. It can be plain SQL. To change the return type the function must be dropped first
drop function add_entry(text);
create or replace function add_entry (_user_name text, _visible_attr integer[])
returns setof mytable as $body$
notify my_notify;
with ins as (
insert into mytable (id, index, username, visible)
select user_id, item, '#' || $1, $2[item]
from
generate_series(1, 5) g(item)
cross join
(values (nextval('my_sequence'))) s(user_id)
returning *
)
select * from ins;
$body$ language sql volatile;
The notification must happen before anything is returned from the function. It is not a problem as if the insert fails the transaction rolls back including the notification. Call it like
select * from add_entry('MyTestUser');
The select will not see the modified table but the returned mytable rows.
If it is necessary for the function to be plpgsql then use return query
create or replace function add_entry (_user_name text, _visible_attr integer[])
returns setof mytable as $body$
begin
notify my_notify;
return query
insert into mytable (id, index, username, visible)
select user_id, item, '#' || $1, $2[item]
from
generate_series(1, 5) g(item)
cross join
(values (nextval('my_sequence'))) s(user_id)
returning *
;
end;
$body$ language plpgsql volatile;

Returning result set from Postgres functions

In my Postgres 9.2 database, I need to build a function that takes several parameters, performs several queries, and then returns a data set that is composed of several rows and several columns. I've built several test functions to get a better grasp of Postgres' functionality, here is one:
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer) RETURNS character varying AS
$BODY$
declare vid integer;
declare vendor character varying;
BEGIN
vid := (select v_id from public.gc_alerts where a_id = id);
vendor := (select v_name from public.gc_vendors where v_id = vid);
RETURN vendor;
END;
$BODY$
LANGUAGE plpgsql;
I know that I can combine this into one query, but this is more of a practice exercise. This works fine and I get the vendor name. However, I need to return more than one column from the gc_vendors table.
Ultimately, I need to return columns from several tables based on subqueries. I've looked into creating a result set function, but I believe it only returns one row at a time. I also looked into returning setof type, but that seems to be limited to existing tables.
After initial feedback, I changed the function to the following:
CREATE OR REPLACE FUNCTION sql_with_rows14(IN v_uid character varying, IN lid integer)
RETURNS table (aid int, aname character varying) AS
$BODY$
declare aid integer;
declare aname character varying;
BEGIN
sql_with_rows14.aid := (select a_id from public.gc_alerts where v_id = sql_with_rows14.v_uid);
sql_with_rows14.aname := (select a_name from public.gc_alerts where a_id = sql_with_rows14.aid);
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
I also tried RETURN NEXT, but same results.
When I query it, if the query returns only one row, it works fine. However it doesn't work for multiple rows. I also tried something like this, with the same result:
...
BEGIN
sql_with_rows14.aid := (select a_id from public.gc_alerts);
sql_with_rows14.aname := (select a_name from public.gc_alerts);
RETURN NEXT;
END;
I need to return more than one column from the gc_vendors table
To return a single row with multiple fields (as opposed to a set of rows), you can either use:
RETURNS row_type
.. where row_type is a pre-defined composite type (like a table name, that serves as such automatically). Or:
RETURNS record
combined with OUT parameters. Be aware that OUT parameters are visible in the body almost everywhere and avoid naming conflicts.
Using the second option, your function could look like this:
CREATE OR REPLACE FUNCTION sql_with_columns(IN _id integer -- IN is optional default
, OUT vid integer
, OUT vendor text)
RETURNS record
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO vid v_id
FROM public.gc_alerts
WHERE a_id = id;
SELECT INTO vendor v_name
FROM public.gc_vendors
WHERE v_id = vid;
RETURN; -- just noise, since OUT parameters are returned automatically
END
$func$;
As you mentioned, you should combine both queries into one, or even use a plain SQL statement instead. This is just a show case. The excellent manual has all the details.
You can also use:
RETURNS TABLE (...)
Or:
RETURNS SETOF row_type
This allows to return a set of rows (0, 1 or many). But that's not in your question.
To get individual columns instead of a record representation, call the function with:
SELECT * FROM sql_with_columns(...);
There are lots of examples here on SO, try a search - maybe with additional key words.
Also read the chapter "Returning from a Function" in the manual.
First of all, consider using views or simple queries. I'd say that if you can process something with a simple query, you shouldn't create function for that. in your case, you can use this query
select
v.v_name, v.* -- or any other columns from gc_alerts or gc_vendors
from public.gc_alerts as a
inner join public.gc_vendors as v on v.v_id = a.vid
where a.a_id = <your id here>
if you want your function to return rows, you can declare it like
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer)
RETURNS table(vendor text, v_id int)
as
$$
select
v.v_name, v.v_id
from public.gc_alerts as a
inner join public.gc_vendors as v on v.v_id = a.vid
where a.a_id = id
$$ language SQL;
or plpgsql function:
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer)
RETURNS table(vendor text, vid int)
AS
$$
declare vid integer;
declare vendor character varying;
BEGIN
sql_with_rows11.vid := 1; -- prefix with function name because otherwise it would be declared variables
sql_with_rows11.vendor := 4;
return next;
sql_with_rows11.vid := 5;
sql_with_rows11.vendor := 8;
return next;
END;
$$ LANGUAGE plpgsql;
sql fiddle demo to fiddle with :)