CTE Recursion to get tree hierarchy - sql-server

I need to get an ordered hierarchy of a tree, in a specific way. The table in question looks a bit like this (all ID fields are uniqueidentifiers, I've simplified the data for sake of example):
EstimateItemID EstimateID ParentEstimateItemID ItemType
-------------- ---------- -------------------- --------
1 A NULL product
2 A 1 product
3 A 2 service
4 A NULL product
5 A 4 product
6 A 5 service
7 A 1 service
8 A 4 product
Graphical view of the tree structure (* denotes 'service'):
A
___/ \___
/ \
1 4
/ \ / \
2 7* 5 8
/ /
3* 6*
Using this query, I can get the hierarchy (just pretend 'A' is a uniqueidentifier, I know it isn't in real life):
DECLARE #EstimateID uniqueidentifier
SELECT #EstimateID = 'A'
;WITH temp as(
SELECT * FROM EstimateItem
WHERE EstimateID = #EstimateID
UNION ALL
SELECT ei.* FROM EstimateItem ei
INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID
)
SELECT * FROM temp
This gives me the children of EstimateID 'A', but in the order that it appears in the table. ie:
EstimateItemID
--------------
1
2
3
4
5
6
7
8
Unfortunately, what I need is an ordered hierarchy with a result set that follows the following constraints:
1. each branch must be grouped
2. records with ItemType 'product' and parent are the top node
3. records with ItemType 'product' and non-NULL parent grouped after top node
4. records with ItemType 'service' are bottom node of a branch
So, the order that I need the results, in this example, is:
EstimateItemID
--------------
1
2
3
7
4
5
8
6
What do I need to add to my query to accomplish this?

Try this:
;WITH items AS (
SELECT EstimateItemID, ItemType
, 0 AS Level
, CAST(EstimateItemID AS VARCHAR(255)) AS Path
FROM EstimateItem
WHERE ParentEstimateItemID IS NULL AND EstimateID = #EstimateID
UNION ALL
SELECT i.EstimateItemID, i.ItemType
, Level + 1
, CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255))
FROM EstimateItem i
INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID
)
SELECT * FROM items ORDER BY Path
With Path - rows a sorted by parents nodes
If you want sort childnodes by ItemType for each level, than you can play with Level and SUBSTRING of Pathcolumn....
Here SQLFiddle with sample of data

This is an add-on to Fabio's great idea from above. Like I said in my reply to his original post. I have re-posted his idea using more common data, table name, and fields to make it easier for others to follow.
Thank you Fabio! Great name by the way.
First some data to work with:
CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20));
INSERT INTO tblLocations (Code, ParentID, Name) VALUES
('A', NULL, 'West'),
('A', 1, 'WA'),
('A', 2, 'Seattle'),
('A', NULL, 'East'),
('A', 4, 'NY'),
('A', 5, 'New York'),
('A', 1, 'NV'),
('A', 7, 'Las Vegas'),
('A', 2, 'Vancouver'),
('A', 4, 'FL'),
('A', 5, 'Buffalo'),
('A', 1, 'CA'),
('A', 10, 'Miami'),
('A', 12, 'Los Angeles'),
('A', 7, 'Reno'),
('A', 12, 'San Francisco'),
('A', 10, 'Orlando'),
('A', 12, 'Sacramento');
Now the recursive query:
-- Note: The 'Code' field isn't used, but you could add it to display more info.
;WITH MyCTE AS (
SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath
FROM tblLocations T1
WHERE ParentID IS NULL
UNION ALL
SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath
FROM tblLocations T2
INNER JOIN MyCTE itms ON itms.ID = T2.ParentID
)
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results.
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath
FROM MyCTE
ORDER BY TreePath;

I believe that you need to add the following to the results of your CTE...
BranchID = some kind of identifier that uniquely identifies the branch. Forgive me for not being more specific, but I'm not sure what identifies a branch for your needs. Your example shows a binary tree in which all branches flow back to the root.
ItemTypeID where (for example) 0 = Product and 1 = service.
Parent = identifies the parent.
If those exist in the output, I think you should be able to use the output from your query as either another CTE or as the FROM clause in a query. Order by BranchID, ItemTypeID, Parent.

