Using PERFORM in recursive query function in PostgreSQL - postgresql

I have a recursive query which I want to use in PostgreSQL function, and it should return a Boolean value.
CREATE OR REPLACE FUNCTION store.is_item(object1 VARCHAR(40), object2 VARCHAR(40))
RETURNS BOOLEAN AS $$
BEGIN
WITH RECURSIVE externals AS (
SELECT object_id, used_id
FROM store.obj_depend
WHERE external = true
), history AS (
SELECT content_id AS id
FROM store.minfos
WHERE id= $2
UNION
SELECT externals.used_id
FROM externals
INNER JOIN history ON history.id = externals.object_id
),
PERFORM (SELECT c.id FROM store.cinfo AS c WHERE c.id = $1 INNER JOIN history
ON c.id = history.id);
RETURN FOUND;
END;
$$ LANGUAGE plpgsql;
When I try this it gives asyntax error at or near SELECT error.

The PERFORM is plpgsql statement and cannot be used inside any SQL command.
You can use PERFORM like proposed #klin, but then CTE is used inside a subquery, and subquery returns one row every time. Then a FOUND variable should be every time true.
In this case is better to use auxiliary variable as targer:
CREATE OR REPLACE FUNCTION store.is_item(object1 VARCHAR(40), object2 VARCHAR(40))
RETURNS BOOLEAN AS $$
DECLARE r record;
BEGIN
WITH RECURSIVE
externals AS (SELECT object_id, used_id
FROM store.obj_depend
WHERE external = true)
history AS (SELECT content_id AS id
FROM store.minfos
WHERE id = object2
UNION
SELECT externals.used_id
FROM externals
INNER JOIN history ON history.id = externals.object_id)
SELECT c.id
FROM store.cinfo AS c
INNER JOIN history ON c.id = history.id
WHERE c.id = $1
INTO r;
RETURN FOUND;
END;
$$ LANGUAGE plpgsql;

Related

Permissions check postgresql function

I have a function written in plpgsql that checks if user have permissions:
CREATE OR REPLACE FUNCTION check_permission(acc_id integer, permissions text[]) RETURNS boolean AS $$
BEGIN
SELECT DISTINCT p.name FROM person_role pr
JOIN role_permission rp ON pr.role_id = rp.role_id
JOIN permission p ON rp.permission_id = p.permission_id
WHERE account_id = acc_id ;
-- check
END;
$$ LANGUAGE plpgsql;
How can i check that select includes all permissions from permissions text[] argument?
You can unnest the array into a result set and from it subtract the role names from your result:
CREATE OR REPLACE FUNCTION check_permission(acc_id integer, permissions text[]) RETURNS boolean AS $$
SELECT NOT EXISTS (
SELECT unnest(permissions)
EXCEPT
SELECT DISTINCT p.name
FROM person_role pr
JOIN role_permission rp ON pr.role_id = rp.role_id
JOIN permission p ON rp.permission_id = p.permission_id
WHERE pr.account_id = acc_id
);
$$ LANGUAGE sql;
It might however better express the intent to use a boolean aggregate over the permission table and a subquery:
SELECT bool_and(EXISTS(
SELECT *
FROM person_role pr
JOIN role_permission rp ON pr.role_id = rp.role_id
WHERE rp.permission_id = p.permission_id
AND pr.account_id = acc_id
))
FROM permission p
WHERE p.name = ANY permissions -- or JOIN (SELECT unnest(permissions) AS name) USING (name)
Notice this will return NULL instead of TRUE when the permissions array is empty.

How to return a table from iteration in function with postgres 11

