Вот версия на основе CTE, больше в качестве доказательства концепции, чем полезный ответ. Он использует STRING_AGG
для объединения дочерних узлов каждого узла по порядку, затем рекурсивно заменяет каждый узел его дочерними элементами для построения выходной строки - это означает, что он не будет работать в ситуациях, когда ключи узлов являются подстроками друг друга.
DECLARE @t TABLE
(id CHAR(1) NOT NULL PRIMARY KEY,
parentid CHAR(1) NULL,
roworder TINYINT NOT NULL
)
INSERT @t (id, parentid, roworder)
VALUES('A', NULL, 0),
('B','A',0),
('C','A',1),
('D','C',0),
('E','C',1),
('F','C',2),
('G','E',0),-- two extra nodes to prove that this isn't a one-off
('H','E',1)
;WITH aggCTE
AS
(
SELECT parentid, STRING_AGG(CONVERT(VARCHAR(MAX), id), ' ') WITHIN GROUP (ORDER BY Roworder) AS children
FROM @t
GROUP BY parentid
)
,recCTE
AS
(
SELECT a.parentid,
a.children,
CAST(ISNULL(a.parentid,'') AS VARCHAR(MAX)) AS processed, --to prevent loops
0 AS seq, --to pick the right output row
a.children AS firstnode --to disambiguate if the data contains multiple trees
FROM aggCTE AS a
WHERE a.parentid IS NULL
UNION ALL
SELECT a.parentid,
REPLACE(a.children, b.parentid, CONCAT(b.children, ' ', b.parentid)) AS children,
CONCAT(a.processed, b.parentid) AS processed,
a.seq + 1 AS seq,
a.firstnode
FROM recCTE AS a
JOIN aggCTE AS b
ON CHARINDEX(b.parentid, a.children) > 0
AND CHARINDEX(b.parentid, a.processed) = 0
)
,rnCTE
AS
(
SELECT children,
ROW_NUMBER() OVER (PARTITION BY firstnode ORDER BY seq DESC) AS rn
FROM recCTE
)
SELECT children AS post_order_traversal
FROM rnCTE
WHERE rn = 1