PL/PgSQL Dynamic Subqueries - sql

I am creating a trigger that runs a check raises an exception if it passes. To do this I need to use a dynamic call because I only have the table name as a string. I am using PostgreSQL but I can't figure out how there execute command works. When I do this:
CREATE OR REPLACE FUNCTION bleep() RETURNS table(id INT) AS $bleep$
BEGIN
RETURN QUERY EXECUTE 'SELECT (id) from Applicant';
END;
$bleep$ LANGUAGE plpgsql;
SELECT * from bleep();
It works perfectly and I get back a table of id's from Applicant. But when I do this:
CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
BEGIN
IF (EXISTS (EXECUTE 'SELECT (id) from Applicant')) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$bleep$ LANGUAGE plpgsql;
It tells me:
ERROR: syntax error at or near "EXECUTE" Position: 87
This is just a toy example I made to figure out how this works and I have read lots of docs and guides. If I can figure out this toy example I can make the full trigger work since I tried it with hardcoding a table name. How can I make this work?

CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
DECLARE
res bool;
BEGIN
EXECUTE 'SELECT exists (select 1 from Applicant)' INTO res;
return res;
END;
$bleep$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
BEGIN
return query EXECUTE 'SELECT exists (select 1 from Applicant)';
END;
$bleep$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION bleep() RETURNS
BOOLEAN AS $bleep$
BEGIN
IF (EXISTS (SELECT id from Applicant)) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$bleep$ LANGUAGE plpgsql;

I misunderstood your question.
How about this?
CREATE FUNCTION bleep(integer) RETURNS boolean
AS 'select case when count(*) = 0 then false else true end from Applicant where id = $1;'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
You can test by SQLFiddle: http://sqlfiddle.com/#!15/33954/1

Related

Update columns for the first user inserted

I'm trying to create a trigger and a function to update some columns (roles and is_verified) for the first user created. Here is my function :
CREATE OR REPLACE FUNCTION public.first_user()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
begin
if(select count(*) from public.user = 1) then
update new set new.is_verified = true and new.roles = ["ROLE_USER", "ROLE_ADMIN"]
end if;
return new;
end;
$function$
;
and my trigger :
create trigger first_user
before insert
on
public.user for each row execute function first_user()
I'm working on Dbeaver and Dbeaver won't persist my function because of a syntax error near the "=". Any idea ?
Quite a few things are wrong in your trigger function. Here it is revised w/o changing your business logic.
However this will affect the second user, not the first. Probably you shall compare the count to 0. Then the condition shall be if not exists (select from public.user) then
CREATE OR REPLACE FUNCTION public.first_user()
RETURNS trigger LANGUAGE plpgsql AS
$function$
begin
if ((select count(*) from public.user) = 1) then
-- probably if not exists (select from public.user) then
new.is_verified := true;
new.roles := array['ROLE_USER', 'ROLE_ADMIN'];
end if;
return new;
end;
$function$;

How do I return a table from a function with a bespoke column name?

This function works:
CREATE OR REPLACE FUNCTION public.a()
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a from ztable';
END;
$function$;
But when I try to add some text to the column name:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as $1_a from ztable' using prefix;
END;
$function$;
This just fails as a syntax error on $1.
Or:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as '||prefix||'_a from ztable';
END;
$function$;
select * from a('some prefix') doesn't work.
Is there some other syntax that does the job?
That's simply not possible. SQL does not allow dynamic column names.
You must assign a column alias with the call. Like:
SELECT a AS prefix_a FROM public.a();
Or in a column definition list directly attached to the function:
SELECT * FROM public.a() AS f(prefix_a);
Or, while dealing with a single output column, even just:
SELECT * FROM public.a() AS prefix_a;
See:
RETURNING rows using unnest()?

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

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;

query has no destination for result data - PostgreSQL

I have created the following stored procedure in PostgreSQL:
CREATE OR REPLACE function incomingdel(IN del_ID varchar) RETURNS VOID AS $$
DECLARE xyz integer;
BEGIN
SELECT * INTO xyz FROM incomingcheck(cast(del_ID as integer));
IF xyz <> 1
THEN
SELECT * INTO xyz FROM perfdel(cast(del_ID as integer));
END IF;
END
$$ LANGUAGE plpgsql
even though the function returns VOID, I am still getting
Error: query has no destination for result data
when i execute the function. Can someone please help?
Thanks
When you do SELECT * INTO ... in a PL/pgSQL function you need to supply a variable of type record or of the row type of the row source, even if the function only returns an integer. The functions incomingcheck(int) and perfdel(int) are obviously returning a single integer in which case you can simplify your function:
CREATE FUNCTION incomingdel(del_ID integer) RETURNS VOID AS $$
DECLARE
xyz integer;
BEGIN
xyz := incomingcheck(del_ID);
IF xyz <> 1 THEN
xyz := perfdel(del_ID);
END IF;
-- Do something with xyz
END; $$ LANGUAGE plpgsql;
If the functionality is actually in the called functions (so you do not process the xyz value), then it becomes simpler still:
CREATE FUNCTION incomingdel(del_ID integer) RETURNS VOID AS $$
BEGIN
IF incomingcheck(del_ID) <> 1 THEN
perfdel(del_ID);
END IF;
END; $$ LANGUAGE plpgsql;

Write a PL/pgSQL function so that FOUND is not set when "nothing" is found?

I am just starting out on functions in PostgreSQL, and this is probably pretty basic, but how is this done?
I would like to be able to use the following in a function:
PERFORM id_exists();
IF FOUND THEN
-- Do something
END IF;
where the id_exists() function (to be used with SELECT and PERFORM) is:
CREATE OR REPLACE FUNCTION id_exists() RETURNS int AS $$
DECLARE
my_id int;
BEGIN
SELECT id INTO my_id
FROM tablename LIMIT 1;
RETURN my_id;
END;
$$ LANGUAGE plpgsql;
Currently, even when my_id does not exist in the table, FOUND is true, presumably because a row is still being returned (a null integer)? How can this be re-written so that an integer is returned if found, otherwise nothing at all is?
Your assumption is correct, FOUND is set to TRUE if the last statement returned a row, regardless of the value (may be NULL in your case). Details in the manual here.
Rewrite to, for instance:
IF id_exists() IS NOT NULL THEN
-- Do something
END IF;
Or rewrite the return value of your function with SETOF so it can return multiple rows - or no row! Use RETURN QUERY like I demonstrate. You can use this function in your original setting.
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE plpgsql AS
$BODY$
BEGIN
RETURN QUERY
SELECT id
FROM tablename
LIMIT 1;
END;
$BODY$;
Or, even simpler with a language SQL function:
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE sql AS
$BODY$
SELECT id
FROM tablename
LIMIT 1;
$BODY$;