Recursive delete of multiple rows in one table - sql

I have a problem with my postgres database. I have a table Tasks with 3 columns: ID, Name and Parent_ID (which refers to another task id in this table):
id | name | parent_id
---+------+-----------
1 | A | 0
2 | B | 1
3 | C | 2
4 | D | 1
5 | E | 0
6 | F | 0
So basically it's like this:
1. A
2. B
3. C
4. D
5. E
6. F
What I'm trying to do is to delete task A, and delete all of its children and all children of children etc etc..(in this case B and D, along with C as its children of B which is deleted) something like cascade delete, but i cant do this. Maybe any function will work?
The result after delete should be
id | name | parent_id
---+------+-----------
5 | E | 0
6 | F | 0
Hope you guys can help me.

You only need a cascading FK-constraint:
\i tmp.sql
CREATE TABLE employees (
id serial PRIMARY KEY
, name VARCHAR NOT NULL
, parent_id INT REFERENCES employees(id) ON DELETE CASCADE
);
INSERT INTO employees
VALUES (1,'A', NULL),(2,'B', 1),(3,'C', 2),
(4,'D', 1),(5,'E', NULL),(6,'F', NULL);
DELETE FROM employees
WHERE name = 'A'
;
SELECT * FROM employees
;
----------
Result:
----------
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 6
DELETE 1
id | name | parent_id
----+------+-----------
5 | E |
6 | F |
(2 rows)

You can build the list of all descendent rows from the starting name with a recursive query, and then use it as a filter for the deletetions.
with recursive cte(id, parent_id) as (
select id, parent_id from mytable where name = 'A'
union all
select t.id, t.parent_id from mytable t inner join cte c on c.id = t.parent_id
)
delete from mytable where id in (select id from cte)
Demo on DB Fiddle - table content after executing the query:
id | name | parent_id
-: | :--- | --------:
5 | E | 0
6 | F | 0

Use recursive cte to get all rows with Name = 'A' and it's subordinates.
Then delete it from table employees.
Here is the step to create the table:
Sample table: CREATE TABLE employees (
id serial PRIMARY KEY,
name VARCHAR NOT NULL,
parent_id INT
);
INSERT INTO employees
VALUES (1,'A', 0),(2,'B', 1),(3,'C', 2),
(4,'D', 1),(5,'E', 0),(6,'F', 0);
Query:
WITH RECURSIVE subordinates AS (
SELECT
id,
parent_id,
name
FROM
employees
WHERE
name = 'A'
UNION
SELECT
e.id,
e.parent_id,
e.name
FROM
employees e
INNER JOIN subordinates s ON s.id = e.parent_id
)
DELETE
FROM employees
WHERE id in (
SELECT
id
FROM
subordinates);
SELECT * FROM employees;

Related

SQL - How to check if users are in the same hierarchy?

I want to find out if users are directly in a parent child relation.
Given my user table schema
User_id | Parent_ID | Name
For example, I have a list of user_id's and I want to know if they are all in the same hierarchical tree.
I have tried using CTE recursive.
Sample data
User_id | Parent_ID | Name
1 | | A
2 | 1 | B
3 | 2 | C
4 | 3 | D
5 | 2 | E
6 | | F
7 | 6 | G
user_id varchar(100)
parent_id varchar(100)
Desired result: Input [2,3,4] => Same Team
Input [2,3,7] => Not same team
Use the top-level parents' parent_id as the hierarchy identifier:
with recursive hierarchies as (
select user_id, user_id as hierarchy_id
from ttable
where parent_id is null
union all
select c.user_id, p.hierarchy_id
from hierarchies p
join ttable c on c.parent_id = p.user_id
)
select * from hierarchies;
With that mapping of each user_id to a single hierarchy_id, you can join to your list of users.
EDIT BEGINS
Since you added sample data and example results that do not match your original question, here is an example of how any minimally competent programmer could slightly tweak the above to match the newly added contradictory examples:
with recursive subhierarchies as (
select user_id, array[user_id] as path
from ttable
where parent_id is null
union all
select c.user_id, p.path||c.user_id as path
from subhierarchies p
join ttable c on c.parent_id = p.user_id
)
select d.user_ids, count(s.path) > 0 as same_team
from (values (array[2, 3, 4]), (array[2, 3, 6])) as d(user_ids)
left join subhierarchies s
on s.path #> d.user_ids
group by d.user_ids
;

