Access To Recursive Tree With Cte [closed] - sql

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have 3 table of data: some people has access a branch(s) and branch can have some sub branches
Table Persons
ID PName
1 P1
2 P2
Table Branches
ID Title BrachIDRef
0 Master null
1 B1 0
2 B2 1
3 B3 2
4 B4 2
5 B5 1
6 B6 0
7 B7 6
Table PersonBranches
Id PersonIDref BranchIDref
1 1 1
2 2 6
3 2 2
And Result I need is Access of Persons to branches and Sub branches in SQL Server Query like this :
P1 B1
P1 (and All Childs of B1 - for each child have a record of data)
P2 B6
P2 and All Childs if B6
P2 B2
P2 and All Childs Of B2

You need to use recursive CTE for Branches table and you need to use ROW_NUMBER() for generate dynamic unique record ID, which will use order records.
Before UNION ALL select query generate parent record values with record ID.
After UNION ALL select query generate child records based on parent record.
Please check below query for your expected result.
SELECT * INTO #Persons
FROM (
SELECT '1' ID,'P1' PName UNION ALL
SELECT '2','P2') a
SELECT * INTO #Branches
FROM (
SELECT '0' ID,'Master' Title,NULL BrachIDRef UNION ALL
SELECT '1','B1','0' UNION ALL
SELECT '2','B2','1' UNION ALL
SELECT '3','B3','2' UNION ALL
SELECT '4','B4','2' UNION ALL
SELECT '5','B5','1' UNION ALL
SELECT '6','B6','0' UNION ALL
SELECT '7','B7','6'
) a
SELECT * INTO #PersonBranches
FROM (
SELECT '1' Id,'1' PersonIDref,'1' BranchIDref UNION ALL
SELECT '2' Id,'2' PersonIDref,'6' BranchIDref UNION ALL
SELECT '3' Id,'2' PersonIDref,'2' BranchIDref
) a
;WITH branchCTE
AS
(
SELECT
B.ID,B.Title,B.BrachIDRef,P.PName,ROW_NUMBER() OVER (ORDER BY B.BrachIDRef,B.ID) AS RID
FROM #Branches B
INNER JOIN #PersonBranches PB ON PB.BranchIDref = B.ID
INNER JOIN #Persons P ON P.ID = PB.PersonIDref
UNION ALL
SELECT
B1.ID,B1.Title,B1.BrachIDRef,C.PName,C.RID
FROM #Branches B1
INNER JOIN branchCTE C ON C.ID = B1.BrachIDRef
WHERE B1.BrachIDRef > 0
)
SELECT
C.PName,
C.Title
FROM branchCTE C
ORDER BY C.RID
DROP TABLE #Persons;
DROP TABLE #Branches;
DROP TABLE #PersonBranches;

You can use connect by prior with different CTE and union it with your normal query to fetch all the children as follows:
cte as
(SELECT ANCESTOR,
LISTAGG(case when Title = ANCESTOR then null else title end, '/') within group (order by null) as pth from
(SELECT DISTINCT ID,
CONNECT_BY_ROOT Title as ANCESTOR,
Title
FROM Branches
CONNECT BY PRIOR ID = BrachIDRef)
GROUP BY
ANCESTOR)
--
SELECT pb.id, p.pname, b.title
from PersonBranches pb
join persons p on (p.id = pb.PersonIDref)
join Branches b on (pb.BranchIDref = b.id)
union all
SELECT pb.id, p.pname, c.pth
from PersonBranches pb
join persons p on (p.id = pb.PersonIDref)
join Branches b on (pb.BranchIDref = b.id)
join cte c on (b.title = c.ANCESTOR)
order by id;
db<>fiddle demo
Cheers!!

Related

Join two child records next to each other in one row in PL Sql [duplicate]

