I would use the XML extensions to split the path into its component parsts, you can also use xml extensions to get the position of each xml element, which combined with ROW_NUMBER
(to account for an empty node at the start) gives you your depth
field:
WITH Menus AS
( SELECT m.MarketID,
[Name] = y.value('.', 'nvarchar(max)'),
[Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
FROM Markets m
CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + CASE WHEN SID = 3 THEN CONVERT(VARCHAR(5), [Time], 8) + ' ' ELSE '' END + marketName + '</y></x>' AS XML))) a (x)
CROSS APPLY x.nodes('/x/y') b (y)
WHERE y.value('.', 'nvarchar(max)') != ''
)
SELECT MarketID,
[Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
Depth
FROM Menus
Example of Split on SQL Fiddle
Just as a side note your table structure will end up with redundant information, depth could be obtained by counting the number of recursions to get back to a top level parent, or if marketID was stored in all rows the top level parent could be obtained by finding depth = 0. So the output of the above query should give you everything you need. But I'll continue none the less.
The first step will be to insert all items into the menu table with 0
as the parent.
WITH Menus AS
( SELECT m.MarketID,
[Name] = y.value('.', 'nvarchar(max)'),
[Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
FROM Markets m
CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + marketName + '</y></x>' AS XML))) a (x)
CROSS APPLY x.nodes('/x/y') b (y)
WHERE y.value('.', 'nvarchar(max)') != ''
)
INSERT Menu (ParentID, Depth, Name, MarketID)
SELECT [ParentID] = 0,
Depth,
[Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
MarketID
FROM Menus
Then update the market table with the right parent IDs
UPDATE Menu
SET ParentID = p.ID
FROM Menu c
INNER JOIN
( SELECT ID, MarketID, Depth
FROM Menu
) p
ON c.MarketID = p.MarketID
AND c.Depth = p.Depth + 1
The final step is to set the Market ID to null for all but the base menu:
WITH CTE AS
( SELECT *,
[maxDepth] = MAX(Depth) OVER(PARTITION BY MarketID)
FROM Menu
)
UPDATE CTE
SET MarketID = NULL
WHERE MaxDepth != Depth;
And voila, you have your desired result.
ADDENDUM
This seems to work:
CREATE TABLE #TempMenu (MarketID INT, Name VARCHAR(200) NOT NULL, Depth INT NOT NULL);
WITH Menus AS
( SELECT m.MarketID,
[Name] = y.value('.', 'nvarchar(max)'),
[Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
FROM Markets m
CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + CASE WHEN SID = 3 THEN CONVERT(VARCHAR(5), [Time], 8) + ' ' ELSE '' END + marketName + '</y></x>' AS XML))) a (x)
CROSS APPLY x.nodes('/x/y') b (y)
WHERE y.value('.', 'nvarchar(max)') != ''
)
INSERT #TempMenu (MarketID, name, Depth)
SELECT MarketID,
[Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
Depth
FROM Menus;
CREATE TABLE #TempPaths
( ID INT NOT NULL,
ParentID INT NOT NULL,
Depth INT NOT NULL,
Name VARCHAR(200) NOT NULL,
MarketID INT NULL,
ParentPath VARCHAR(200) NULL,
CurrentPath VARCHAR(200) NULL
);
WITH Paths AS
( SELECT MarketID,
[Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
Depth,
[MaxDepth] = MAX(Depth) OVER(PARTITION BY MarketID),
[ParentPath] = ( SELECT '/' + Name
FROM #TempMenu p
WHERE p.MarketID = c.MarketID
AND p.Depth < c.Depth
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'),
[CurrentPath] = ( SELECT '/' + Name
FROM #TempMenu p
WHERE p.MarketID = c.MarketID
AND p.Depth <= c.Depth
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)')
FROM #TempMenu c
), Paths2 AS
( SELECT DISTINCT [ParentID] = 0, Depth, Name, [MarketID] = NULL, [ParentPath], [CurrentPath]
FROM Paths
WHERE MaxDepth != Depth
UNION
SELECT 0, Depth, Name, MarketID, [ParentPath], [CurrentPath]
FROM Paths
WHERE MaxDepth = Depth
)
-- USE MERGE CONDITION THAT WILL NEVER MATCH, ALLOWS ACCESS TO VALUES NOT BEING INSERTED IN THE OUTPUT CLAUSE
MERGE INTO Menu m USING Paths2 p ON 1 = 0
WHEN NOT MATCHED THEN
INSERT (ParentID, Depth, Name, MarketID)
VALUES (p.ParentID, p.Depth, p.Name, p.MarketID)
OUTPUT inserted.ID, inserted.ParentID, inserted.Depth, inserted.Name, inserted.MarketID, p.ParentPath, p.CurrentPath INTO #TempPaths;
UPDATE Menu
SET ParentID = rel.ParentID
FROM Menu
INNER JOIN
( SELECT [ChildID] = c.ID, [ParentID] = p.ID
FROM #TempPaths c
INNER JOIN #TempPaths p
ON c.ParentPath = p.CurrentPath
) rel
ON rel.ChildID = Menu.ID;
DROP TABLE #TempMenu, #TempPaths;
To explain roughly what is going on I have used the same method as above to split the paths into their component parts, putting these into a temp table (for performance reasons), the split paths are then combined into full paths again and put into another temporary table, these paths will be used later to match parent and child records.
The next part uses the merge statement to insert the records to the menu table, this is used because the Identities from the insert need to be matched to the full paths, and when using INSERT
the OUTPUT
only allows access to the inserted values, and not other columns from the source.
Finally once all the records are inserted the temp table of paths can be used to match parent and child records (based on the path), and update the menu table.
This seems quite a convoluted method, but it is entirely set based so should out perform the procedural approach.