Returning Query from PL/pgSQL - sql

I have a list of records which has employee ids for each employee (emp_id), and their manager's employee id (manager_id)
Lets say I wanted to run a query to get all columns for the entire chain of command for Sandrine (emp_id = 63679) (i.e. Sandrine, Sandrine's boss, Sandrine's boss's boss, etc.)
How would I write this query?
I tried the following loop in PL/pgSQL:
CREATE FUNCTION getpersons() RETURNS SETOF person
LANGUAGE plpgsql AS
$$ Declare
counter INTEGER := 63679 ;
Begin
WHILE (Counter is not null) loop
RETURN QUERY EXECUTE (SELECT * FROM employees WHERE employees.emp_id = Counter);
Counter = employees.manager_id;
END LOOP;
END; $$
SELECT * from person;
the db looks like this:

Your function has several errors.
you are selecting from a table named employees, so the function should return setof employees, not "person"
You don't store the result of the select anywhere, so you can't actually assign the "counter" variable (which is a really bad choice for a variable name)
execute is only needed for dynamic SQL, not if you have static SQL
you should pass the ID of the employee to start with as a parameter, rather than hard coding it.
So in order to actually use the row you retrieved you need to store it in a variable, which can be defined as the same type as the resulting table.
Putting all of that together, your function should look like this:
CREATE FUNCTION getpersons(p_startid integer)
RETURNS SETOF employee
LANGUAGE plpgsql AS
$$ Declare
l_id INTEGER := p_startid;
l_row employee;
Begin
WHILE (l_id is not null) loop
SELECT *
into l_row
FROM employee
WHERE emp_id = l_id;
l_id := l_row.manager_id;
return next l_row;
END LOOP;
END;
$$
;
Then you can use it like this:
select *
from getpersons(63679);
However, it is not needed to write a function to traverse such a hierarchy. This can be done with a single SQL statement using a recursive common table expression
with recursive persons as (
select *
from employees
where id = 63679
union all
select ch.*
from employees ch
join persons p on p.manager_id = ch.id
)
select *
from persons;

Related

Procedure returns query table in Postgresql

I try to create a procedure that return a query table from Postgresql, but I'm stuck at creating the procedure
CREATE OR REPLACE PROCEDURE "sap_data"."get_emp_number"(dep VARCHAR)
RETURNS TEMP TABLE (
emp_id VARCHAR,
department VARCHAR)
AS $BODY$
BEGIN
RETURN QUERY SELECT
emp_id,
department
FROM
emp_data
WHERE
department = dep
END;$BODY$
LANGUAGE plpgsql
When I save this procedure, this error occurs:
ERROR: syntax error at or near "TABLE"
LINE 2: RETURNS TABLE (
I want to return this as a query so I can query this table using parameter to filter it.
There are several errors in the creation.
It is function.
Functions can't return a temporal table.
You should prefix columns with a table name in RETURN QUERY SELECT.
The semicolon before END is needed.
it might look something like this:
CREATE OR REPLACE Function "get_emp_number"(dep VARCHAR)
RETURNS TABLE (
emp_id VARCHAR,
department VARCHAR)
AS $BODY$
BEGIN
RETURN QUERY SELECT
emp_data.emp_id,
emp_data.department
FROM
emp_data
WHERE
emp_data.department = dep;
END;
$BODY$
LANGUAGE plpgsql

Postgres - returning row id's into stored procedure variable causes error

I have a stored procedure which I'm trying to use to delete several rows of a table based on an array of id's, from the rows that are deleted I want to return those id's and store them in a variable so that it can be used in another delete statement. Here's a reduced segment of my function.
create or replace function "table_a"."deletes"
(p_ids int[]
)
returns int
as $$
declare
v_directories int[];
begin
delete from "table_a"."foo" where "hoo_id" = any(unnest(p_ids)) returning id into v_dirs;
delete from "table_a"."bar" where "foo_id" = any(unnest(v_dirs));
return 1;
exception
when others
then raise exception '% %', sqlstate, sqlerrm;
end;
$$ LANGUAGE plpgsql;
This gives me an error of -
'set-returning functions are not allowed in WHERE'
What am I missing?
Use a CTE instead:
with ids as (
delete from "table_a"."foo"
where "hoo_id" = any(unnest(p_ids))
returning id
)
delete from "table_a"."bar"
where "foo_id" in (select id from ids);

PostgreSQL - Creating a loop with a SELECT

I have these Stored Procedure logic to be implemented:
CREATE OR REPLACE FUNCTION get_array(arraynumbers integer[])
RETURNS TABLE (name text) AS $$
DECLARE
index integer := 0
BEGIN
FOREACH index < arraynumbers
LOOP
SELECT e.name as empname FROM employee as e
WHERE e.id = arraynumbers[index]
LIMIT 1
name.push(empname)
ENDLOOP;
RETURN name;
END;
$$
LANGUAGE PLPGSQL;
The goal is to loop based on the length of the array parameter and every index of the parameter will be the condition for retrieving a record and push it to a variable and return the variable as table.
What is the correct way of writing it in PostgreSQL Stored Procedure?
It's unclear to me what exactly the result should be, but as far as I can tell, you don't need a loop or a PL/pgSQL function:
CREATE OR REPLACE FUNCTION get_array(arraynumbers integer[])
RETURNS TABLE (name text)
AS
$$
SELECT e.name
FROM employee as e
WHERE e.id = any(arraynumbers);
$$
LANGUAGE SQL;
This will return one row for each id in arraynumbers that exist in the employee table. As the function is declared as returns table there is no need to collect the values into a single variable (which you didn't declare to begin with)

Create stored procedure (int[] as param) which deletes existing records in table when there is no match in int array

ex: if i have sent 1,2,3 params to stored procedure with idxyz, then table has 1,2,3,4,5 ids then 4,5 should be deleted from table.
CREATE OR REPLACE FUNCTION example_array_input(INT[]) RETURNS SETOF ids AS
$BODY$
DECLARE
in_clause ALIAS FOR $1;
clause TEXT;
rec RECORD;
BEGIN
FOR rec IN SELECT id FROM ids WHERE id = ANY(in_clause)
LOOP
RETURN NEXT rec;
END LOOP;
-- final return
RETURN;
END
$BODY$ language plpgsql;
ex: SELECT * FROM example_array_input('{1,2,4,5,6}'::INT[]);
if existing table has 1,2,3,4,5,6,7,8,9. then it should delete 7,8,9 from that table since these are not there in the input array
You can use a DELETE statement like this for your purpose.
DELETE FROM ids
where id NOT IN ( select UNNEST('{1,2,4,5,6}'::INT[]) ) ;
DEMO
You can use a sql function that returns the deleted ids:
CREATE OR REPLACE FUNCTION example_array_input(in_clause INT[]) RETURNS SETOF ids
language sql
AS
$SQL$
DELETE
FROM ids
WHERE id NOT IN ( SELECT unnest(in_clause) )
RETURNING id;
$SQL$;
You can see a running example in http://rextester.com/PFG55537
In a 1 to 10 table running
SELECT * FROM example_array_input('{1,2,4,5,6}'::INT[]);
you obtain:

Understanding PostgreSQL select operator

I'm writing the following stored procedure:
CREATE OR REPLACE FUNCTION getid() RETURNS table(id integer) AS $$
DECLARE
rec RECORD;
BEGIN
select id into rec from player where id = 3;
END $$
LANGUAGE plpgsql;
select * from getid();
And when I'm trying to execute that script I got the error:
column reference "id" is ambiguous
Why? I thought that id column of the returned table is not participate in the select operator...
The issue is that it worked on PostgreSQL 8.4 but doesn't work on PosgtreSQL 9.4. Couldn't you explain what actually has been added in the PostgreSQL 9.4 so it doesn't work?
Postgres is confused when you use the same names for arguments and columns... Consider using a convention with some prefix for all input parameters - for example p_id
I would write:
CREATE OR REPLACE FUNCTION getid() RETURNS table(p_id integer) AS $$
DECLARE
rec RECORD;
BEGIN
SELECT INTO rec id FROM player WHERE id = 3;
END $$
LANGUAGE plpgsql;
To solve that specific problem you would do this in your select:
SELECT player.id INTO rec FROM player WHERE player.id = 3;