Related

Add a where clause in SQL Server CTE

I have used a SQL Server CTE for selecting data from my self-referencing table whose schema is
CREATE TABLE BaseDomainTable
(
[BaseModelId] INT,
[Comments] NVARCHAR(100) NULL,
[ParentModelId] INT NULL
)
Each ModelId has comments and may or may not have parent ie any model can be a first model and any model can start a new branch of its own by starting from any parent
INSERT INTO BaseDomainTable ([BaseModelId],[Comments],[ParentModelId])
VALUES (1, 'Comments 1', NULL), (2, 'Comments 2', 1),
(3, 'Comments for 3', 2), (4, 'Comments 4', 2)
After this insert, 1 is my base parent, 2 is derived from 1, 3 and 4 are derived from 2.
To get the data in a hierarchical format I have added a cte.
WITH ParentCTECheck (BaseModelId, Comments, ParentModelId) AS
(
SELECT
Parent.BaseModelId, Parent.Comments, Parent.ParentModelId
FROM
BaseDomainTable Parent
WHERE
Parent.ParentModelId IS NULL
AND Parent.Comments IS NOT NULL
UNION ALL
SELECT
Derived.BaseModelId, Derived.Comments, Derived.ParentModelId
FROM
BaseDomainTable Derived
JOIN
ParentCTECheck ON ParentCTECheck.BaseModelId = Derived.ParentModelId
WHERE
Derived.ParentModelId IS NOT NULL
AND Derived.Comments IS NOT NULL
)
SELECT *
FROM ParentCTECheck
AND I am getting this output:
BaseModelId Comments ParentModelId
------------------------------------------
1 Comments 1 NULL
2 Comments 2 1
3 Comments for 3 2
4 Comments 4 2
I want to change it such that if I pass in the BaseModelId as 4 the cte will traverse the loop for 4 and skip all the data related to 3 in this case and return data for 4,2,1. And when I pass 2 it should skip both 3 and 4 and get the CTE will traverse loop of 2 ie 2,1
Is there a way that this can be done?
You have to traverse the tree bottom up:
;WITH ParentCTECheck AS (
-- Anchor query: get leaf node
SELECT BaseModelId, Comments, ParentModelId
FROM BaseDomainTable
WHERE BaseModelId = 4 -- <-- Id of leaf node
UNION ALL
-- Recursive query: go up the tree and get next level nodes.
-- Recursion terminates as soon as the parent node is met.
SELECT bt.BaseModelId, bt.Comments, bt.ParentModelId
FROM BaseDomainTable AS bt
JOIN ParentCTECheck AS ct ON bt.BaseModelId = ct.ParentModelId
)
SELECT *
FROM ParentCTECheck
Demo here

How do I look for matching data in SQL?