Following functions are created for doing housekeeping within the database (PostgreSQL 11.4).
entity_with_multiple_taskexec: returns a list of entities for which the housekeeping should be done.
row_id_to_delete: returns tuples of id's to delete
Just for completeness, the function which works fine:
CREATE OR REPLACE FUNCTION entity_with_multiple_taskexec()
RETURNS TABLE(entitykey varchar) AS
$func$
BEGIN
RETURN QUERY select distinct task.entitykey from
(select task.entitykey from task where dtype = 'PropagationTask' group by task.entitykey having count(*) > (select count(*) from conninstance)) more_than_one_entry
inner join task on task.entitykey = more_than_one_entry.entitykey
inner join taskexec on taskexec.task_id = task.id order by task.entitykey asc;
END
$func$ LANGUAGE plpgsql;
But which the second function, I'm not able to return a table, created from looping through the results of the entity_with_multiple_taskexec function;
CREATE OR REPLACE FUNCTION row_id_to_delete()
RETURNS TABLE(task_id varchar, taskexec_id varchar) AS
$func$
DECLARE
entityrow RECORD;
resultset RECORD;
BEGIN
FOR entityrow IN SELECT entitykey FROM entity_with_multiple_taskexec() LOOP
insert into resultset select task.id as task_id, taskexec.id as taskexec_id from task
inner join taskexec on taskexec.task_id = task.id where taskexec.entitykey = entityrow.entitykey order by taskexec.enddate desc offset 1
END LOOP;
RETURN resultset;
END
$func$ LANGUAGE plpgsql;
This breaks with the following error
ERROR: syntax error at or near "END"
LINE 12: END LOOP;
I've tried different approaches. What would be a good solution to return the table?
You don't need a loop, just join to the function as if it is a table.
There is also no need to use PL/pgSQL for this, a simple language sql function will be more efficient.
CREATE OR REPLACE FUNCTION row_id_to_delete()
RETURNS TABLE(task_id varchar, taskexec_id varchar) AS
$func$
select task.id as task_id, taskexec.id as taskexec_id
from task
join taskexec on taskexec.task_id = task.id
join entity_with_multiple_taskexec() as mt on mt.entitykey = taskexec.entitykey
order by taskexec.enddate desc
offset 1
$func$
LANGUAGE sql;

PostgreSQL - Error column reference "id" is ambiguous

I've aliased everything, but can't seem to get this query working without the
error column reference \"id\" is ambiguous"
It seems to work if i remove one of the joins, but i'm just confused as to why it won't work with two?
create function influence.person_info(user_id integer)
returns setof influence.person_object as
$$
declare
obj influence.person_object;
begin
select t1.email as a_email, t2.organisation_url as a_org, t3.first_name as a_first, t3.last_name as a_last into obj
from influence_private.person_account as t1
inner join influence_private.organisation_account as t2 on (t1.organisation_id = t2.id)
inner join influence.person as t3 on (t1.person_id = t3.id)
where id = $1;
return next obj;
end;
$$ LANGUAGE plpgsql stable;
Any pointers?
In your where clause you will need to explicitly state which id you are referring to.
If you check your query:
select t1.email as a_email, ...
from influence_private.person_account as t1
join influence_private.organisation_account as t2
on (t1.organisation_id = t2.id) -- here
join influence.person as t3
on (t1.person_id = t3.id) -- here
Both your tables (influence_private and influence) have column named id. Postgresql doesn't know which one you want to use so you have to use full name like t2.id.
The problem of your query is t2 and t3 both tables have a column called id. In the Where clause id is ambigous because it doesn't know what id you are refering (t2 or t3) specify it and it will work properly.
Example fix:
create function influence.person_info(user_id integer)
returns setof influence.person_object as
$$
declare
obj influence.person_object;
begin
select t1.email as a_email, t2.organisation_url as a_org, t3.first_name as a_first, t3.last_name as a_last into obj
from influence_private.person_account as t1
inner join influence_private.organisation_account as t2 on (t1.organisation_id = t2.id)
inner join influence.person as t3 on (t1.person_id = t3.id)
where t1.id = $1;
return next obj;
end;
$$ LANGUAGE plpgsql stable;

POSTGRESQL- Query has no destination for result data