This question already has an answer here:
Join/Pivot items with EAV table
(1 answer)
Closed 1 year ago.
I have 2 tables,
select * from Srn_Table1;
ID
CUSTOMERNAME
1
TEST CUSTOMER
select * from Srn_Table2;
ID
ADDRESS
ADDRESSID
1
palakkad
1
1
thrissur
2
I need the Result as
ID
ADDRESS
ADDRESSID
ADDRESS
ADDRESSID
1
palakkad
1
thrissur
2
ID is common.
How can I join the 2 tables to get the result..?
I would add a row number to the second table, so that you can join that table once for when that row number is 1, and a second time for when it is 2:
with ext as (
select Srn_Tablea2.*,
row_number() over (partition by id order by addressid) rn
from Srn_Tablea2
)
select a.id, b.address, b.addressid, c.address, c.addressid
from Srn_Tablea1 a
left join ext b on a.id = b.id and b.rn = 1
left join ext c on a.id = c.id and c.rn = 2;
Of course, if you have cases where you have 3 or more addresses for the same id, you'll have to create more joins, and produce more columns. But the principle remains the same.

SQL | List all all tuples(a, b, c) if there exists another tuple with equal (b,c)

I have three tables where the bold attribute(s) is the primary key
Restaurants(restaurant_ID, name, ...)
resturant_ID, name, ...
1, Macdonalds
2, Hubert
3, Dorsia
... ...
Identifier(restaurant_ID, food_ID)
restaurant_ID, food_ID, ...
1, 1
1, 4
2, 1
2, 7
... ...
Food(food_ID, name, ...)
food_ID food_name
1 Chips
2 Burgers
3 Salmon
... ...
Using postgres I want to list out all restaurants (restaurant_id and name - 1 row per restaurant) that have share the exact same set of foods with at least one other restaurant.
For example, let's say
Restaurant with ID "1" has only associated food_id's 1 and 4 as shown in Identifier
Restaurant with ID "3" has only associated food_id's 4 and 1 as shown in Identifier
Restaurant with ID "7" has only associated food_id's 6 as shown in Identifier
Restaurant with ID "9" has only associated food_id's 6 as shown in Identifier
Then output
Restaurant_id name
1 name1
3 name3
7 ...
9 ...
Any help would be greatly appreciated!
Thank you
Use the aggregate function string_agg() to get the full list of foods for each restaurant:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select r.*
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
where exists (select 1 from cte where restaurant_ID <> c.restaurant_ID and foods = c.foods)
But I would prefer to group restaurants based on matching foods:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select string_agg(r.name, ',') restaurants
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
group by foods
having count(*) > 1
See the demo.
Here is a way to get the unique set of resturants having exactly same food items. This uses array_agg() and array_to_string() functions
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id1,
r1.name as restaurant_1,
t2.restaurant_id as restaurant_id2,
r2.name as restaurant_2,
t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id < t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;
EDIT : Here is a dB fiddle - https://dbfiddle.uk/?rdbms=postgres_12&fiddle=e2de05edfbe036cc0d81c64d60f0b599 . Also, just for reference, solution to the same problem in Oracle using listagg function - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=12785c3d5abbca97be5d44dd45a6da4a
Update : Below query addresses the update output format of the question.
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
--concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id,
r1.name as restaurant--,
--t2.restaurant_id as restaurant_id2,
--r2.name as restaurant_2,
--t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id = t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;
As I understand your question, you want all restaurants that have the same list of foods as restaurant 1.
If so, that's a relation division problem. Here is an approach using joins and aggregation:
select r.name
from identifier i1
inner join identifier i2 on i2.food_id = i1.food_id
inner join restaurant r on r.restaurant_id = i2.restaurant_id
where i1.restaurant_id = 1
group by r.restaurant_id
having count(*) = (select count(*) from identifier i3 where i3.restaurant_id = 1)

Is it possible to replace content from multiple foreign keys when doing a query?

