How to develop a recursive CTE in T-SQL? - sql-server

I am new to recursive CTEs. I am trying to develop a CTE which will return all of the employees under each manager name. So I have two tables: people_rv and staff_rv
People_rv table contains all of the people, both managers and employees. Staff_rv only contains manager information. Uniqueidentifier staff values are stored in Staff_rv. Uniqueidentifier employee values are stored in people_rv. People_rv contains varchar first and last name values for both managers and employees.
But when I run the following CTE I get an error:
WITH
cteStaff (ClientID, FirstName, LastName, SupervisorID, EmpLevel)
AS
(
SELECT p.people_id, p.first_name, p.last_name, s.supervisor_id,1
FROM people_rv p JOIN staff_rv s on s.people_id = p.people_id
WHERE s.supervisor_id = '95E16819-8C3A-4098-9430-08F0E3B764E1'
UNION ALL
SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
INNER JOIN cteStaff r on s2.staff_id = r.ClientID
)
SELECT
FirstName + ' ' + LastName AS FullName,
EmpLevel,
(SELECT first_name + ' ' + last_name FROM people_rv p join staff_rv s on s.people_id = p.people_id
WHERE s.staff_id = cteStaff.SupervisorID) AS Manager
FROM cteStaff
OPTION (MAXRECURSION 0);
My output is:
Barbara G 1 Melanie K
Dawn P 1 Melanie K
Garrett M 1 Melanie K
Stephanie P 1 Melanie K
Amanda F 1 Melanie K
Amanda T 1 Melanie K
Stephanie G 1 Melanie K
Carlos H 1 Melanie K
So it is not iterating any more than the first level. Why not?
Melanie is the top most supervisor, but each of the persons in the leftmost column are also supervisors. So this query should also return level 2.

