问题
I've got a very huge table with hierarchy which can not be modified. Nodes in the table have an Id
, ParentId
, a Level
and some data. The Level
means that node with level N
can be a child not only for level N-1
but also for level N-2
, N-3
etc. The good news are that the number of levels is limited - there are only 8 of them. Level 1 is on the top of the hierarchy and level 8 is the end of it.
And now I need to flatten that table with respect to the place of the levels. The result should be like this:
Lvl1 Lvl2 Lvl3 ... Lvl8
xxx xxx null xxx
xxx null xxx xxx
xxx null null xxx
xxx xxx xxx xxx
First step
As the level number is limited, the first idea was to LEFT JOIN
several times all the table on ParentId = Id
. But this made levels change their place, as level 6 could be skipped and level 5 took it's place.
Second step So I've used CASE WHEN
to select value depending on the row's level.
-- LEVEL 4
CASE
WHEN lvl6.[Level] = 4 THEN lvl6.Data -- in case levels 6 and 5 were skipped, we can find 4th level data here
WHEN lvl5.[Level] = 4 THEN lvl5.Data
WHEN lvl4.[Level] = 4 THEN lvl4.Data
ELSE NULL
END AS l4Data,
It solved my problem but it was VERY slow.
Third step The combination of levels is also limited (1-2-3-4-5-6-7-8
, 1-3-5-6-7-8
, etc.) So I decided to use more LEFT JOIN
s to glue all combinations of levels together:
WITH
l7 AS (SELECT * FROM myTable WHERE [Level] = 7),
l6 AS (SELECT * FROM myTable WHERE [Level] = 6),
...
FROM l7
...
LEFT JOIN l6 AS l6_7 ON l7.ParentId = l6_7.Id -- 7-6-5-4-1
LEFT JOIN l5 AS l5_7 ON l6_7.ParentId = l5_7.Id
LEFT JOIN l4 AS l4_7 ON l5_7.ParentId = l4_7.Id
LEFT JOIN l1 AS l1_7 ON l4_7.ParentId = l1_7.Id
And then I selected data using COALESCE
:
COALESCE(l3.Data, l3_1.Data, l3_2.Data, l3_3.Data) AS l3Data,
It made my query VERY complicated and hard to extend, but as for now it's the fastest result I've achieved.
Are there any faster and tiny ways to flattern that table? Any help will be appreciated.
Thanks in advance!
回答1:
This is an example how you could go with a recursive CTE:
To be honest: I'd not expect this to be very fast with huge data...
There is the HIERARCHYID data type, but you said, that you are not allowed to change the table's structure...
DECLARE @t TABLE(Name VARCHAR(100),id INT,parentId INT);
INSERT INTO @t VALUES
('Element 1',1,0)
,('Element 1.1',2,1)
,('Element 1.2',3,1)
,('Element 1.3',4,1)
,('Element 1.1.1',5,2)
,('Element 1.1.2',6,2)
,('Element 1.2.1',7,3)
,('Element 1.2.1.1',8,7)
,('Element 1.2.1.2',9,7);
WITH CTE AS
(
SELECT *
,CAST(parentId AS VARCHAR(MAX)) + ',' + CAST(CAST(id AS VARCHAR(MAX)) AS VARCHAR(MAX)) AS IdListTopDown
,CAST(Name AS varchar(MAX)) AS NameList
FROM @t
WHERE parentId = 0
UNION ALL
SELECT t.*
,CAST(c.IdListTopDown AS VARCHAR(MAX)) + ',' + CAST(CAST(t.id AS VARCHAR(MAX)) AS VARCHAR(MAX))
,CAST(c.NameList + ' | ' + t.Name AS varchar(MAX))
FROM @t AS t
JOIN CTE c ON c.id = t.parentId
)
SELECT CTE.*
FROM CTE
WHERE NOT EXISTS(SELECT * FROM @t WHERE parentId=CTE.id)
ORDER BY CTE.IdListTopDown
The result
Element 1.1.1 5 2 0,1,2,5 Element 1 | Element 1.1 | Element 1.1.1
Element 1.1.2 6 2 0,1,2,6 Element 1 | Element 1.1 | Element 1.1.2
Element 1.2.1.1 8 7 0,1,3,7,8 Element 1 | Element 1.2 | Element 1.2.1 | Element 1.2.1.1
Element 1.2.1.2 9 7 0,1,3,7,9 Element 1 | Element 1.2 | Element 1.2.1 | Element 1.2.1.2
Element 1.3 4 1 0,1,4 Element 1 | Element 1.3
来源:https://stackoverflow.com/questions/35550757/fast-way-to-flatten-hierarchy-table