I have the following tables:
TABLE PLAYER
id | name
1 | A
2 | B
3 | C
4 | D
TABLE PAIRINGS
id | player_a | player_b
1 | 3 |1
2 | 2 |4
Both columns in table Pairings are foreign keys to column id of table player.
My problem is, I would like to avoid making a query from code every time I want to know which is my player's name (like, Select name From Player Where Id = pairings.player_a). I have thought about adding Name as an extra columnd to Pairings table, but that would denormalize it.
Instead, it would be nice if I could get the names in just one query, like:
RESULT
player_a | player_b
C | A
B | D
Is it this possible? Thanks so much in advance.
You may join the PAIRINGS table to the PLAYER table, twice:
SELECT
p1.name AS player_a,
p2.name AS player_b
FROM PAIRINGS pr
INNER JOIN PLAYER p1
ON pr.player_a = p1.id
INNER JOIN PLAYER p2
ON pr.player_b = p2.id;
Demo
Don't do it! One of the points of using a relational database is that data is stored in only one place. That is a big convenience. Of course, there are exceptions, but these exceptions should have firm reasons.
In this case, just define a view:
CREATE VIEW vw_pairings AS
SELECT p.*, pa.name AS player_a_name,
pb.name AS player_b_name
FROM PAIRINGS p JOIN
PLAYER pa
ON p.player_a = pa.id JOIN
PLAYER pb
ON p.player_b = pb.id;
When you query from the view, you will see the names, along with all the other information in the PAIRINGS table.
Hope can help you
Select * Into #PLAYER From (
Select 1 [ID], 'A' [Name] Union All
Select 2 [ID], 'B' [Name] Union All
Select 3 [ID], 'C' [Name] Union All
Select 4 [ID], 'D' [Name]
) A
Select * Into #PAIRINGS From (
Select 1 [ID], 3 [PLAYER_A], 1 [PLAYER_B] Union All
Select 2 [ID], 2 [PLAYER_A], 4 [PLAYER_B]
) A
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
You can create view, for avoid making query
Example
Create View vwPAIRINGS As
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
After that, just select usual
Select * from vwPAIRINGS

Combine rows from Mulitple tables into single table

I have one parent table Products with multiple child tables -Hoses,Steeltubes,ElectricCables,FiberOptics.
ProductId -Primary key field in Product table
ProductId- ForeignKey field in Hoses,Steeltubes,ElectricCables,FiberOptics.
Product table has 1 to many relationship with Child tables
I want to combine result of all tables .
For eg - Product P1 has PK field ProductId which is used in all child tables as FK.
If Hoses table has 4 record with ProductId 50 and Steeltubes table has 2 records with ProductId 50 when I perform left join then left join is doing cartesian product of records showing 8 record as result But it should be 4 records .
;with HOSESTEELCTE
as
(
select '' as ModeType, '' as FiberOpticQty , '' as NumberFibers, '' as FiberLength, '' as CableType , '' as Conductorsize , '' as Voltage,'' as ElecticCableLength , s.TubeMaterial , s.TubeQty, s.TubeID , s.WallThickness , s.DWP ,s.Length as SteelLength , h.HoseSeries, h.HoseLength ,h.ProductId
from Hoses h
left join
(
--'' as HoseSeries,'' as HoseLength ,
select TubeMaterial , TubeQty, TubeID , WallThickness , DWP , Length,ProductId from SteelTubes
) s on (s.ProductId = h.ProductId)
) select * from HOSESTEELCTE
Assuming there are no relationships between child tables and you simply want a list of all child entities which make up a product you could generate a cte which has a number of rows which are equal to the largest number of entries across all the child tables for a product. In the example below I have used a dates table to simplify the example.
so for this data
create table products(pid int);
insert into products values
(1),(2);
create table hoses (pid int,descr varchar(2));
insert into hoses values (1,'h1'),(1,'h2'),(1,'h3'),(1,'h4');
create table steeltubes (pid int,descr varchar(2));
insert into steeltubes values (1,'t1'),(1,'t2');
create table electriccables(pid int,descr varchar(2));
truncate table electriccables
insert into electriccables values (1,'e1'),(1,'e2'),(1,'e3'),(2,'e1');
this cte
;with cte as
(select row_number() over(partition by p.pid order by datekey) rn, p.pid
from dimdate, products p
where datekey < 20050105)
select * from cte
create a cartesian join (one of the rare ocassions where an implicit join helps) pid to rn
result
rn pid
-------------------- -----------
1 1
2 1
3 1
4 1
1 2
2 2
3 2
4 2
And if we add the child tables
;with cte as
(select row_number() over(partition by p.pid order by datekey) rn, p.pid
from dimdate, products p
where datekey < 20050106)
select c.pid,h.descr hoses,s.descr steeltubes,e.descr electriccables from cte c
left join (select h.*, row_number() over(order by h.pid) rn from hoses h) h on h.rn = c.rn and h.pid = c.pid
left join (select s.*, row_number() over(order by s.pid) rn from steeltubes s) s on s.rn = c.rn and s.pid = c.pid
left join (select e.*, row_number() over(order by e.pid) rn from electriccables e) e on e.rn = c.rn and e.pid = c.pid
where h.rn is not null or s.rn is not null or e.rn is not null
order by c.pid,c.rn
we get this
pid hoses steeltubes electriccables
----------- ----- ---------- --------------
1 h1 t1 e1
1 h2 t2 e2
1 h3 NULL e3
1 h4 NULL NULL
2 NULL NULL e1
In fact, the result having 8 rows can be expected to be the result, since your four records are joined with the first record in the other table and then your four records are joined with the second record of the other table, making it 4 + 4 = 8.
The very fact that you expect 4 records to be in the result instead of 8 shows that you want to use some kind of grouping. You can group your inner query issued for SteelTubes by ProductId, but then you will need to use aggregate functions for the other columns. Since you have only explained the structure of the desired output, but not the semantics, I am not able with my current knowledge about your problem to determine what aggregations you need.
Once you find out the answer for the first table, you will be able to easily add the other tables into the selection as well, but in case of large data you might get some scaling problems, so you might want to have a table where you store these groups, maintain it when something changes and use it for these selections.