I have a database table for a todo list application, and i need a way to track tasks which are dependant on other tasks, I already have a table with ID,title, description, IsComplete and a DependsOnTask column, containing the unique identifier for the task another given task is dependant on.
the problem is, when I try the below in SQL it doesn't give any results!
SELECT TOP 1000 [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [master].[dbo].[ToDoItems] where ToDoItems.id =ToDoItems.DependsOnTask;
So my question is, is there a way to find all records with a unique identifier matching DependsOnTask?
Thanks in advance :)
You are missing a JOIN:
SELECT tdi.*, dot.*
FROM dbo.ToDoItems tdi JOIN
dbo.ToDoItems dot
ON dot.id = tdi.DependsOnTask;
This returns all tasks where DependsOnTask is not null, along with information from that record.
Notes:
You don't need to use square braces when they are not necessary. They just clutter up queries.
Use table aliases and qualify column names, so you know where columns are coming from.
You need to use an explicit JOIN for references back to the same table.
If you have a hierarchical structure and task could have a parent and that parent is a child to another task, yo can use recursive CTE to find all hierarchy of determined task.
Let me show an example.
You got structure like this:
SELECT *
FROM (VALUES
(1,'Title1','Do some stuff 1', 0, NULL),
(2,'Title2','Do some stuff 2', 0, NULL),
(3,'Title3','Do some stuff 3', 1, 1),
(4,'Title4','Do some stuff 4', 1, 1),
(5,'Title5','Do some stuff 5', 0, 2),
(6,'Title6','Do some stuff 6', 1, 2),
(7,'Title7','Do some stuff 7', 0, 4),
(8,'Title8','Do some stuff 8', 0, NULL)
) as t([id],[title],[description],[complete],[DependsOnTask])
So task 1 has 2 child tasks - 3 and 4. The 4th task got 1 child - 7. You want to get all child tasks of task with id = 1:
DECLARE #taskid int = 1
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [ToDoItems]
WHERE [id] = #taskid
UNION ALL
SELECT t.*
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT *
FROM cte
Output:
id title description complete DependsOnTask
1 Title1 Do some stuff 1 0 NULL
3 Title3 Do some stuff 3 1 1
4 Title4 Do some stuff 4 1 1
7 Title7 Do some stuff 7 0 4
So if you change last select to:
SELECT #taskid as main,
id,
DependsOnTask
FROM cte
You will get:
main id DependsOnTask
1 1 NULL
1 3 1
1 4 1
1 7 4
So you get all child tasks of Task1.
If you change CTE like this:
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
,[id] as Parent
FROM [ToDoItems]
WHERE [DependsOnTask] IS NULL
UNION ALL
SELECT t.*,
c.Parent
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT Parent,
id,
DependsOnTask
FROM cte
You will got all you need: Parent task, Child tasks and what are they dependent on:
Parent id DependsOnTask
1 1 NULL
2 2 NULL
8 8 NULL
2 5 2
2 6 2
1 3 1
1 4 1
1 7 4

CTE to pull entire tree from arbitrary entry

I'm trying to build a CTE which will pull back all records which are related to a given, arbitrary record in the database.
Create table Requests (
Id bigint,
OriginalId bigint NULL,
FollowupId bigint NULL
)
insert into Requests VALUES (1, null, 3)
insert into Requests VALUES (2, 1, 8)
insert into Requests VALUES (3, 1, 4)
insert into Requests VALUES (4, 3, null)
insert into Requests VALUES (5, null, null)
insert into Requests VALUES (6, null, 7)
insert into Requests VALUES (7, 6, null)
insert into Requests VALUES (8, 2, null)
OriginalId is always the Id of a previous record (or null). FollowupId points to the most recent followup record (which, in turn, points back via OriginalId) and can probably be ignored, but it's there if it's helpful.
I can easily pull back either all ancestors or all descendants of a given record using the following CTE
;With TransactionList (Id, originalId, followupId, Steps)
AS
(
Select Id, originalId, followupId, 0 as Steps from requests where Id = #startId
union all
select reqs.Id, reqs.originalId, reqs.followupId, Steps + 1 from requests reqs
inner join TransactionList tl on tl.Id = reqs.originalId --or tl.originalId = reqs.Id
)
SELECT Id from TransactionList
However, if I use both where clauses, I run into recursion, hit the recursion limit, and it bombs out. Even combining both sets, I don't get the entire tree - just one branch from it.
I don't care about anything other than the list of Ids. They don't need to be sorted, or to display their relationship or anything. Doesn't hurt, but not necessary. But I need every Id in a given tree to pull back the same list when it's passed as #startId.
As an example of what I'd like to see, this is what the output should be when #startId is set to any value 1-4 or 8:
1
2
3
4
8
And for either 6 or 7, I get back both 6 and 7.
You can just create 2 CTE's.
The first CTE will get the Root of the hierarchy, and the second will use the Root ID to get the descendants of the Root.
;WITH cteRoot AS (
SELECT *, 0 [Level]
FROM Requests
WHERE Id = #startId
UNION ALL
SELECT r.*, [Level] + 1
FROM Requests r
JOIN cteRoot cte ON r.Id = cte.OriginalID
),
cteDesc AS (
SELECT *
FROM cteRoot
WHERE OriginalId IS NULL
UNION ALL
SELECT r.*, [Level] + 1
FROM Requests r
JOIN cteDesc cte ON r.OriginalId = cte.Id
)
SELECT * FROM cteDesc
SQL Fiddle

