Call an Array result from select function to do update function with postgresql - sql

I'm new with SQL functions and postgreSQL. I just try to select some mails of my compte table to update them afterwards so I create select and update functions but I have always the error:
"ERROR: query "SELECT emailAnonymisation()" returned more than one row
CONTEXT: PL/pgSQL function updateMail() line 5 during statement block local variable initialization"
The problem is in my update function but I don't know if it's only a variable problem or a function logical problem...
My select function
CREATE OR REPLACE FUNCTION emailAnonymisation()
RETURNS table (mail varchar)
LANGUAGE plpgsql
AS $$
BEGIN
return query
SELECT compte.mail
FROM compte
limit 100;
END
$$;
My update function where I call the emailAnonymisation() function and where the problem is I think
CREATE OR REPLACE FUNCTION updateMail()
RETURNS varchar[] AS
$BODY$
DECLARE
_tbl varchar[]:=emailAnonymisation();
t text;
BEGIN
FOREACH t IN ARRAY _tbl
LOOP
EXECUTE '
UPDATE ' || t || '
SET t = REPLACE(SUBSTR(t,LOCATE('#',t) + 1),"X")
WHERE LOCATE('#',t) > 0;';
END LOOP;
END;
$BODY$ LANGUAGE plpgsql;
the update call
select updateMail();

Try using SETOF:
CREATE OR REPLACE FUNCTION emailAnonymisation()
RETURNS SETOF string(mail varchar)
LANGUAGE plpgsql
AS $$
BEGIN
return query
SELECT compte.mail
FROM compte
limit 100;
END
$$;

Ok I have finally found what was the problem with the select I should have a return like this with RETURNS character varying[]
CREATE OR REPLACE FUNCTION emailAnonymisation()
RETURNS character varying[]
AS $$
DECLARE
result character varying[];
BEGIN
SELECT ARRAY( SELECT compte.mail as mail
FROM compte
WHERE mail IS NOT NULL
limit 100)
into result;
return result;
END;
$$
LANGUAGE plpgsql;
And for the update the same type as the select function
CREATE OR REPLACE FUNCTION updateMail()
RETURNS character varying(150) AS
$BODY$
DECLARE
_tbl character varying[]:=emailAnonymisation();
mail text;
endUrl text := '#mail.com';
BeginningUrl text := random_string(15);
BEGIN
FOREACH mail IN ARRAY _tbl
LOOP
UPDATE ' || t || '
SET t = REPLACE(SUBSTR(t,LOCATE('#',t) + 1),"X")
WHERE LOCATE('#',t) > 0;';
END LOOP;

Related

Postgres function to return number of rows deleted per schema