How to select all values between 2 tables in SQL?

I have the following two tables in Oracle database (read only access so I can only use select).
Question table:
N Question ID
1 1
2 2
3 3
Response table:
Question ID Response day
1 01-04-15
3 02-04-15
4 03-04-15
I want the output result to be:
Question ID Response day
1 01-04-15
2 null
3 02-04-15
4 03-04-15
I m strying with the following query and I'm ony getting the question ID when there is a response. Anyone know where I'm going wrong?
select questionID, responseday
from questions
join response
where question.question.ID = response.question.ID;
With this query I'm getting these results:
Question ID Response day
1 01-04-15
3 02-04-15
4 03-04-15
One method is a full outer join:
select coalesce(q.questionid, r.questionid) as questionid,
r.responseday
from questions q full outer join
responses r
on q.questionid = r.questionid;
Based on your schema, a simple LEFT JOIN will be sufficient for your model.
SELECT q.Question_ID,
r.Response_Day
FROM Question AS "q"
LEFT JOIN Response AS "r"
ON r.Question_ID = q.Question_ID;
JOIN or INNER JOIN will only return the result sets from both tables where ALL attributes match. Because your Question record with ID of 2 does not exist in the Response table, SQL will exclude this data.
To account for NULLs, you have to LEFT JOIN, see the sqlfiddle here:
SQL Fiddle
Also, have a look at this graphical model explaining all the JOINs:
Visual Representation of SQL Joins
Use Left Join
select
q.questionid as questionid,
r.responseday
from
questions q
left join
responses r
on
q.questionid = r.questionid;
FULL outer JOIN is sufficient or the output required.
SELECT NVL(a.id,b.id),
b.nm
FROM
(SELECT 1 id,1 nm FROM dual
UNION ALL
SELECT 2 id,2 nm FROM dual
UNION ALL
SELECT 3 id,3 nm FROM dual
)a
FULL OUTER JOIN
(SELECT 1 id,'01-04-15' nm FROM dual
UNION
SELECT 3 id,'02-04-15' nm FROM dual
UNION ALL
SELECT 4 id,'03-04-15' nm FROM dual
)b
ON a.id = b.id;
---------------------------------OUTPUT -----------------------------------
ID NM
1 01-04-15
3 02-04-15
4 03-04-15
2 NULL
----------------------------------OUTPUT-----------------------------------
select questionID
, responseday
from questions
join response
on question.question.ID = response.question.ID;