Retrieving full hierarchy sorted by a column under PostgreSQL's Ltree module

I'm using PostgreSQL's Ltree module for storing hierarchical data. I'm looking to retrieve the full hierarchy sorted by a particular column.
Consider the following table:
votes | path | ...
-------+-------+-----
1 | 1 | ...
2 | 1.1 | ...
4 | 1.2 | ...
1 | 1.2.1 | ...
3 | 2 | ...
1 | 2.1 | ...
2 | 2.1.1 | ...
4 | 2.1.2 | ...
... | ... | ...
In my current implementation, I'd query the database with SELECT * FROM comments ORDER BY path, which would return the whole tree:
Node 1
-- Node 1.1
-- Node 1.2
---- Node 1.2.1
Node 2
-- Node 2.1
---- Node 2.1.1
---- Node 2.1.2
However, I want to sort by votes (not by id, which is what sorting by path amounts to). Each depth level needs to be independently sorted, with the correct tree structure kept intact. Something that would return the following:
Node 2
-- Node 2.1
---- Node 2.1.2
---- Node 2.1.1
Node 1
-- Node 1.2
---- Node 1.2.1
-- Node 1.1
Postgres' WITH RECURSIVE might be appropriate, but I'm not sure. Any ideas?
You were on the right track with WITH RECURSIVE.
Solution with recursive CTE
WITH RECURSIVE t AS (
SELECT t.votes
, t.path
, 1::int AS lvl
, to_char(t2.votes, 'FM0000000') AS sort
FROM tbl t
JOIN tbl t2 ON t2.path = subltree(t.path, 0, 1)
UNION ALL
SELECT t.votes
, t.path
, t.lvl + 1
, t.sort || to_char(t2.votes, 'FM0000000')
FROM t
JOIN tbl t2 ON t2.path = subltree(t.path, 0, t.lvl + 1)
WHERE nlevel(t.path) > t.lvl
)
SELECT votes, path, max(sort) AS sort
FROM t
GROUP BY 1, 2
ORDER BY max(sort), path;
Major points
The crucial part is to replace every level of the path with the value of votes. Thereby we assemble one column we can ORDER BY at the end. This is necessary, because the path has an unknown depth and we cannot order by an unknown number of expressions in static SQL.
In order to get a stable sort, I convert votes to a string with leading zeroes using to_char(). I use seven digits in the demo, which works for vote values below 10.000.000. Adjust according to your maximum vote count.
In the final SELECT I exclude all intermediary states to eliminate duplicates. Only the last step with max(sort) remains.
This works in standard SQL with a recursive CTE, but is not very efficient for large trees. A plpgsql function that recursively updates the sort path in a temporary table without creating temporary dupes might perform better.
Only works with the additional module ltree installed, which provides the functions subltree() and nlevel(), as well as the ltree data type.
My test setup, for review convenience:
CREATE TEMP TABLE tbl(votes int, path ltree);
INSERT INTO tbl VALUES
(1, '1')
, (2, '1.1')
, (4, '1.2')
, (1, '1.2.1')
, (3, '2')
, (1, '2.1')
, (2, '2.1.1')
, (4, '2.1.2')
, (1, '2.1.3')
, (2, '3')
, (17, '3.3')
, (99, '3.2')
, (10, '3.1.1')
, (2345, '3.1.2')
, (1, '3.1.3')
;
PL/pgSQL table function doing the same
Should be faster with huge trees.
CREATE OR REPLACE FUNCTION f_sorted_ltree()
RETURNS TABLE(votes int, path ltree)
LANGUAGE plpgsql VOLATILE AS
$func$
DECLARE
lvl integer := 0;
BEGIN
CREATE TEMP TABLE t ON COMMIT DROP AS
SELECT tbl.votes
, tbl.path
, ''::text AS sort
, nlevel(tbl.path) AS depth
FROM tbl;
-- CREATE INDEX t_path_idx ON t (path); -- beneficial for huge trees
-- CREATE INDEX t_path_idx ON t (depth);
LOOP
lvl := lvl + 1;
UPDATE t SET sort = t.sort || to_char(v.votes, 'FM0000000')
FROM (
SELECT t2.votes, t2.path
FROM t t2
WHERE t2.depth = lvl
) v
WHERE v.path = subltree(t.path, 0 ,lvl);
EXIT WHEN NOT FOUND;
END LOOP;
-- Return sorted rows
RETURN QUERY
SELECT t.votes, t.path
FROM t
ORDER BY t.sort;
END
$func$;
Call:
SELECT * FROM f_sorted_ltree();
Read in the manual about setting temp_buffers.
I would be interested which performs faster with your real life data.
create table comments (
id serial,
parent_id int,
msg text,
primary key (id)
);
insert into comments (id, parent_id, msg) values (1, null, 'msg 1');
insert into comments (id, parent_id, msg) values (2, null, 'msg 2');
insert into comments (id, parent_id, msg) values (3, 1, 'msg 1 / ans 1');
insert into comments (id, parent_id, msg) values (4, null, 'msg 3');
insert into comments (id, parent_id, msg) values (5, 2, 'msg 2 / ans 1');
insert into comments (id, parent_id, msg) values (6, 2, 'msg 2 / ans 2');
insert into comments (id, parent_id, msg) values (7, 2, 'msg 2 / ans 3');
desc
WITH RECURSIVE q AS
(
SELECT id, msg, 1 as level, ARRAY[id] as path
FROM comments c
WHERE parent_id is null
UNION ALL
SELECT sub.id, sub.msg, level + 1, path || sub.id
FROM q
JOIN comments sub
ON sub.parent_id = q.id
)
SELECT id, msg, level
FROM q
order by path || array_fill(100500, ARRAY[8 - level]) desc;
results in
4,"msg 3",1
2,"msg 2",1
7,"msg 2 / ans 3",2
6,"msg 2 / ans 2",2
5,"msg 2 / ans 1",2
1,"msg 1",1
3,"msg 1 / ans 1",2
asc
WITH RECURSIVE q AS
(
SELECT id, msg, 1 as level, ARRAY[id] as path
FROM comments c
WHERE parent_id is null
UNION ALL
SELECT sub.id, sub.msg, level + 1, path || sub.id
FROM q
JOIN comments sub
ON sub.parent_id = q.id
)
SELECT id, msg, level
FROM q
--order by path || array_fill(100500, ARRAY[8 - level]) desc;
order by path;
results in
1,"msg 1",1
3,"msg 1 / ans 1",2
2,"msg 2",1
5,"msg 2 / ans 1",2
6,"msg 2 / ans 2",2
7,"msg 2 / ans 3",2
4,"msg 3",1

