I have a Table that stores Hierarchical information using the Adjacency List model. (uses a self referential key - example below. This Table may look familiar):
category_id name parent
----------- -------------------- -----------
1 ELECTRONICS NULL
2 TELEVISIONS 1
3 TUBE 2
4 LCD 2
5 PLASMA 2
6 PORTABLE ELECTRONICS 1
7 MP3 PLAYERS 6
8 FLASH 7
9 CD PLAYERS 6
10 2 WAY RADIOS 6
What is the best method to "flatten" the above data into something like this?
category_id lvl1 lvl2 lvl3 lvl4
----------- ----------- ----------- ----------- -----------
1 1 NULL NULL NULL
2 1 2 NULL NULL
6 1 6 NULL NULL
3 1 2 3 NULL
4 1 2 4 NULL
5 1 2 5 NULL
7 1 6 7 NULL
9 1 6 9 NULL
10 1 6 10 NULL
8 1 6 7 8
Each row is one "Path" through the Hierarchy, except there is a row for each node (not just each leaf node). The category_id column represents the current node and the "lvl" columns are its ancestors. The value for the current node must also be in the farthest right lvl column. The value in the lvl1 column will always represent the root node, values in lvl2 will always represent direct descendants of lvl1, and so on.
If possible the method to generate this output would be in SQL, and would work for n-tier hierarchies.
As mentioned, SQL has no clean way to implement tables with dynamically varying numbers of columns. The only two solutions I have used before are: 1. A fixed number Self-Joins, giving a fixed number of columns (AS per BobInce) 2. Generate the results as a string in a single column
The second one sounds grotesque initially; storing IDs as string?! But when the output is formatted as XML or something, people don't seem to mind so much.
Equally, this is of very little use if you then want to join on the results in SQL. If the result is to be supplied to an Application, it Can be very suitable. Personally, however, I prefer to do the flattening in the Application rather than SQL
I'm stuck here on a 10 inch screen without access to SQL so I can't give the tested code, but the basic method would be to utilise recursion in some way;
- A recursive scalar function can do this
- MS SQL can do this using a recursive WITH statement (more efficient)
Scalar Function (something like):
CREATE FUNCTION getGraphWalk(@child_id INT)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE @graph VARCHAR(4000)
-- This step assumes each child only has one parent
SELECT
@graph = dbo.getGraphWalk(parent_id)
FROM
mapping_table
WHERE
category_id = @child_id
AND parent_id IS NOT NULL
IF (@graph IS NULL)
SET @graph = CAST(@child_id AS VARCHAR(16))
ELSE
SET @graph = @graph + ',' + CAST(@child_id AS VARCHAR(16))
RETURN @graph
END
SELECT
category_id AS [category_id],
dbo.getGraphWalk(category_id) AS [graph_path]
FROM
mapping_table
ORDER BY
category_id
I haven't used a recursive WITH in a while, but I'll give the syntax a go even though I don't have SQL here to test anything :)
Recursive WITH
WITH
result (
category_id,
graph_path
)
AS
(
SELECT
category_id,
CAST(category_id AS VARCHAR(4000))
FROM
mapping_table
WHERE
parent_id IS NULL
UNION ALL
SELECT
mapping_table.category_id,
CAST(result.graph_path + ',' + CAST(mapping_table.category_id AS VARCHAR(16)) AS VARCHAR(4000))
FROM
result
INNER JOIN
mapping_table
ON result.category_id = mapping_table.parent_id
)
SELECT
*
FROM
result
ORDER BY
category_id
EDIT - OUTPUT for both is the same:
1 '1'
2 '1,2'
3 '1,2,3'
4 '1,2,4'
5 '1,2,5'
6 '1,6'
7 '1,6,7'
8 '1,6,7,8'
9 '1,6,9'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With