You may be in an infinite loop with your join. I would check how many levels you expect the table to actually go down. Generally you join a recursion on something similar to do
ID = ParentID
of something either contained in a table or in an expression. Keep in mind you can also create a CTE prior to a recursive CTE if you have to make up your relationship.
Here is an example that will self execute, it may help.
Declare #table table ( PersonId int identity, PersonName varchar(512), Account int, ParentId int, Orders int);
insert into #Table values ('Brett', 1, NULL, 1000),('John', 1, 1, 100),('James', 1, 1, 200),('Beth', 1, 2, 300),('John2', 2, 4, 400);
select
PersonID
, PersonName
, Account
, ParentID
from #Table
; with recursion as
(
select
t1.PersonID
, t1.PersonName
, t1.Account
--, t1.ParentID
, cast(isnull(t2.PersonName, '')
+ Case when t2.PersonName is not null then '\' + t1.PersonName else t1.PersonName end
as varchar(255)) as fullheirarchy
, 1 as pos
, cast(t1.orders +
isnull(t2.orders,0) -- if the parent has no orders than zero
as int) as Orders
from #Table t1
left join #Table t2 on t1.ParentId = t2.PersonId
union all
select
t.PersonID
, t.PersonName
, t.Account
--, t.ParentID
, cast(r.fullheirarchy + '\' + t.PersonName as varchar(255))
, pos + 1 -- increases
, r.orders + t.orders
from #Table t
join recursion r on t.ParentId = r.PersonId
)
, b as
(
select *, max(pos) over(partition by PersonID) as maxrec -- I find the maximum occurrence of position by person
from recursion
)
select *
from b
where pos = maxrec -- finds the furthest down tree
-- and Account = 2 -- I could find just someone from a different department

Your problem as far as I can tell is is you have no join connecting managers to their employees.
This join
INNER JOIN cteStaff r on r.StaffID = s2.staff_id
Just joins the same initial level 1 staffer back to himself.
UPDATE:
Still not quite right! You have a supervisor_id, but again you're still not actually using that to join back to the CTE.
So for each recursion of this CTE you need to (excluding the name join):
select {Level 1 Boss}, NULL (no supervisor)
union
select {new employee}, {that employee's boss}
So the join must connect the CTE's ClientID (the level 1 boss) to the second UNION query's supervisor field, which looks to be supervisor_id , not staff_id.
The JOIN to accomplish this second task is (from what I can tell of your staff_rv table schema):
SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
INNER JOIN cteStaff r on s2.supervisor_id = r.ClientID
Note the bottom join joins the r.ClientID (the level 1 boss) to the staffer's supervisor_id field.
(NB: I think your staff_id and supervisor_id's mimic your people_id values from the people_rv table, so this join should work fine. But if they are different (i.e. a staffer's supervisor_id isn't that supervisor's people_id) then you'll need to write the join such that the staffer's supervisor_id can be joined to their people_id you're storing as ClientID in the CTE.)

Here's a good simple Recursive CTE to review (it may not be the answer, but someone else searching on how to make a recursive CTE may need it):
-- Recursive CTE
;
WITH Years ( myYear )
AS (
-- Base case
SELECT DATEPART(year, GETDATE())
UNION ALL
-- Recursive
SELECT Years.myYear - 1
FROM Years
WHERE Years.myYear >= 2002
)
SELECT *
FROM Years

Note that this probably won't solve your problem, but is a means to hopefully seeing where you're going wrong in the original query.
The default is 100 levels of recursion - you can set it to unlimited by using the MAXRECURSION query hint where you're selecting from your CTE:
...
FROM cteStaff
OPTION (MAXRECURSION 0);
From MSDN:
MAXRECURSION number
Specifies the maximum number of recursions allowed for this query. number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied. If this option is not specified, the
default limit for the server is 100.
When the specified or default number for MAXRECURSION limit is reached during query execution, the query is ended and an error is
returned.
Because of this error, all effects of the statement are rolled back. If the statement is a SELECT statement, partial results or no
results may be returned. Any partial results returned may not include
all rows on recursion levels beyond the specified maximum recursion
level.

Related

Getting non-deterministic results from WITH RECURSIVE cte

I'm trying to create a recursive CTE that traverses all the records for a given ID, and does some operations between ordered records. Let's say I have customers at a bank who get charged a uniquely identifiable fee, and a customer can pay that fee in any number of installments:
WITH recursive payments (
id
, index
, fees_paid
, fees_owed
)
AS (
SELECT id
, index
, fees_paid
, fee_charged
FROM table
WHERE index = 1
UNION ALL
SELECT t.id
, t.index
, t.fees_paid
, p.fees_owed - p.fees_paid
FROM table t
JOIN payments p
ON t.id = p.id
AND t.index = p.index + 1
)
SELECT *
FROM payments
ORDER BY 1,2;
The join logic seems sound, but when I join the output of this query to the source table, I'm getting non-deterministic and incorrect results.
This is my first foray into Snowflake's recursive CTEs. What am I missing in the intermediate result logic that is leading to the non-determinism here?
I assume this is edited code, because in the anchor of you CTE you select the fourth column fee_charged which does not exist, and then in the recursion you don't sum the fees paid and other stuff, basically you logic seems rather strange.
So creating some random data, that has two different id streams to recurse over:
create or replace table data (id number, index number, val text);
insert into data
select * from values (1,1,'a'),(2,1,'b')
,(1,2,'c'), (2,2,'d')
,(1,3,'e'), (2,3,'f')
v(id, index, val);
Now altering you CTE just a little bit to concat that strings together..
WITH RECURSIVE payments AS
(
SELECT id
, index
, val
FROM data
WHERE index = 1
UNION ALL
SELECT t.id
, t.index
, p.val || t.val as val
FROM data t
JOIN payments p
ON t.id = p.id
AND t.index = p.index + 1
)
SELECT *
FROM payments
ORDER BY 1,2;
we get:
ID INDEX VAL
1 1 a
1 2 ac
1 3 ace
2 1 b
2 2 bd
2 3 bdf
Which is exactly as I would expect. So how this relates to your "it gets strange when I join to other stuff" is ether, your output of you CTE is not how you expect it to be.. Or your join to other stuff is not working as you expect, Or there is a bug with snowflake.
Which all comes down to, if the CTE results are exactly what you expect, create a table and join that to your other table, so eliminate some form of CTE vs JOIN bug, and to debug why your join is not working.
But if your CTE output is not what you expect, then lets help debug that.

Issue while using 'case when ' in 'where' clause sql server

While using case when in where clause in sql query it's not working.
Problem :
I have two tables named TblEmployee and TblAssociate.Both tables contains common columns PeriodId, EmpId and AssociateId. My requirement is to fetch values from
TblEmployee with combination of EmpId and AssociateId from TblAssociate should be excluded.And the exclusion should be based on PeriodId condition.`
If(#PeriodID<50)
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId+AssociateId) NOT IN (SELECT EmpId+AssociateId FROM TblAssociate)
END
ELSE
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId) NOT IN (SELECT EmpId FROM TblAssociate)
END
The above code is working, but I need to avoid that IF-ELSE condition and I wish to use 'case when' in where clause.Please help
Try this:
SELECT *
FROM TblEmployee
WHERE (EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END) NOT IN
(SELECT EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END FROM TblAssociate)
You say your code is working but this is rather odd, since it doesn't make much sense to add together id values. In any case, the above statement produces a result that is equivalent to the code originally posted.
You could use AND-OR combination in the WHERE clause. Additionally, you should not be using + as it may lead to incorrect result. You can rewrite your query as:
SELECT e.*
FROM TblEmployee e
WHERE
(
#PeriodID < 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE
a.EmpId = e.EmpId
AND a.AssociateId = e.AssociateId
)
)
OR
(
#PeriodID >= 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE a.EmpId = e.EmpId
)
)
The addition of IDs do not guarantee uniqueness. For instance, if EmpId is 5 and AssociateId is 6, then EmpId + AssociateId = 11, while EmpId + AssociateId = 11 even if EmpId is 6 and AssociateId is 5. In the query below, I made sure that the subquery will stop searching when the first record is found and will return a single record, having the value of 1. We select the employee if and only if 1 is among the results. In the subquery we check the operand we are sure of first and then check if we are not in a period where AssociateId must be checked, or it matches.
select *
from TblEmployee
where 1 in (select top 1 1
from TblAssociate
where TblEmployee.EmpId = TblAssociate.EmpId and
(#PeriodID >= 50 or TblEmployee.AssociateId = TblAssociate.AssociateId))

I need all the employee under a supervisor

i have a below described situation
EmpID Name SupervisorID
1 A 9
2 B 8
3 C 1
4 D 3
I need all the employees under supervisor ID 1
Here EmployeeID 3 is under 1 i need 4 is is also under 3
Following output is required.
EmpID Name SupervisorID
3 C 1
4 D 3
You need to Use Recursive CTE for this.Try this,
With CTE as
(
select EmpID,Name,SupervisorID from Emp
where SupervisorID =1
Union All
select a.EmpID,a.Name,a.SupervisorID from Emp as a
inner join CTE b on a.SupervisorID= b.EmpID
)
select * from CTE
Fiddle Demo Here
Please take a look at this question also, it is same like your question. Sql server CTE and recursion example
Nobody expects the Spanish Inquisition a HierarchyId. These days, whenever I see an org structure (like the one you have here), I reach for the HierarchyId datatype. In essence, it allows you to answer questions like "which value(s) are 'under' this one?" and "which value(s) does this one belong to?". Here's how I'd implement it:
alter table dbo.Employee add OrgStructure HierarchyId null;
with h as (
select EmployeeId, SupervisorId, '/' + cast(EmployeeId as varchar) + '/' as h
from dbo.Employee as e
where e.SupervisorId is null --employees w/o a supervisor
union all
select e.EmployeeId, e.SupervisorId, h.h + '/' + cast(EmployeeId as varchar) + '/'
from dbo.Employee as e
join h
on e.SupervisorId = h.SupervisorId
)
update e
set OrgStructure = h.h
from dbo.Employee as e
join h
on e.EmployeeId = h.EmployeeId;
create index [IX_Employee_OrgStructure] on dbo.Employee (OrgStructure)
Now that the heavy lifting is done, actually answering your problem is trivial:
select *
from dbo.Employee as supervisor
join dbo.Employee as reports
on reports.OrgStructure.IsDescendantOf(supervisor.OrgStructure)
where supervisor.EmployeeId = 1
The advantage that I see is that you're not calculating the hierarchy on the fly every time you need to answer this type of question. You do it once and you're done.

SQL Server: Joining in rows via. comma separated field

I'm trying to extract some data from a third party system which uses an SQL Server database. The DB structure looks something like this:
Order
OrderID OrderNumber
1 OX101
2 OX102
OrderItem
OrderItemID OrderID OptionCodes
1 1 12,14,15
2 1 14
3 2 15
Option
OptionID Description
12 Batteries
14 Gift wrap
15 Case
[etc.]
What I want is one row per order item that includes a concatenated field with each option description. So something like this:
OrderItemID OrderNumber Options
1 OX101 Batteries\nGift Wrap\nCase
2 OX101 Gift Wrap
3 OX102 Case
Of course this is complicated by the fact that the options are a comma separated string field instead of a proper lookup table. So I need to split this up by comma in order to join in the options table, and then concat the result back into one field.
At first I tried creating a function which splits out the option data by comma and returns this as a table. Although I was able to join the result of this function with the options table, I wasn't able to pass the OptionCodes column to the function in the join, as it only seemed to work with declared variables or hard-coded values.
Can someone point me in the right direction?
I would use a splitting function (here's an example) to get individual values and keep them in a CTE. Then you can join the CTE to your table called "Option".
SELECT * INTO #Order
FROM (
SELECT 1 OrderID, 'OX101' OrderNumber UNION SELECT 2, 'OX102'
) X;
SELECT * INTO #OrderItem
FROM (
SELECT 1 OrderItemID, 1 OrderID, '12,14,15' OptionCodes
UNION
SELECT 2, 1, '14'
UNION
SELECT 3, 2, '15'
) X;
SELECT * INTO #Option
FROM (
SELECT 12 OptionID, 'Batteries' Description
UNION
SELECT 14, 'Gift Wrap'
UNION
SELECT 15, 'Case'
) X;
WITH N AS (
SELECT I.OrderID, I.OrderItemID, X.items OptionCode
FROM #OrderItem I CROSS APPLY dbo.Split(OptionCodes, ',') X
)
SELECT Q.OrderItemID, Q.OrderNumber,
CONVERT(NVarChar(1000), (
SELECT T.Description + ','
FROM N INNER JOIN #Option T ON N.OptionCode = T.OptionID
WHERE N.OrderItemID = Q.OrderItemID
FOR XML PATH(''))
) Options
FROM (
SELECT N.OrderItemID, O.OrderNumber
FROM #Order O INNER JOIN N ON O.OrderID = N.OrderID
GROUP BY N.OrderItemID, O.OrderNumber) Q
DROP TABLE #Order;
DROP TABLE #OrderItem;
DROP TABLE #Option;

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