Assumptions:
CategoryId is a primary key.
If the Description at the row level is null look at parent row. If Parent row has null description, look at parent's parent. Etc. In other words use the first non-null description of ancestors.
If row level Description is null, and no ancestor exists with non null Description, overall description is null
Set up example table and test data.
create table #SO (CategoryID int primary key
, ParentCategoryID int Null
, Name varchar(255) not null
, Description varchar(MAX) Null
)
insert into #SO (CategoryID, ParentCategoryID, Name, Description)
values (1, null, 'Top 1', 'Top 1 Description')
, (2, null, 'Top 2', 'Top 2 Description')
, (3, null, 'Top 3', null)
, (11, 1, 'Child 11', 'Child 11 Description')
, (12, 1, 'Child 12', null)
, (21, 2, 'Child 21', null)
, (211, 21, 'Child 211', null)
, (2111, 211, 'Child 2111', null)
, (2112, 211, 'Child 2112', 'Child 2112 Description')
, (31, 3, 'Child 31', 'Child 31 Description')
, (32, 3, 'Child 32', null)
Using a recursive CTE. Note that the tree is walked upwards. We start with all rows, and then look at parents as needed instead of doing the normal tree manipulation of starting at the top of the tree and working down.
; with Description (BaseCategoryId
, CurrentParentCategoryId
, CurrentDescription
, CurrentLevel)
as
(-- Anchor -- Start with all rows in the table.
select CategoryId as BaseCategoryId
, ParentCategoryId as CurrentParentCategoryId
, Description as CurrentDescription
, 0 as CurrentLevel
from #SO -- Recursive -- We are walking up the tree from all nodes,
-- We only continue up the tree when we do not have a description yet.
union all
select D.BaseCategoryId
, so.ParentCategoryId
, so.Description
, D.CurrentLevel + 1
from #SO so
inner join Description D
on D.CurrentParentCategoryId = so.CategoryId
and D.CurrentDescription is null)
select DL.BaseCategoryId as CategoryId
, DL.CurrentDescription as UltimateDescription
-- Now self outer join with the CTE every step of the walk
-- for each BaseCategoryId, and then filter all but the top
-- level. (Level is measured as distance from base.)
from Description as DL
left outer join Description as DR
on DL.BaseCategoryId = DR.BaseCategoryId
and DL.CurrentLevel < DR.CurrentLevel
where DR.BaseCategoryId is null
order by DL.BaseCategoryId
The output is a mapping from CategoryId to the ultimate description.
In terms of re-use I would make the above a view.