I have a table in the following structure. The element with p_id as null is to be displayed as root and all elements mentioned in p_id column as inner and rest of the elements in the id column as leaf. is this the correct way to write the query or is there a more optimized way to write query in postgres sql.
select id, 'Leaf' as node
from tree
where id NOT IN ( select distinct t1.id as node
from tree t1 join tree t2 on t1.id = t2.p_id )
union
select distinct p_id, 'Inner' as node
from tree
where p_id NOT IN( select id from tree where p_id IS NULL)
union
select id, 'Root'
from tree
where p_id is NULL
order by id
The table for the same looks like
I have tried above query and it gives expected result, however, I am not sure if this perfect.
id p_id
-----------
1 2
3 2
6 8
9 8
2 5
8 5
5 (null)
1 Leaf
2 Inner
3 Leaf
5 Root
6 Leaf
8 Inner
9 Leaf
Usually when working with trees, there is a separate table of nodes. If so, you don't have to use union for this but can move the logic into the select:
select id,
(case when not exists (select 1 from tree t where t.id = n.id and t.p_id is null)
then 'root'
when not exists (select 1 from tree t where t.p_id = n.id)
then 'leaf'
else 'inner'
end)
from nodes n;
You can also do this without the nodes table:
select v.id,
(case when count(*) filter (where t.p_id is null) > 0 then 'root'
when bool_and(v.is_child) then 'leaf'
else 'inner'
end)
from tree t cross join lateral
(values (id, true), (p_id, false)
) v(id, is_child)
where v.id is not null
group by v.id
order by v.id;
Here is a db<>fiddle.
Related
I'm sure this has been asked before, but I can't seem to find any questions that help.
Here's an example of a table:
ID Name Parent ID
---------------------------
1 Ice cream 3
2 Chips 4
3 Sweets null
I'm trying to figure out how to write a single query which, given ID=1, will return me rows 1 and 3. Is this possible without making two queries?
Also, is there a way to return the information of the parent as a custom column? So, rather than returning 2 rows, returning the row where id=1 with parent_id=3 added on?
You can use union all and exists:
select * from mytable where parent_id = 3
union all
select t.*
from mytable t
where exists (select 1 from mytable t1 where t1.parent_id = t.id and t.parent_id = 3)
If you want to do this over multiple levels of hierarchy, then you would typically use a recursive query. The syntax slightly varies accross databases (and not all of them support recursion), but the idea is:
with recursive cte as (
select * from mytable where parent_id = 3
union all
select t.*
from cte c
inner join mytable t on t.parent_id = c.id
)
select * from cte
This is how a straight up noob would do it. Hold tight for someone to give a better way
SELECT ID, Name, Parent_ID
FROM table
WHERE ID = 1
UNION
SELECT ID, Name, Parent_ID
FROM table
WHERE ID = (SELECT Parent_ID FROM table WHERE ID = 1)
Are you looking for something like this?:
select child.ID, child.Name, parent.ID as ParentId, parent.Name as ParentName
from T child left outer join T parent on parent.Id = child.parent_id;
I need a tricky SQL query that should lists to me all unmatched destinations. But there is parent-child relation in destination table. So if a matched destination has an unmatched child destination both parent and child should be listed. I tried to create a table to visualize it. I hope I would help to understand better way.
I tried to write below query bot it did not work
SELECT
*
FROM
Table_A AS ParentTable
WHERE
ID NOT IN (SELECT TableA_ID FROM Table_B WHERE TableA_ID IS NOT NULL)
-- this is to find UNmatched records
AND NOT EXISTS (
SELECT * FROM Table_A AS ChildTable
WHERE ChildTable.Parent = ParentTable.Code)
-- this is the part that I was not sure (:
Here is main destination table
TABLE A
ID Destination ParentID
1 France 0
2 Île-De-France 1
3 Ablis 2
4 Provence-Alpes-Cote D'azur 1
5 Aix-En-Provence 4
Here is the second table
TABLE B
ID Destination TableA_ID
100 France 1
101 Île-De-France 2
102 Ablis NULL
103 Provence-Alpes-Cote D'azur 4
104 Aix-En-Provence 5
In this situation I need to retrieve below table since "Ablis" not matched.
RESULTING TABLE
ID Destination ParentID
1 France 0
2 Île-De-France 1
3 Ablis 2
You can try a recursive cte like below.
See live demo
; with rcte as
(
SELECT
A.id, A.destination, A.parentid
FROM
TABLE_B b join TABLE_A a on a.destination=b.destination WHERE TableA_ID IS NULL
union all
select
T.id, T.destination, T.parentid
from
table_A T join rcte r
on r.parentid=t.id
)
select * from rcte order by parentid asc
I would use not exist with recursive CTE
with t as (
select Id, Destination, ParentID
from table_a a
where not exists (select 1 from table_b where a.id = TableA_ID)
union all
select t.Id, t.Destination, t.ParentID
from t c join table_a a1
on t.id = c.ParentID
)
select * from t
order by 3;
Is anybody to know how to find sets which contain all sets of other sets
table
id elem
1 A
1 C
2 B
2 D
2 A
2 C
3 A
3 E
3 F
4 F
4 F
I want to get this:
id
2
3
In that case id 2 contains (A,C) - all set of id 1
and id 3 contains (F) - all set of id 4
In my query I need to get all id which contains all set (all elements) at least one id.
I will be very grateful. Thank you.
I didn't have much to go off of for this one, but it was interested so I guessed. I'm using SQL Server
Follow along in Rextester
I submit the answer which has nested selects, then I follow through on the step by step logic to clarify what's going on:
Build a quick table
CREATE TABLE a (id int, t varchar(10))
INSERT INTO a (id, t)
VALUES (1,'A'),(1,'B'),(1,'B'),(1,'B'),(2,'A')
,(2,'B'),(2,'C'),(3,'C'),(3,'D'),(4,'A'),(5,'P');
Here is the solution:
select distinct p_id supersets from
(
select e.p_id, e.c_id, count(*) matches from (
select distinct c.id p_id, c.t p_t, d.id c_id, d.t c_t from a c
inner join a d on c.t = d.t and c.id <> d.id) e
group by e.p_id, e.c_id) sup
inner join
(select id, count(distinct t) reqs from a group by id) sub
on sub.id = sup.c_id and sup.matches = sub.reqs;
Here are the logical steps broken out to help explain why I'm doing what I'm doing:
--Step1
--Create a list of matches between (distinct) values where IDs are not the same
select distinct c.id p_id, c.t p_t, d.id c_id, d.t c_t from a c
inner join a d on c.t = d.t and c.id <> d.id;
--Step2
--Create a unique list of parent IDs and their child IDs and the # of distinct matches
--For example 2 has 2 matches with 1 and vice versa
select e.p_id, e.c_id, count(*) matches from (
select distinct c.id p_id, c.t p_t, d.id c_id, d.t c_t from a c
inner join a d on c.t = d.t and c.id <> d.id) e
group by e.p_id, e.c_id;
--Step2a
--Create a sub query to see how many distinct values are in each "Set"
select id, count(distinct t) reqs from a group by id;
Now we put it all together in the join (above, first) to make sure the total # of matches from Parent to Child make up 100% of the child values ie(is a super set)
I think the following does what you want:
select t1.id
from t t1 join
(select t.*, count(*) over (partition by id) as cnt
from t
) t2
on t1.elem = t2.elem and t1.id <> t2.id
group by t1.id, t2.id, t2.cnt
having count(*) = cnt;
This matches each id to each other id based on the elements. If the number of matches equals the count in the second set, then all match -- and you have a superset.
I notice that you have duplicate elements. Let's handle this with a CTE:
with t as (
select distinct id, elem
from t
)
select t1.id
from t t1 join
(select t.*, count(*) over (partition by id) as cnt
from t
) t2
on t1.elem = t2.elem and t1.id <> t2.id
group by t1.id, t2.id, t2.cnt
having count(*) = cnt;
The title may not be accurate for the question but here goes! I have the following table:
id1 id2 status
1 2 a
2 3 b
3 4 c
6 7 d
7 8 e
8 9 f
9 10 g
I would like to get the first id1 and last status based on a dynamic chain joining, meaning that the result table will be:
id final_status
1 c
6 g
Logically, I want to construct the following arrays based on joining the table to itself:
id1 chained_ids chained_status
1 [2,3,4] [a,b,c]
6 [7,8,9,10] [d,e,f,g]
Then grab the last element of the chained_status list.
Since if we were to keep joining this table to itself on id1 = id2 we would eventually have single rows with these results. The problem is that the number of joins is not constant (a single id may be chained many or few times). There is always a 1 to 1 mapping of id1 to id2.
Thanks in advanced! This can be done in either T-SQL or Hive (if someone has a clever map-reduce solution).
You can do this with a recursive CTE:
;WITH My_CTE AS
(
SELECT
id1,
id2,
status,
1 AS lvl
FROM
My_Table T1
WHERE
NOT EXISTS
(
SELECT *
FROM My_Table T2
WHERE T2.id2 = T1.id1
)
UNION ALL
SELECT
CTE.id1,
T3.id2,
T3.status,
CTE.lvl + 1
FROM
My_CTE CTE
INNER JOIN My_Table T3 ON T3.id1 = CTE.id2
)
SELECT
CTE.id1,
CTE.status
FROM
My_CTE CTE
INNER JOIN (SELECT id1, MAX(lvl) AS max_lvl FROM My_CTE GROUP BY id1) M ON
M.id1 = CTE.id1 AND
M.max_lvl = CTE.lvl
I need to retrieve the last (or first) id of a child's parent.
Example:
ID PARENT_ID
----------------
1 NULL
2 1
3 2
So if I search the parent id of id=3 I would have 1 as result.
I tried this but it gives me the same id...
with
tree(id)
as
(
select id
from myTable
where id = 3
union all
select t.id
from myTable t
inner join tree on tree.id = t.father_id
)
select *
from tree;
I already saw examples here and on several websites ;)
You've got some inconsistent naming here. But anyway, your CTE needs to include the parent_id too.
Like this:
with
tree(id,parent_id)
as
(
select id, parent_id
from myTable
where id = 3
union all
select t.id, t.parent_id
from myTable t
inner join tree on t.id = tree.parent_id
)
select *
from tree;