How to get average with recursive query

I'm trying to write recursive query with postgres and its working fine and returning me all users which comes under user 5 with this codes :
WITH RECURSIVE subordinates AS (
SELECT
id,
supervisor_id,
name
FROM
employees
WHERE
id = 5
UNION
SELECT
e.id,
e.supervisor_id,
e.name
FROM
employees e
INNER JOIN subordinates s ON s.id = e.supervisor_id
) SELECT
*
FROM
subordinates;
But I've 1 more table in it which is called biscuits which have 3 columns type, weight and cooked_by_employee_id
now I want average weight of biscuits with id, name and supervisor_id but just small twist is if user id 1 comes under 3 and 3 comes under 4 and 4 comes under 5 then it should return average weight of all 1,3,4 and 5 cooked_by_employee_id records and 1 user can have multiple records in biscuits
I tried this but not working
WITH RECURSIVE subordinates AS (
SELECT
e.id,
e.supervisor_id,
e.name,
AVG(b.weight)
FROM
employees e
LEFT JOIN burrito b ON
e.id=b.cooked_by_employee_id
WHERE
e.id = 5
UNION
SELECT
e.id,
e.supervisor_id,
e.name,
b.weight
FROM
employees e
INNER JOIN subordinates s ON s.id = e.supervisor_id
LEFT JOIN burrito b ON b.cooked_by_employee_id=e.supervisor_id
) SELECT
*
FROM
subordinates;
Sample data :
employees :
+----+------+---------------+
| id | name | supervisor_id |
+----+------+---------------+
| 1 | a | 3 |
+----+------+---------------+
| 2 | b | 4 |
+----+------+---------------+
| 3 | c | 5 |
+----+------+---------------+
| 4 | d | 0 |
+----+------+---------------+
| 5 | e | 0 |
+----+------+---------------+
burrito :
+----+-------+--------+-----------------------+
| id | type | weight | cooked_by_employee_id |
+----+-------+--------+-----------------------+
| 1 | sweet | 1 | 1 |
+----+-------+--------+-----------------------+
| 2 | salty | 2 | 1 |
+----+-------+--------+-----------------------+
| 3 | sweet | 3 | 3 |
+----+-------+--------+-----------------------+
So if it runs for employee_id 5 then it should return
+-------------+------+-----------------------------------------------------+
| employee_id | name | weight |
+-------------+------+-----------------------------------------------------+
| 5 | e | 2 (Average 2 because both 1 and 3 id comes under 5) |
+-------------+------+-----------------------------------------------------+
You have the recursive CTE figured out and that could be half (or more) of the battle. The following however builds a slightly different one. The recursive cte (path_to_chef) gets employee ids and builds a path from the top level to the employee. The second cte (cook_chef) then strips the path string back to the top level employee id. The result then contains 2 columns, the employee id and the employee id for the root level employee. This then sets the stage to join with the other tables for the final result.
The query can work as stand alone and for a single root level by supplying id where indicated. But I tend to create generic reusable routines so I've wrapped it into a SQL function.
create or replace function chef_avg_burrito_weight()
returns table ( employee_id integer
, name text
, weight numeric
)
language sql
as $$
with recursive path_to_chef as
( select id, trim(to_char(id, '9999'))||'>' path
from employees
where supervisor_id = 0
union all
select e.id, path || trim(to_char(e.id, '9999'))||'>'
from employees e
join path_to_chef c on e.supervisor_id = c.id
) -- select * from path_to_chef
, cook_chef as
(select id cook
, substring(path from '^([[:digit:]]+)>')::integer chef
from path_to_chef
) -- select * from cook_chef
select e.id, e.name, round(avg(b.weight),2) average_weight
from burrito b
join cook_chef c on (b.cooked_by_employee_id = c.cook)
join employees e on (e.id = c.chef)
-- where chef = 5
group by e.id, e.name;
$$;
-- test
select *
from chef_avg_burrito_weight()
where employee_id = 5;