I'm new to postgres and to programming and I already searched for solution for this but I couldn't quite get it. I'm trying to make a function that will return information about all the customers on that particular country whenever I call the country. This is the error that pops up. I'm really sorry for asking this but I've been stuck here since yesterday.
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function country(text) line 5 at SQL statement
Here is the function:
create or replace function country(text) returns text as $$
begin
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = '$1';
end;
$$
language plpgsql;
If you are executing a select statement in a PL/pgSQL function, then you should place the result of the query in some variable(s) (= the destination). Then you work with the variable(s) in the function. You should also have a RETURN statement.
create or replace function country(text) returns text as $$
declare -- declare some variables
id integer;
fname text;
lname text;
begin
select customer_id, customer.first_name, customer.last_name
into id, fname, lname -- store query results in variables
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = $1; -- don't quote parameter references
-- do something with the variables and return a value from the function
return format('%s: %s %s', id, upper(lname), fname);
end;
$$ language plpgsql;
Do note that the above only works if the query returns a single row. If the query returns multiple rows you can use a loop in the function. Even simpler, you can just return the results from the query like so:
create or replace function country(text)
returns table (id integer, first_name varchar, last_name varchar) as $$
begin
return query
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = $1;
end;
$$ language plpgsql;
But like Evan Carroll said, unless you need a PL/pgSQL function to modify the data before returning it, you are better off with a simple view.
CREATE OR REPLACE FUNCTIOn func_process_client_contact(p_client_version_id bigint, p_publish_id bigint)
RETURNS TABLE(status_message character varying)
LANGUAGE plpgsql
AS $function$
declare
p_rowcount int;
BEGIN
-- validating input parameters : start
IF ( p_client_version_id IS NULL OR p_publish_id IS NULL )
THEN
RETURN;
END IF;
-- validating input parameters : end
WITH cte_target AS
(
SELECT
g.name AS clientname,
gcr.group_id AS clientid,
cr.id AS clientrelationid,
crc.id AS clientrelationcontactid,
crd.id AS clientrelationdesignation_id
FROM GROUPS g
JOIN group_client_relation gcr ON gcr.group_id =g.id
JOIN client_relation cr ON cr.id = gcr.client_relation_id
JOIN client_relation_contact crc ON crc.client_relation_id =cr.id
JOIN client_relation_designation crd ON cr.client_relation_designation_id =crd.id
)
SELECT * FROM cte_target ct WHERE ct.clientname='ABC';
GET DIAGNOSTICS p_rowcount = ROW_COUNT;
RAISE NOTICE 'returned % rows', p_rowcount;
IF ( p_rowcount=0 )
THEN
RETURN query SELECT 'hello' AS status_message;
ELSE
RETURN query SELECT 'Success' AS status_message;
END IF;
END
$function$
Please use below to get result of given function..
create or replace function country(in_parameter text,out out_res refcursor) as $$
begin
open out_res for
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = '$1';
end;
$$
language plpgsql;
This is not normal for SQL. Normally, this would be a VIEW.
CREATE VIEW myView AS
SELECT customer_id, customer.first_name, customer.last_name
FROM customer
INNER JOIN address USING (address_id)
INNER JOIN city USING (city_id)
INNER JOIN country USING (country_id);
Then you do
SELECT * FROM myView WHERE country = ?
All of that said, if you insist on making this a function, and you shouldn't, you should make it a LANAGUAGE SQL and not LANGUAGE plppsql.
It worked for my colleague when she used OPEN MYCURS before the select query and RETURN MYCURS after the select query.
There might be some situations where we want to call another psql function from a function. If we just want to invoke the function and don't assign the return value to anything, then using select inner_function_call(); inside the main function will throw this error. As the hint suggests, use perform inner_function_call() instead.

How to execute dynamic query in PostgreSQL?

I am trying to execute dynamic query using PostgreSQL. I have a function with three parameters and need to suffix that parameters with some variables(to make view name) and need to retrieve rows from that variables(view) and return the result.
Example
create or replace function testing(abc varchar,def varchar,ghi varchar)
returns setof record as
$BODY$
Declare
temptable1 varchar :='temp1_';
temptable2 varchar :='temp2_';
viewname varchar :='view_';
Body
temptable1 := temptable1||abc;
temptable2 := temptable2||def;
viewname := viewname||ghi;
execute 'Drop table if exists'||temptable1;
execute 'Drop table if exists'||temptable2;
WITH cm
AS
(
SELECT "ssno","rlno",
DENSE_RANK() OVER(Partition by "ssno" Order By "rlno" )FoundIn
From viewname;
)
SELECT DISTINCT * INTO temptable1
FROM cm
WHERE FoundIn > 1;
SELECT DISTINCT cr."ssno", cdr."rlno"
INTO temptable2
FROM temptable1 l1
INNER JOIN viewname cr on l1."rlno" = cr."rlno"
ORDER BY "rlno";
/* Need to result should be display for below query */
SELECT DISTINCT cr.ssno AS Nos, cr.rlno, FoundIn,cr.Name, cr.Address,
from temptable1 l1
inner join viewname cr on l1.rlno = cr.rlno
order by "rlno"
end;
$BODY$
Language plpgsql;