I have two tables defined below (note that the Regions table is recursive and that the recursion can potentially have many levels).
Regions
Id | ParentId | Name |
---|---|---|
1 | null | EU |
2 | 1 | Germany |
3 | 1 | France |
Cities
Id | Name | RegionId |
---|---|---|
1 | Berlin | 2 |
2 | Hamburg | 2 |
3 | Paris | 3 |
4 | Nice | 3 |
I want to see how many cities there are in a particular region. Desired output below:
Region | CityCount |
---|---|
EU | 4 |
Germany | 2 |
France | 2 |
This query gives me the count of cities in every child region, but how do I join in the recursive table to also get the parent (in this case EU
) region?
select R.Name, count(C.Id)
from Regions R
join Cities C on C.RegionId = R.Id
group by R.Name
having count(C.Id) > 1
I've tried to simplify a real-world problem I'm facing, this is obviously the simplification.
CodePudding user response:
This appears to be what you're after. You can use an rCTE to move through the hierachy to the root, but each iteration retains certain information; in this case the name of the original node. Then you can still JOIN
on the RegionID
:
WITH rCTE AS(
SELECT R.ID,
R.ParentID,
R.[Name]
FROM dbo.Regions R
UNION ALL
SELECT R.ID,
R.ParentID,
C.[Name]
FROM dbo.Regions R
JOIN rCTE C ON R.ParentID = C.ID)
SELECT r.[Name],
COUNT(*) AS CityCount
FROM rCTE r
JOIN dbo.Cities C ON r.ID = C.RegionID
GROUP BY r.[Name];
CodePudding user response:
You can use a recursive CTE to flatten your regions
tree:
with flatregions as
(
select t.ID, t.ParentID, t.Name, 1 as lvl
from regions t
union all
select t.ID, tt.ParentID, tt.Name, t.lvl 1
from flatregions t
inner join regions tt on tt.ID = t.ParentID
)
select * FROM flatregions;
ID | ParentID | Name | lvl |
---|---|---|---|
1 | EU | 1 | |
2 | 1 | Germany | 1 |
3 | 1 | France | 1 |
3 | EU | 2 | |
2 | EU | 2 |
Then use that CTE in the JOIN
of your cities
table:
with flatregions as
(
select t.ID, t.ParentID, t.Name, 1 as lvl
from regions t
union all
select t.ID, tt.ParentID, tt.Name, t.lvl 1
from flatregions t
inner join regions tt on tt.ID = t.ParentID
)
select R.Name, count(C.Id) as CityCount
from flatregions R
join Cities C on C.RegionId = R.Id
group by R.Name
having count(C.Id) > 1
Region | CityCount |
---|---|
EU | 4 |
Germany | 2 |
France | 2 |
See this db<>fiddle.