replace select table by an update

I have an intermediate table:
text_mining_molecule
|text_mining_id| molecule_id |
| -------------| ---------- |
| ID | ID |
and two other tables:
Table Molécules:
id | main_name | others …
--- | --------- | ------
1 | caféine | others …
Table jsonTextMining:
id | title | molecule_name | others …
---|------- |-------------------------------------|------
1 | title1 | colchicine, cellulose, acid, caféine| others …
text_mining_molecule need to be inserted when select a choice in a list with ID's from 2 others tables json_text_mining and molecule.
Actually there is a dropdown that already insert all rows from json_text_mining to text_mining when choose a score under 4.
INSERT INTO text_mining (id, solrid, originalpaper, annotatedfile, title, keyword, importantsentence, version, klimischscore, moleculename, synonymname, validation)
SELECT id, solrid, originalpaper, annotatedfile, title, keyword, importantsentence, version, klimischscore, molecule_name, synonym_name, validation
FROM json_text_mining WHERE klimischscore < 4
This works but i need text_mining_molecule to be filled also with related ID's so i have also this part of code :
SELECT s.id, m.id
FROM (SELECT id, regexp_split_to_table(molecule_name,', ') AS m_name
FROM json_text_mining) AS s, molecule m
WHERE m.main_name = s.m_name;
How can i update text_mining_molecule table directly with an insert instead a select ?
use CTE. eg if text_mining_molecule.molecule references molecule.id, would be smth like:
with c as (
SELECT s.id sid, m.id mid
FROM (SELECT id, regexp_split_to_table(molecule_name,', ') AS m_name
FROM json_text_mining) AS s, molecule m
WHERE m.main_name = s.m_name
)
update text_mining_molecule t
set sid = c.sid
from c
where t.molecule = c.mid

Want to know all possible Parent and Child Rows against specific Id?

How to know all possible Parent and Child Rows against specific Id?
e.g. have following table:
MyTable:
-----------------------------------------------------
| Id | PId | Description |
-----------------------------------------------------
| 1 | NULL | A is Parent |
| 2 | 1 | B is Child of A |
| 3 | 2 | C is Child of B |
| 4 | NULL | D is Parent |
| 5 | NULL | E is Parent |
| 6 | 5 | F is Child of E |
-----------------------------------------------------
want to know all possible parent and child when pass spesific id
e.g.
CASE-01:
When #MyLookupId=2 OR #MyLookupId=1 OR #MyLookupId=3 One of from them Then Result Should Be,
-------
| Id |
-------
| 1 |
| 2 |
| 3 |
-------
CASE-02:
When #MyLookupId=4 Then Result Should Be,
-------
| Id |
-------
| 4 |
-------
CASE-03:
When #MyLookupId=6 Then Result Should Be,
-------
| Id |
-------
| 5 |
| 6 |
-------
Here is SQL for table:
IF OBJECT_ID('tempdb.dbo.#MyTable', 'U') IS NOT NULL DROP TABLE #MyTable;
SELECT * INTO #MyTable FROM (
SELECT (1)Id, (NULL)PId, ('A IS Parent')Description UNION ALL
SELECT (2)Id, (1)PId, ('B IS Child of A')Description UNION ALL
SELECT (3)Id, (2)PId, ('C IS Child of B')Description UNION ALL
SELECT (4)Id, (NULL)PId, ('D IS Parent')Description UNION ALL
SELECT (5)Id, (NULL)PId, ('E IS Parent')Description UNION ALL
SELECT (6)Id, (5)PId, ('F IS Child of E')Description ) AS tmp
SELECT * FROM #MyTable
You could use recursive cte
-- temp returns full tree of each rootId (parentid = null)
;WITH temp AS
(
SELECT sd.Id, sd.PId, sd.Id AS RootId
FROM #MyTable sd
WHERE sd.PId IS NULL
UNION ALL
SELECT sd.Id, sd.PId, t.RootId
FROM temp t
INNER JOIN #MyTable sd ON t.Id = sd.PId
)
SELECT t2.Id
FROM temp t
INNER JOIN temp t2 ON t2.RootId = t.RootId
WHERE t.Id = #Id
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/RAITMT72805
The answer given by TriV works, but requires a calculation of the entire hierarchy of your source table each time the query is run, which may not perform well at larger scale.
A more narrow approach is to find the Parent and Child records that only relate to the ID you are searching for:
declare #t table(ID int, PID int);
insert into #t values(1,null),(2,1),(3,2),(4,null),(5,null),(6,5);
declare #ID int = 2;
with c as
(
select ID
,PID
from #t
where ID = #ID
union all
select t.ID
,t.PID
from #t t
join c
on(t.PID = c.ID)
)
,p as
(
select ID
,PID
from #t
where ID = #ID
union all
select t.ID
,t.PID
from #t t
join p
on(t.ID = p.PID)
)
select ID
from p
union all
select ID
from c
where c.ID <> #ID
order by ID;
Output:
ID
````
1
2
3