I'm trying to adapt a Postgres stored procedure into a function in order to provide some feedback to the caller.
The procedure conditionally deletes rows in specific schemas, and I'd want the function to do the same, but also return the amount of rows that were deleted for each schema.
The original stored procedure is:
create or replace procedure clear_tenants()
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
My current transform into a function is:
CREATE OR REPLACE FUNCTION testfun()
RETURNS TABLE(tsname varchar, amount numeric) AS
$BODY$
declare
tenant text;
trow record;
BEGIN
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
WITH deleted AS (
delete from %I.parent
where expiration_date_time < now()
IS TRUE RETURNING *
)
tsname := tenant;
amount := (SELECT * FROM deleted;);
return next;
$ex$, tenant);
end loop;
END
$BODY$ language plpgsql;
This is probably wrong in all kinds of ways. I'm definitely confused.
When executing this with SELECT * FROM testfun(), I get the following error:
ERROR: syntax error at or near "tsname"
LINE 7: tsname := tenant;
^
QUERY:
WITH deleted AS (
delete from anhbawys.parent
where expiration_date_time < now()
IS TRUE RETURNING *
)
tsname := tenant;
amount := (SELECT * FROM deleted;);
return next;
CONTEXT: PL/pgSQL function testfun() line 9 at EXECUTE
SQL state: 42601
So clearly I'm not properly assigning the row's columns, but I'm not sure how.
I have found this question which seemed similar, but it's bit complex for my understanding.
You can use GET DIAGNOSTICS after a DELETE statement to get the number of rows deleted:
CREATE OR REPLACE FUNCTION testfun()
RETURNS TABLE(tsname varchar, amount bigint) AS
$BODY$
declare
tenant text;
BEGIN
for tenant in
select tenant_schema
from tenant_schema_mappings
loop
execute format(
'delete from %I.parent
where expiration_date_time < now()', tenant);
tsname := tenant;
GET DIAGNOSTICS amount := ROW_COUNT;
return next;
end loop;
END
$BODY$
language plpgsql;
If the answer is as simple as your question there is a system catalog for this.
select schemaname , count(n_tup_del)
from pg_catalog.pg_stat_all_tables psat
group by schemaname
You can use get diagnostics (noddy function to get my point across)
create or replace function delfunc()
returns void
as $$
declare
affected_rows int ;
begin
delete from atable where a > 998 ;
GET DIAGNOSTICS affected_rows = ROW_COUNT;
insert into logtable values(affected_rows);
end $$
language plpgsql

PostgreSQL add each item to list - DB functions

I'd like to create a DB function that will accept a list of numbers and return a list of numbers. For each item in the list that was passed to the function, it should check some condition and add it to the response list. However, I don't think the way I am trying to do it is really a correct one. What I tried writing is basically some pseudo code here.
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
DECLARE return_list integer[];
FOREACH field IN ARRAY numbers LOOP
CASE
WHEN field = 3 THEN -- add 43 (this was a random thought, but I am basically trying to map a few of the numbers to different values)
END
END LOOP;
RETURN QUERY SELECT * FROM return_list;
$BODY$
LANGUAGE sql VOLATILE
COST 100;
You need to put this into an IF statement. To append a value to an array use ||
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
DECLARE
return_list integer[] := integer[];
BEGIN
FOREACH field IN ARRAY numbers LOOP
if field = 3 then
return_list := return_list || 43;
elsif field = 15 then
return_list := return_list || 42;
else
return_list := return_list || field;
end if;
END LOOP;
return return_list; --<< no SELECT required, just return the variable
END;
$BODY$
LANGUAGE plpgsql --<< you need PL/pgSQL, not SQL for the above
STABLE;
This can also be done using SQL rather than PL/pgSQL which usually is more efficient:
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
select array_agg(field order by idx)
from (
select case
when field = 3 then 43
when field = 15 then 42
else field
end as field,
idx
from unnest(numbers) with ordinality as t(field, idx)
) x;
$BODY$
LANGUAGE sql
STABLE;

Evaluate dynamic condition in PL/pgSQL using EXECUTE

I have a function that needs to dynamically validate the input based on the value type. It does this by finding the constraint in another table, but for simplicity I provide the constraint as well in the function below.
This other table contains (value_type, value_constraint), where value_constraint is a text field that contains e.g. value::int > 0. I need to dynamically check this constraint within my insert function. I was trying to do that using EXECUTE as seen below, but it's not working.
How do I dynamically execute a condition statement and get the value as a boolean into v_successful_insert?
CREATE OR REPLACE FUNCTION insert_value(p_value_type text, p_value_constraint text, p_value text) RETURNS boolean
AS $$
DECLARE
v_successful_insert bool;
BEGIN
EXECUTE p_value_constraint INTO v_successful_insert;
IF v_successful_insert THEN
INSERT INTO my_table (value_type, value)
VALUES (p_value_type, p_value);
END IF;
RETURN v_successful_insert;
END;
$$
LANGUAGE plpgsql volatile;
The code is run on Postgresql 10.6.
You need to do a SELECT in the execute. For example:
DO $$
DECLARE
p_value TEXT := 'x';
p_value_constraint TEXT := '::int > 0';
result BOOLEAN;
BEGIN
BEGIN
EXECUTE 'SELECT $1' || p_value_constraint
INTO result
USING p_value;
EXCEPTION
WHEN INVALID_TEXT_REPRESENTATION THEN
result := FALSE;
END;
RAISE NOTICE '%', result;
END $$
Prints FALSE

Relation does not exist PLPGSQL

I am trying to create a view inside the function using plpgsql which returns the x column of the "small" table which is defined as (x integer,y integer).
create or replace function skyline_naive2(dataset text) returns setof integer as
$$
declare
fullx text;
begin
fullx = dataset||'_skyline_naive2';
execute format('create view %s as select x,y from %s',fullx,dataset);
return query select x from fullx;
end
$$ language plpgsql;
select * from skyline_naive2('small');
It returns "relation fullx does not exist"
I understand that it is because there is no fullx relation, but I want to call the view using the variable name.
Any help will be
Use dynamic SQL for select (as you have used for create):
create or replace function skyline_naive2(dataset text) returns setof integer as
$$
declare
fullx text;
begin
fullx = dataset||'_skyline_naive2';
execute format('create view %I as select x,y from %I',fullx,dataset);
return query execute format('select x from %I', fullx);
end
$$ language plpgsql;
You need to EXECUTE your dynamic query:
RETURN QUERY EXECUTE 'SELECT x FROM ' || fullx;

PostgreSQL execute + date

I am trying to create an SQL select statement in PL/pgSQL procedure. It was all fine until I had to add a date to the select. The problem is that to compare dates in select clause my date must be enclosed in single quotes '' (e.g. '01.01.2011'), but in my case it is already a text type and I can't add it there.
Below is a sample code that should have same problem:
CREATE OR REPLACE FUNCTION sample(i_date timestamp without time zone)
RETURNS integer AS
$BODY$
DECLARE
_count integer := 0;
_sql text := '';
BEGIN
IF i_date IS NOT NULL THEN
_cond := _cond || ' AND t.created>' || i_date;
END IF;
_sql := 'SELECT count(*) FROM test t WHERE 1=1' || _cond;
EXECUTE _sql INTO _count;
RETURN _count;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Is there any other way to "escape" date? Or some other suggestions?
To use insert values safely and efficiently into a dynamically build and executed SQL string, there are a number of possibilities. The best one is to use the USING clause like this:
CREATE OR REPLACE FUNCTION sample(_date timestamp without time zone)
RETURNS integer AS
$BODY$
BEGIN
RETURN QUERY EXECUTE '
SELECT count(*)::int
FROM test t
WHERE t.created > $1'
USING _date;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;
This is actually a bad example, because it could be simplified to:
CREATE OR REPLACE FUNCTION sample(_date timestamp)
RETURNS integer AS
$BODY$
SELECT count(*)::int
FROM test
WHERE created > $1;
$BODY$ LANGUAGE sql;
Another way would be to use quote_literal().
I wrote more about dynamic SQL in plpgsql here.