Добрый день,
Прежде чем я начну! Настоятельно рекомендуется переосмыслить структуру вашей базы данных, если это ваше требование. Использование рекурсивного CTE (или, возможно, большинства других рекурсивных решений) означает, что вы используете петлевое решение, которое не рекомендуется в СУБД (которое лучше всего оптимизировано для работы с SET данных). Если вам нужно использовать рекурсивные данные, то вам следует подумать, например, об использовании типа данных иерархии (или реализовать что-то подобное или лучше - да, например, существуют более эффективные алгоритмы для иерархии, основанные на диапазонах, но их более сложно объяснить вкратце сообщение на форуме). Более того, у вас должна быть очень веская причина делать это без рекурсивного CTE, поскольку для текущей структуры рекурсивное CTE должно обеспечивать наилучшую производительность в большинстве случаев.
С учетом вышесказанного, поскольку вы запрашиваете это решение, проверьте, удовлетворяет ли этот запрос вашим потребностям
-- DDL+DML
drop table if exists [ProductHierarchy];
CREATE TABLE [dbo].[ProductHierarchy]
(
[ProductID] INT NOT NULL ,
[ParentProductID] INT NULL
);
INSERT [dbo].[ProductHierarchy]
( [ProductID], [ParentProductID] )
VALUES ( 1, NULL ),
( 2, 1 ),
( 3, 1 ),
( 4, 2 ),
( 5, 4 ),
( 6, 4 ),
( 7, 4 ),
( 8, 2)
GO
-- Solution?
SELECT distinct [level], [ProductID] FROM (
-- I recomend to check this inner quesry first
-- in order to understand the logic
select
A.ProductID as L1,
B.ProductID as L2,
C.ProductID as L3,
D.ProductID as L4
from (
select ProductID
from [ProductHierarchy]
where ParentProductID is null
) A
-- For each level that we wany to get
-- we will add another sub-query
-- like bellow
left join(
SELECT [ProductID], [ParentProductID]
FROM [ProductHierarchy]
) B
ON B.[ParentProductID] = A.[ProductID]
left join(
SELECT [ProductID], [ParentProductID]
FROM [ProductHierarchy]
) C
ON C.[ParentProductID] = B.[ProductID]
left join(
SELECT [ProductID], [ParentProductID]
FROM [ProductHierarchy]
) D
ON D.[ParentProductID] = C.[ProductID]
) T
UNPIVOT (
ProductID for [Level] in (L1,L2,L3,L4)
) as a
Примечание. При сравнении моего решения ( при условии, что оно соответствует вашим потребностям ) с Lukasz Szozda, согласно плану выполнения (я не проверял IO или Time), мое решение будет использовать только 22%, в то время как решение Lukasz использует 78% (протестировано на сервере sql 2017)