SQL To Find All Descendents

I have a data table like this
Entities:
ID | Parent_ID
1 | null
2 | 1
3 | 1
4 | 3
5 | 4
6 | 4
I'd like a sql expression that will return a row for every entity and a linear descendant, plus a row for null if the entity has no descendants. So given the above data my result set would be:
Entity | Descendant
1 | 2
1 | 3
1 | 4
1 | 5
1 | 6
2 | null
3 | 4
3 | 5
3 | 6
4 | 5
5 | null
6 | null
I tried using a common table expression to achieve this, and think it's the way to do it, given its ability to recurse, but I couldn't get my head wrapped around the spawning of many rows for a single parent.
with all_my_children (my_father,my_id,my_descendant,level)
as
(
select parent_id,id,null,0
from Entities
where id not in (select parent_id from entities)
union all
select e.parent_id,e.id,amc.my_id,amc.level+1
from Entities e
inner join all_my_children amc
on e.id = amc.my_father
WHERE ????? --How do I know when I'm done? and How do I keep repeating parents for each descendant?
)
select my_id, my_descendant from all_my_children
Thanks for your time.
Here's what you asked for
WITH TEMP AS
(
SELECT ID AS ENTITY, PID AS DESCENDANTS
FROM YPC_BI_TEMP.DBO.SV7104
WHERE PID IS NULL
UNION ALL
SELECT PID AS ENTITY, ID AS DESCENDANTS
FROM YPC_BI_TEMP.DBO.SV7104
WHERE PID IS NOT NULL
UNION ALL
SELECT PRNT.ENTITY, CHILD.ID AS DESCENDANTS
FROM YPC_BI_TEMP.DBO.SV7104 AS CHILD
INNER JOIN TEMP AS PRNT
on PRNT.DESCENDANTS = CHILD.PID
--AND PRNT.ENTITY IS NOT NULL
)
SELECT DISTINCT ENTITY, DESCENDANTS FROM TEMP
UNION
SELECT ID AS ENTITY, NULL AS DESCENDANTS FROM YPC_BI_TEMP.DBO.SV7104
WHERE ID NOT IN (SELECT ENTITY FROM TEMP)
Deleted my previous answer, but I think this might do the trick...
WITH all_my_children AS (my_father,my_id,my_descendant,level)
(
SELECT parent_id, id, null, 0
FROM Entities
WHERE parent_id IS NULL -- the roots of your tree
UNION ALL
SELECT COALESCE(e2.parent_id, e.parent_id), e.id, amc.my_id, amc.level+1
FROM Entities e
JOIN all_my_children amc
ON e.parent_id = amc.my_id
LEFT JOIN Entities e2
ON e.id = e2.parent_id
)
SELECT * FROM all_my_children