SQL Server 2005: Update rows in a specified order (like ORDER BY)?

I want to update rows of a table in a specific order, like one would expect if including an ORDER BY clause, but SQL Server does not support the ORDER BY clause in UPDATE queries.
I have checked out this question which supplied a nice solution, but my query is a bit more complicated than the one specified there.
UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
FROM TableA AS Child
WHERE Child.ParentColB = Parent.ColB
ORDER BY Child.Priority)
ORDER BY Parent.Depth DESC;
So, what I'm hoping that you'll notice is that a single table (TableA) contains a hierarchy of rows, wherein one row can be the parent or child of any other row. The rows need to be updated in order from the deepest child up to the root parent. This is because TableA.ColA must contain an up-to-date concatenation of its own current value with the values of its children (I realize this query only concats with one child, but that is for the sake of simplicity - the purpose of the example in this question does not necessitate any more verbosity), therefore the query must update from the bottom up.
The solution suggested in the question I noted above is as follows:
UPDATE messages
SET status=10
WHERE ID in (SELECT TOP (10) Id
FROM Table
WHERE status=0
ORDER BY priority DESC
);
The reason that I don't think I can use this solution is because I am referencing column values from the parent table inside my subquery (see WHERE Child.ParentColB = Parent.ColB), and I don't think two sibling subqueries would have access to each others' data.
So far I have only determined one way to merge that suggested solution with my current problem, and I don't think it works.
UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
FROM TableA AS Child
WHERE Child.ParentColB = Parent.ColB
ORDER BY Child.Priority)
WHERE Parent.Id IN (SELECT Id
FROM TableA
ORDER BY Parent.Depth DESC);
The WHERE..IN subquery will not actually return a subset of the rows, it will just return the full list of IDs in the order that I want. However (I don't know for sure - please tell me if I'm wrong) I think that the WHERE..IN clause will not care about the order of IDs within the parentheses - it will just check the ID of the row it currently wants to update to see if it's in that list (which, they all are) in whatever order it is already trying to update... Which would just be a total waste of cycles, because it wouldn't change anything.
So, in conclusion, I have looked around and can't seem to figure out a way to update in a specified order (and included the reason I need to update in that order, because I am sure I would otherwise get the ever-so-useful "why?" answers) and I am now hitting up Stack Overflow to see if any of you gurus out there who know more about SQL than I do (which isn't saying much) know of an efficient way to do this. It's particularly important that I only use a single query to complete this action.
A long question, but I wanted to cover my bases and give you guys as much info to feed off of as possible. :)
Any thoughts?
You cannot succeed this in one query, because your updates are correlated (ie. level N depends on the updated value of level N+1). Relational engines frown on this very explicitly because of the Halloween Problem. The query plan will go out of its way to ensure that the updates occur as if they had two stages: one in which the current state was read, and then one in which the updated state was applied. If necessary, they'll spool intermediate tables just to preserve this apparent execution order (read all->write all). Since your query, if I understand correctly, tries to break this very premise I don't see any way you'll succeed.
UPDATE statements will be executed as a single query, not as a step by step result.
You need to either use a while loop/cursor (uhhgg) or maybe make use of a CTE expression view to achieve what you are trying, which gives you the recursice possibility.
Have a look at
Using Common Table Expressions
Recursive Queries Using Common Table
Expressions
Here is a one line SQL solution. If you ever relax the requirement that it need be one update statement you can factor out some of the complexity
CREATE TABLE [TableA](
[ID] [int] NOT NULL,
[ParentID] [int] NULL,
[ColA] [varchar](max) NOT NULL,
[Priority] [varchar](50) NOT NULL,
[Depth] [int] NOT NULL)
go
INSERT TableA
SELECT 1, NULL, 'p', 'Favorite', 0 UNION ALL
SELECT 2, 1, 'm', 'Favorite', 1 UNION ALL
SELECT 3, 1, 'o', 'Likeable', 1 UNION ALL
SELECT 4, 2, 'v', 'Favorite', 2 UNION ALL
SELECT 5, 2, 'v', 'Likeable', 2 UNION ALL
SELECT 6, 2, 'd', 'Likeable', 2 UNION ALL
SELECT 7, 6, 'c', 'Red-headed Stepchild', 3 UNION ALL
SELECT 8, 6, 's', 'Likeable', 3 UNION ALL
SELECT 9, 8, 'n', 'Favorite', 4 UNION ALL
SELECT 10, 6, 'c', 'Favorite', 3 UNION ALL
SELECT 11, 5, 'c', 'Favorite', 3 UNION ALL
SELECT 12, NULL, 'z', 'Favorite', 0 UNION ALL
SELECT 13, 3, 'e', 'Favorite', 2 UNION ALL
SELECT 14, 8, 'k', 'Likeable', 4 UNION ALL
SELECT 15,4, 'd', 'Favorite', 3
;WITH cte AS (
SELECT a.i, a.Depth, a.maxd, a.mind, a.maxc, a.di, a.ci, a.cdi, a.ID, a.y, CAST('' AS varchar(max))z
FROM(
SELECT DISTINCT i = 1
,p.Depth
,maxd = (SELECT MAX(Depth) FROM TableA)
,mind = (SELECT MIN(Depth) FROM TableA)
,maxc = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c))
,di = (SELECT MIN(Depth) FROM TableA)
,ci = 1
,cdi = (SELECT MIN(Depth) FROM TableA)
,p.ID
,CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) + CASE WHEN g IS NULL THEN '' ELSE '(' END
+ ISNULL(g,'') + CASE WHEN g IS NULL THEN '' ELSE ')' END y
FROM TableA p
LEFT JOIN TableA c ON (c.ParentID = p.ID)
CROSS APPLY (SELECT SPACE(1) + CAST(c2.ID AS varchar(max)) + ColA + SPACE(1)
FROM TableA c2 WHERE ParentID = p.ID
ORDER BY Priority
FOR XML PATH(''))f(g)
)a
UNION ALL
SELECT r.i, r.Depth, r.maxd, r.mind, r.maxc, r.di, r.ci, r.cdi, r.ID
,CASE WHEN di = cdi
THEN REPLACE(r.y,LEFT(r.z,CHARINDEX(SPACE(1),r.z,2)), r.z)
ELSE r.y END [y]
,r.z
FROM(
SELECT i = i + 1
,Depth
,[maxd]
,[mind]
,[maxc]
,CASE WHEN ci = maxc AND cdi = maxd
THEN di + 1
ELSE di
END [di]
,CASE WHEN cdi = [maxd]
THEN CASE WHEN ci + 1 > maxc
THEN 1
ELSE ci + 1
END
ELSE ci
END [ci]
,CASE WHEN cdi + 1 > maxd
THEN mind
ELSE cdi + 1
END [cdi]
,id,y
,CAST(ISNULL((SELECT y FROM(
SELECT p.Depth,p.ID
,SPACE(1) + CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) +
CASE WHEN g IS NULL THEN '' ELSE '(' END + ISNULL(g,'')
+ CASE WHEN g IS NULL THEN '' ELSE ')' END y
,r1 = DENSE_RANK() OVER(ORDER BY p.ID) --child number
,r2 = ROW_NUMBER() OVER(PARTITION BY p.ID ORDER BY p.ID) --DISTINCT not allowed in recursive section
FROM TableA p
JOIN TableA c ON (c.ParentID = p.ID)
CROSS APPLY (SELECT SPACE(1)+CAST(c2.ID AS varchar(max))+ColA+SPACE(1)
FROM TableA c2
WHERE ParentID = p.ID
ORDER BY Priority
FOR XML PATH(''))f(g)
WHERE p.Depth = cdi AND cdi < di AND p.ID <> cte.ID
)v
WHERE r1 = ci
AND r2 = 1
AND cte.y LIKE '%' + LEFT(v.y,CHARINDEX(SPACE(1),v.y,2) ) + '%'),'') AS varchar(max)) z
FROM cte
WHERE [di]<[maxd] or [ci]<[maxc] or [cdi]<[maxd]
)r
)--cte
UPDATE t
SET ColA = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
(y,SPACE(1),''),'1',''),'2',''),'3',''),'4',''),'5',''),'6',''),'7',''),'8',''),'9',''),'0','')
FROM cte
JOIN TableA t ON (t.ID = cte.ID)
WHERE di = (SELECT MAX(Depth) FROM TableA)
AND cdi = (SELECT MAX(Depth) FROM TableA)
AND ci = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c))
OPTION(maxrecursion 0)
SELECT * FROM TableA
DROP TABLE TableA
JMTyler-
1 What kind of data is in ColA? What does it look like?
2 How is/should that column be originally populated? I ask this because you would only be able to run the update once since the value in that column would be modified from a previous run. Any additional runs would just concatenate more data. Which makes me believe there is another ColC with the original value for ColA (a person's name?)
3 Will a row ever be deleted orphaning it's children? If yes what should their ParentColB then point to? NULL? Does their depth then get set to 0 so they are now at the top of the hierarchy?
If you can answer this I can give you a solution
Thanks

Resources