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

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);

Related

update in a function sql in dbeaver/postgres

I have to update a field in a table with concat() and I'm thinking to use a function with an update sql. I also want to have a rollback if update doesnt' work.
I have this function but it just works the select sql and for the first row of the table "clients"
CREATE OR REPLACE FUNCTION value_concat()
RETURNS record
LANGUAGE plpgsql
AS $function$
DECLARE
rows_affected integer := 0;
query constant text not null := 'select * from db.clients';
result record;
BEGIN
EXECUTE query INTO result;
RETURN result;
UPDATE db.clients SET clients.name = concat(clients.name, '-US');
exception when raise_exception
then
begin
rows_affected := 0;
rollback;
end;
RETURN record;
END;
$function$
;
Do I have to make a select sql before the update?
Why the update is not working, should I do a for/loop before the update sql?
The below code returns just one record and not all records form the select sql, why?
EXECUTE query INTO result;
RETURN result;
I hope this help.
CREATE OR REPLACE FUNCTION value_concat()
RETURNS TABLE (
field_name1 VARCHAR,
field_name2 INT [, ...]
)
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE db.clients SET clients.name = concat(clients.name, '-US');
RETURN QUERY
select * from db.clients;
exception when raise_exception
then
begin
rows_affected := 0;
rollback;
end;
RETURN record;
END;
$function$
;
Do I have to make a select SQL before the update?
There's no need to select, it works well. You should check the query to apply where for unwanted updates for other records.
Why the update is not working, should I do a for/loop before the
update SQL?
Because you're returning before the update and the rest of the code is always skipped.
The below code returns just one record and not all records form the
select sql, why?
It's because you just return a record type. the record type can only store one row from a result.

Break update operation when function returns NULL value in PostgreSQL

Let' assume I have a table named mytable:
I have one function which returns text and sometime it can return NULL also. ( this is just demo function in real use case function is complex )
CREATE OR REPLACE FUNCTION parag_test (id text)
RETURNS text
LANGUAGE plpgsql
AS
$$
DECLARE
--- variables
BEGIN
IF(id= 'Ram') THEN
RETURN 'shyam';
ELSE
RETURN NULL;
END IF;
END
$$
I want to update mytable till the time when my function returns non NULL values. if it returns NULL value I want to break update operation that time.
if we use below update query it will not stops updating when function returns NULL
update mytable SET id = parag_test (id) ;
Table after triggering above query looks like :
But what my expectation of output is :
because when we try to update second row function parag_test will return NULL and I want to stop update operation there.
So is there any way in PostgreSQL for achieving that ?
If you do have a primary key (say row number) to your table, this approach could work. - https://dbfiddle.uk/?rdbms=postgres_14&fiddle=0350562961be16333f54ebbe0eb5d5cb
CREATE OR REPLACE FUNCTION parag_test()
RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
a int;
i varchar;
BEGIN
FOR a, i IN SELECT row_num, id FROM yourtable order by row_num asc
LOOP
IF(i = 'Ram') THEN
UPDATE yourtable set id = 'shyam' where row_num = a;
END IF;
IF (i is null) then
EXIT;
END IF;
END LOOP;
END;
$$

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:

SQL: send query to all database available

How is it possible to send a query to all databases on a server? I do not want to input all databases names, the script should auto-detect them.
example query:
SELECT SUM(tourney_results.amt_won)-SUM((tourney_summary.amt_buyin+tourney_summary.amt_fee)) as results
FROM tourney_results
INNER JOIN tourney_summary
ON tourney_results.id_tourney=tourney_summary.id_tourney
Where id_player=(SELECT id_player FROM player WHERE player_name='Apple');
So what I want to achieve here, if there is 2 databases, the first one would result 60, the second one would result 50, I need the 55 output here.
All databeses would have the same structure, tables etc.
You can do it using plpgsql and db_link. First install the db_link extension in the database you are connecting to:
CREATE EXTENSION dblink;
Then use a plpgsql function which iterates over all database on the server and executes the query. See this example (see comments inline). Note that I used a sample query in the function. You have to adapt the function with your real query:
CREATE or REPLACE FUNCTION test_dblink() RETURNS BIGINT AS
$$
DECLARE pg_database_row record;
query_result BIGINT;
_dbname TEXT;
_conn_name TEXT;
return_value BIGINT;
BEGIN
--initialize the final value
return_value = 0;
--first iterate over the records in the meta table pg_database
FOR pg_database_row in SELECT * FROM pg_database WHERE (NOT datistemplate) AND (datallowconn) LOOP
_dbname = pg_database_row.datname;
--build a connection name for db_link
_conn_name=_dbname||'myconn';
--close the connection is already active:
IF array_contains(dblink_get_connections(),_conn_name) THEN
PERFORM dblink_disconnect(_conn_name);
END IF;
-- open the connection with the actual database name
PERFORM dblink_connect(_dbname||'myconn', 'dbname='||_dbname);
-- check if the table does exist in the database:
PERFORM * FROM dblink(_conn_name,'SELECT 1 from pg_tables where tablename = ''your_table''') AS t(id int) ;
IF FOUND THEN
-- if the table exist, perform the query and save the result in a variable
SELECT * FROM dblink(_conn_name,'SELECT sum(id) FROM your_table limit 1') AS t(total int) INTO query_result;
IF query_result IS NOT NULL THEN
return_value = return_value + query_result;
END IF;
END IF;
PERFORM dblink_disconnect(_conn_name);
END LOOP;
RETURN return_value;
END;
$$
LANGUAGE 'plpgsql';
Execute the function with
select test_dblink();