How can I factor out the common SELECT statement in the following function?
CREATE OR REPLACE PROCEDURE delete_comment(cid integer[]) AS $$
BEGIN
DELETE FROM comment_tree_path
WHERE descendant IN (SELECT descendant
FROM comment_tree_path
WHERE ancestor = ANY(cid));
DELETE FROM comment
WHERE comment_id IN (SELECT descendant
FROM comment_tree_path
WHERE ancestor = ANY(cid));
END;
$$ LANGUAGE plpgsql;
Actually the second DELETE statement won't work, because the first one will delete all rows with cids from comment_tree_path table and as a result the second SELECT will be empty.
You can use a CTE:
with descendants as (
SELECT descendant
FROM comment_tree_path
WHERE ancestor = ANY(cid)
), delete_tree_path as (
DELETE FROM comment_tree_path
WHERE descendant IN (select descendant from descendants)
)
DELETE FROM comment
WHERE comment_id IN (select descendant from descendants);
Related
We used to have a really badly performing CTE that used to take 3-5 mins to execute. I have modified the CTE and used a function with temp tables to accomplish the same task. Now the new function runs in less than 5 secs.
This is what I did:
From
WITH CTE1 AS (
SELECT ...
FROM ...
),
CTE2 AS (
SELECT ...
FROM ...
),
CTEn AS (
SELECT ...
FROM ...
)
SELECT A,B,C,D
FROM CTE1
JOIN CTE2 ON ...
JOIN CTEn ON ...;
TO
CREATE OR REPLACE FUNCTION FUNC_ABC(a integer)
RETURNS TABLE(A integer, B integer, C integer, D integer)
LANGUAGE plpgsql
AS $function$
DECLARE
x ALIAS for $1;
BEGIN
DROP TABLE IF EXISTS CTE1;
DROP TABLE IF EXISTS CTE2;
DROP TABLE IF EXISTS CTEn;
CREATE TEMP TABLE CTE1 AS
( SELECT ...
FROM ...
);
CREATE TEMP TABLE CTE2 AS
( SELECT ...
FROM ...);
CREATE TEMP TABLE CTEn AS
( SELECT ...
FROM ...);
CREATE INDEX ix_cte1 ON CTE1(A);
CREATE INDEX ix_cte2 ON CTE2(B);
CREATE INDEX ix_cten ON CTEn(C);
CREATE INDEX ix_cten ON CTEn(D);
RETURNS QUERY SELECT A,B,C,D
FROM CTE1
JOIN CTE2 ON ...
JOIN CTEn ON ...
END;
$function$
;
As I stated above, the function pretty fast. The reason behind adding "DROP TABLE" is that, within a transaction, this function can be executed any number of times. But, intermittently, we see an error like:
ERROR: must be owner of relation CTE1
I am not able to reproduce this error. And there is only one user that runs this function. No other user has permissions to execute this function.
I couldn't think of a scenario when this would fail. Any thoughts of insights will be appreciated.
Child table that looks like this
CREATE TABLE folder_item (
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
,parent_id uuid REFERENCES folder_item (id) ON DELETE CASCADE
,role text NOT NULL DEFAULT 'inherit'
);
With a permissions model
CREATE POLICY folder_item_rolecheck ON folder_item FOR SELECT USING ( role = assigned_role );
However if it finds a row with 'inherit' I want it to look at the parent role instead (recursively)
Is that possible?
-- Set NO FORCE ROW LEVEL SECURITY on table "folder_item" to off RLS for OWNER
ALTER TABLE folder_item NO FORCE ROW LEVEL SECURITY
-- Create function with RECURSIVE qwery and SECURITY DEFINER with OWNER as for table "folder_item"
CREATE OR REPLACE FUNCTION folder_item_check_child(
in_parent_id uuid
, in_role text)
RETURNS boolean
LANGUAGE 'plpgsql'
COST 100
STABLE SECURITY DEFINER
AS $BODY$BEGIN
RETURN EXISTS(
WITH RECURSIVE
childs AS (
SELECT tt.id, tt.role FROM folder_item AS tt
WHERE tt.parent_id=in_parent_id
UNION
SELECT child.id, child.role
FROM childs AS parent
INNER JOIN folder_item AS child ON child.parent_id=parent.id
)
SELECT * FROM childs AS tt WHERE tt.role=in_role
);
END$BODY$;
-- CREATE POLICY
CREATE POLICY folder_item_rolecheck ON folder_item FOR SELECT USING ( role = assigned_role
OR folder_item_check_child(id, assigned_role)
);
A few days ago I asked a question about deleting using WITH RECURSIVE from PostgreSQL. There is:
DELETE recursive PostgreSQL
That works fine: the intention, initially, was to delete parent folders recursively as long as the final child was deleted. The following image describes it better:
Files tree view
By deleting the file 5.jpg, all parent folders, in this situation, would be deleted as well.
But now I have to delete the parent folders only if they get empty, i.e. by losing its only child. I tried the following:
WITH RECURSIVE all_uploads (codigo, parent, ext, uploader) AS (
SELECT ut1.codigo, ut1.codigo_upload_temp_pai AS parent, ut1.codigo_extensao AS ext, ut1.codigo_usuario_inclusao AS uploader
FROM upload_temp ut1
WHERE ut1.codigo = 576
UNION ALL
SELECT ut2.codigo, ut2.codigo_upload_temp_pai AS parent, ut2.codigo_extensao AS ext, ut2.codigo_upload_temp_pai AS uploader
FROM upload_temp ut2
JOIN all_uploads au ON au.parent = ut2.codigo
WHERE (SELECT ut3.codigo FROM upload_temp ut3 WHERE ut3.codigo_upload_temp_pai = ut2.codigo LIMIT 1) IS NULL
AND ext IS NULL
AND uploader = 1535
)
DELETE FROM upload_temp WHERE codigo IN (SELECT codigo FROM all_uploads);
I thought the only way to check if a folder is empty is to perform a sub-select considering the self relationship. If SELECT ut3.codigo FROM upload_temp ut3 WHERE ut3.codigo_upload_temp_pai = ut2.codigo LIMIT 1) IS NULL returns true, so the folder is empty. And by using the self referencing feature (same DB table for folders and files), I know it's a folder by checking codigo_extensao field (only files have extensions).
Well, it's not working, it removes only my 5.jpg. Any hint? Thanks in advance!
You can't DELETE recursively like you want. The logic here is to create a query that deletes everything you want, and run it recursively until there is anything more to delete.
Here is a function that does exactly what you need:
CREATE OR REPLACE FUNCTION p_remove_empty_folders(_codigo_usuario_ integer) RETURNS integer AS $$
DECLARE
AFFECTEDROWS integer;
BEGIN
WITH a AS (
DELETE FROM upload_temp WHERE codigo IN (SELECT ut1.codigo FROM upload_temp ut1 WHERE ut1.codigo_usuario_inclusao = _codigo_usuario_ AND ut1.codigo_extensao IS NULL AND NOT EXISTS (SELECT * FROM upload_temp ut2 WHERE ut2.codigo_upload_temp_pai = ut1.codigo))
RETURNING 1
)
SELECT count(*) INTO AFFECTEDROWS FROM a;
WHILE AFFECTEDROWS > 0 LOOP
WITH a AS (
DELETE FROM upload_temp WHERE codigo IN (SELECT ut1.codigo FROM upload_temp ut1 WHERE ut1.codigo_usuario_inclusao = _codigo_usuario_ AND ut1.codigo_extensao IS NULL AND NOT EXISTS (SELECT * FROM upload_temp ut2 WHERE ut2.codigo_upload_temp_pai = ut1.codigo))
RETURNING 1
)
SELECT count(*) INTO AFFECTEDROWS FROM a;
END LOOP;
RETURN 0;
END;
$$ LANGUAGE plpgsql;
Cya!
I have a trigger like this: (Basically on update of a column in table1, I update a column in table 2)
CREATE OR REPLACE TRIGGER AAA AFTER UPDATE
ON TABLE_1 REFERENCING NEW AS NEWROW OLD AS OLDROW
FOR EACH ROW
WHEN (
NEWROW.DELETED ='Y' AND NEWROW.ID IN (41,43)
AND OLDROW.DELETED = 'N'
)
DECLARE
id_1 number;
id_2 number;
id_3 number;
BEGIN
select id_1, id_2,id_3 into id_1,id_2,id_3 from table_1 where id_1 = :NEWROW.id1 and id2 = some_other_row.id2;
if id_1 is null
then
update table2 set deleted = 'Y' , where table2.id_1 = id_1 and table2.id_2=id_2 and table2.id_3 = id_3;
end if;
EXCEPTION
WHEN OTHERS
THEN
-- Consider logging the error and then re-raise
RAISE;
END AAA;
/
When I update table1 I get:
ORA-04091: table table1 is mutating, trigger/function may not see it
I thought this error happens only when you are updating the table on which the trigger is trying to update something. But here I am updating table1 and trigger is supposed to update table2. SO why is the error?
It's the SELECT statement that is causing the problem here. Inside the trigger, you cannot SELECT from the same table. In your example, you don't need/can't use the SELECT statement. You can get the values by simply using :newrow.id_1, :newrow.id_2 and :newrow.id_3.
I'm trying to run a graph search to find all nodes accessible from a starting point, like so:
with recursive
nodes_traversed as (
select START_NODE ID
from START_POSITION
union all
select ed.DST_NODE
from EDGES ed
join nodes_traversed NT
on (NT.ID = ed.START_NODE)
and (ed.DST_NODE not in (select ID from nodes_traversed))
)
select distinct * from nodes_traversed
Unfortunately, when I try to run that, I get an error:
Recursive CTE member (nodes_traversed) can refer itself only in FROM clause.
That "not in select" clause is important to the recursive expression, though, as it provides the ending point. (Without it, you get infinite recursion.) Using generation counting, like in the accepted answer to this question, would not help, since this is a highly cyclic graph.
Is there any way to work around this without having to create a stored proc that does it iteratively?
Here is my solution that use global temporary table, I have limited recursion by level and nodes from temporary table.
I am not sure how it will work on large set of data.
create procedure get_nodes (
START_NODE integer)
returns (
NODE_ID integer)
as
declare variable C1 integer;
declare variable C2 integer;
begin
/**
create global temporary table id_list(
id integer
);
create index id_list_idx1 ON id_list (id);
*/
delete from id_list;
while ( 1 = 1 ) do
begin
select count(distinct id) from id_list into :c1;
insert into id_list
select id from
(
with recursive nodes_traversed as (
select :START_NODE AS ID , 0 as Lv
from RDB$DATABASE
union all
select ed.DST_NODE , Lv+1
from edges ed
join nodes_traversed NT
on
(NT.ID = ed.START_NODE)
and nt.Lv < 5 -- Max recursion level
and nt.id not in (select id from id_list)
)
select distinct id from nodes_traversed);
select count(distinct id) from id_list into :c2;
if (c1 = c2) then break;
end
for select distinct id from id_list into :node_id do
begin
suspend ;
end
end