Это не особенно эффективно, но если то, что вы хотите сделать, это эффективно "взорвать" всю иерархию и получить результаты в последовательности от родителя к листу, что-то вроде этого сделает это:
WITH CategoryHierarchy AS
(
SELECT
ID, ParentCategoryIdFk, 0 AS Level,
ROW_NUMBER() OVER (ORDER BY ID) AS SubTreeID
FROM Category
WHERE CategoryID IN
(
SELECT pc.CategoryID
FROM Sale s
INNER JOIN Product p
ON p.saleidfk = s.id
INNER JOIN ProductCategory pc
ON pc.productid = p.id
WHERE s.id = @SaleID
)
UNION ALL
SELECT c.ID, c.ParentCategoryIdFk, h.Level + 1, h.SubTreeID
FROM CategoryHierarchy h
INNER JOIN Category c
ON c.ID = h.ParentID
)
SELECT c.ID, c.ParentCategoryIdFk AS ParentID, c.Name
FROM CategoryHierarchy h
INNER JOIN Category c
ON c.ID = h.ID
ORDER BY h.SubTreeID ASC, h.Level DESC
Это должно привести к следующим результатам:
ID | ParentID | Name
---+----------+----------
1 | NULL | Men
2 | 1 | Shoes
3 | 2 | Sport
---+----------+----------
1 | NULL | Men
2 | 1 | Shoes
4 | 2 | Casual
---+----------+----------
1 | NULL | Men
5 | 1 | Watches
---+----------+----------
6 | NULL | Women
10 | 6 | Watches
Конечно, фактические результаты не будут иметь таких разделителей, я добавил их, чтобы прояснить значение результатов.
Если вы не хотите, чтобы он полностью взорвался, как это, вы можете использовать другой rownum, чтобы вернуть только первый экземпляр каждого из родителей:
WITH CategoryHierarchy AS
(
SELECT
ID, ParentCategoryIdFk, 0 AS Level,
ROW_NUMBER() OVER (ORDER BY ID) AS SubTreeID
FROM Category
WHERE CategoryID IN
(
SELECT pc.CategoryID
FROM Sale s
INNER JOIN Product p
ON p.saleidfk = s.id
INNER JOIN ProductCategory pc
ON pc.productid = p.id
WHERE s.id = @SaleID
)
UNION ALL
SELECT c.ID, c.ParentCategoryIdFk, h.Level + 1, h.SubTreeID
FROM CategoryHierarchy h
INNER JOIN Category c
ON c.ID = h.ParentID
),
Filter_CTE AS
(
SELECT
ID, Level, SubTreeID
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY SubTreeID) AS RowNum
FROM CategoryHierarchy
)
SELECT c.ID, c.ParentCategoryIdFk AS ParentID, c.Name
FROM Filter_CTE f
INNER JOIN Category c
ON c.ID = f.ID
WHERE f.RowNum = 1
ORDER BY f.SubTreeID ASC, f.Level DESC
... выдаст вам результаты, аналогичные:
ID | ParentID | Name
---+----------+----------
1 | NULL | Men
2 | 1 | Shoes
3 | 2 | Sport
4 | 2 | Casual
5 | 1 | Watches
6 | NULL | Women
10 | 6 | Watches
Примечание: Будьте осторожны со второй версией, поскольку не обязательно гарантировать, что результаты будут возвращаться в иерархическом порядке. Просто так получается, что эта версия делает, потому что сами идентификаторы находятся в иерархическом порядке. Вы можете обойти это ограничение, но это усложнит этот и без того сложный запрос.
Вторая версия гарантирует , что главная категория всегда будет отображаться перед любой из ее подкатегорий, что хорошо, если вы планируете построить рекурсивную структуру данных с использованием словаря. Возможно, он не подходит для более быстрого построения дерева на основе стеков или создания отчетов напрямую пользователю. Для этих целей вы бы хотели использовать первую версию.