PostgreSQL - Creating a loop with a SELECT - sql

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)

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

Returning Query from PL/pgSQL

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;

Generalize Get/Create Stored Procedure From One Item to Many

I have an express.js server running an application and from that server I can access or create "variant_id"s in PostgreSQL (Version 11) by using a stored procedure.
SELECT(get_or_create_variant_id(info_about_variant));
Sometimes I also need to get a bunch of these variant ids back by using a different stored procedure that takes multiple variants and returns multiple ids.
SELECT(get_or_create_variant_ids([info_about_variant, info_about_another_variant]));
What is the best way to generalize getting/creating a single id to doing multiple at once? I'm handling it in a LOOP in my stored procedure, but it feels like I should be able to use a JOIN instead.
CREATE OR REPLACE FUNCTION get_or_create_variant_id(
variant_in VARIANT_TYPE
) RETURNS INT AS $$
DECLARE variant_id_out INTEGER;
BEGIN
-- I'll be changing this to a ON CONFLICT block shortly
SELECT(get_variant_id(variant_in) INTO variant_id_out);
IF (variant_id_out IS NOT NULL) THEN
RETURN variant_id_out;
ELSE
INSERT INTO public.variant (
[some_fields]
)
VALUES (
[some_values]
)
RETURNING variant_id INTO variant_id_out;
RETURN variant_id_out;
END IF;
END;
$$ LANGUAGE plpgsql;
-- What is the best way to avoid a loop here?
CREATE OR REPLACE FUNCTION get_or_create_variant_ids(
variants_in VARIANT_TYPE []
) RETURNS INT [] AS $$
DECLARE variant_ids_out INTEGER [];
DECLARE variants_in_length INTEGER;
DECLARE current_variant_id INTEGER;
BEGIN
SELECT (array_length(variants_in, 1) INTO variants_in_length);
FOR i IN 1..variants_in_length LOOP
SELECT(get_or_create_variant_id(variants_in[i]) INTO current_variant_id);
SELECT(array_append(variant_ids_out, current_variant_id) INTO variant_ids_out);
END LOOP;
RETURN variant_ids_out;
END;
$$ LANGUAGE plpgsql;
-- Everything below is included for completeness, but probably less relevant to my question.
CREATE TYPE variant_type AS (
[lots of info about the variant]
);
CREATE OR REPLACE FUNCTION get_variant_id(
variant_in VARIANT_TYPE
) RETURNS INT AS $$
DECLARE variant_id_out INTEGER;
BEGIN
SELECT variant_id into variant_id_out
FROM public.variant
WHERE
[I want them to]
;
RETURN variant_id_out;
END;
$$ LANGUAGE plpgsql;
You can avoid explicit loop using builtin array functions - in this case, unnest function, and array constructor.
CREATE OR REPLACE FUNCTION get_or_create_variant_ids_v2(
variants_in VARIANT_TYPE []
)
RETURNS integer []
LANGUAGE sql AS $$
SELECT ARRAY(
SELECT get_or_create_variant_id(u.v)
FROM unnest(variants_in) AS u(v)
)
$$ LANGUAGE sql;

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:

Return a query from a function?

I am using PostgreSQL 8.4 and I want to create a function that returns a query with many rows.
The following function does not work:
create function get_names(varchar) returns setof record AS $$
declare
tname alias for $1;
res setof record;
begin
select * into res from mytable where name = tname;
return res;
end;
$$ LANGUAGE plpgsql;
The type record only allows single row.
How to return an entire query? I want to use functions as query templates.
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
BEGIN
RETURN QUERY
SELECT t.col_a, t.col_b -- must match RETURNS TABLE
FROM mytable t
WHERE t.name = _tname;
END
$func$ LANGUAGE plpgsql;
Call like this:
SELECT * FROM get_names('name')
Major points:
Use RETURNS TABLE, so you don't have to provide a list of column names with every call.
Use RETURN QUERY, much simpler.
Table-qualify column names to avoid naming conflicts with identically named OUT parameters (including columns declared with RETURNS TABLE).
Use a named variable instead of ALIAS. Simpler, doing the same, and it's the preferred way.
A simple function like this could also be written in LANGUAGE sql:
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
SELECT t.col_a, t.col_b --, more columns - must match RETURNS above
FROM mytable t
WHERE t.name = $1;
$func$ LANGUAGE sql;