SQL Query Peers and above - sql

There are recursive queries available to find all children or parents in a hierarchy (for example in an employee-manager hierarchy) but I couldn't find any example of peers and above/parents
EmpID FName LName MgrID
11 Sally Smith NULL
1 Alex Adams 11
2 Barry Brown 11
3 Lee Osaka 11
4 David Kennson 11
5 Eric Bender 11
7 David Lonning 11
6 Lisa Kendall 4
8 John Marshbank 4
12 Barbara ONeill 4
13 Phil Wilconkinski 4
9 James Newton 3
10 Terry OHaire 3
So if I want peer-above of EmpID=6 then it should return
EmpID FName LName MgrID
11 Sally Smith NULL grandparent/peer
1 Alex Adams 11 parent-peer
2 Barry Brown 11 parent-peer
3 Lee Osaka 11 parent-peer
4 David Kennson 11 (parent of id=6)
5 Eric Bender 11 parent-peer
7 David Lonning 11 parent-peer
6 Lisa Kendall 4 (id=6)
8 John Marshbank 4 peer
12 Barbara ONeill 4 peer
13 Phil Wilconkinski 4 peer
Any help will be very appreciated

With the way your table is currently structured, one way of achieving this is to include a path in your recursive CTE, then parse the path to figure out whether someone belongs to that path in the hierarchy.
For example,
DECLARE #Table TABLE
(
EmpID INT NOT NULL,
FName VARCHAR(20) NOT NULL,
LName VARCHAR(20) NOT NULL,
MgrID INT
);
INSERT #Table (EmpID, FName, LName, MgrID)
VALUES
(11, 'Sally', 'Smith', NULL),
(1, 'Alex', 'Adams', 11),
(2, 'Barry', 'Brown', 11),
(3, 'Lee', 'Osaka', 11),
(4, 'David', 'Kennson', 11),
(5, 'Eric', 'Bender', 11),
(7, 'David', 'Lonning', 11),
(6, 'Lisa', 'Kendall', 4),
(8, 'John', 'Marshbank', 4),
(12, 'Barbara', 'ONeill', 4),
(13, 'Phil', 'Wilconkinski', 4),
(9, 'James', 'Newton', 3),
(10, 'Terry', 'OHaire', 3);
WITH CTE AS
(
SELECT EmpID,
FName,
LName,
MgrID,
Tree = CAST(EmpID AS VARCHAR(255)),
Lvl = 1
FROM #Table
WHERE MgrID IS NULL
UNION ALL
SELECT T.EmpID,
T.FName,
T.LName,
T.MgrID,
CAST(CTE.Tree + '-' + CAST(T.EmpID AS VARCHAR(255)) AS VARCHAR(255)), -- Your paths will look like '11-4-6'.
CTE.Lvl + 1
FROM CTE
JOIN #Table T ON T.MgrID = CTE.EmpID
)
SELECT EmpID,
FName,
LName,
MgrID,
Lvl,
Direct = CASE MIN(CASE WHEN EmpID = Item THEN 1 ELSE 2 END) WHEN 1 THEN 'Direct' ELSE 'Peer' END
FROM CTE
JOIN
(
SELECT Item
FROM CTE
CROSS APPLY dbo.[DelimitedSplit8K](Tree, '-') D
WHERE CTE.EmpID = 6
) D ON D.Item = CTE.EmpID
OR D.Item = CTE.MgrID
GROUP BY EmpID, FName, LName, MgrID, Lvl;
This uses a split function to break up the path and only display people in that path. The split function can be found here and looks like this:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;

Related

Get all subordinates based on line manager marker

I have an employees table that holds an employeeID, Name...etc.... and intLineManager. intLine manager is the employeeID of your line manager. Something like this, with Dave being the top boss with no manager.
intEmpID |Name|intLineManager
1 |Dave| NULL
2 |Sue |1
3 |Mike|1
4 |Matt|2
5 |Sara|3
6 |Paul|4
I'm looking for an SQL script that will return all employees that are underneath a person.
So a search on Mike will return Sara, a search on Sue will return Matt + Paul and finally a search on Dave will return everyone.
You can use a recursive CTE like so:
CREATE TABLE #Employees(EmpID int, Name varchar(10), LineManager int NULL)
INSERT INTO #Employees VALUES
(1, 'Dave', NULL),
(2, 'Sue', 1),
(3, 'Mike', 1),
(4, 'Matt', 2),
(5, 'Sara', 3),
(6, 'Paul', 4)
;WITH CTE as
(
SELECT EmpID, Name, LineManager
FROM #Employees
WHERE EmpID = 2 --Sue
UNION ALL
SELECT e.EmpID, e.Name, e.LineManager
FROM #Employees AS E
INNER JOIN CTE AS C ON c.EmpID = e.LineManager
)
SELECT *
FROM CTE
where EmpID > (SELECT TOP 1 EmpID FROM CTE)
ORDER BY EmpID
Result for sue(EmpID 2)
EmpID Name LineManager
----------- ---------- -----------
4 Matt 2
6 Paul 4

Selecting rows when there is a change in the value of column from the previous row

I have the following table:-
Name Status Timestamp
Ben 1 2015-01-01
Ben 1 2015-01-02
Joe 1 2015-11-12
Joe 2 2015-11-13
Joe 2 2016-12-14
Joe 2 2016-12-15
Paul 1 2015-08-16
Paul 1 2015-08-17
Paul 3 2015-08-18
Paul 3 2015-08-19
Mark 2 2015-09-20
Mark 2 2015-09-25
Mark 2 2015-09-26
Mark 3 2015-10-27
I need a query that returns only the rows where there is a change in the 'Status'. It should return the row when the 'Status' is changed and also the previous row.
For instance the result should be like the below:-
Name Status Timestamp
Joe 1 2015-11-12
Joe 2 2015-11-13
Paul 1 2015-08-17
Paul 3 2015-08-18
Mark 2 2015-09-26
Mark 3 2015-10-27
How can I achieve this result.
You can use a CTE with a CASE and LAG and LEAD to calculate what rows to select. this will work for versions 2012 and higher:
Create and populate sample table (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Name varchar(4),
[Status] int,
[Timestamp] date
)
INSERT INTO #T VALUES
('Joe', 1, '2015-11-12'),
('Joe', 2, '2015-11-13'),
('Joe', 2, '2016-12-14'),
('Joe', 2, '2016-12-15'),
('Paul' ,1, '2015-08-16'),
('Paul' ,1, '2015-08-17'),
('Paul' ,3, '2015-08-18'),
('Paul' ,3, '2015-08-19'),
('Mark' ,2, '2015-09-20'),
('Mark' ,2, '2015-09-25'),
('Mark' ,2, '2015-09-26'),
('Mark' ,3, '2015-10-27')
The cte - Note that I use both lag and lead inside the case expression.
;WITH CTE AS
(
SELECT Name,
[Status],
[Timestamp],
CASE WHEN LAG([Status]) OVER(PARTITION BY Name ORDER BY [Timestamp]) <> [Status] OR
LEAD([Status]) OVER(PARTITION BY Name ORDER BY [Timestamp]) <> [Status] THEN
1
END As Filter
FROM #T
)
The query:
SELECT Name,
[Status],
[Timestamp]
FROM CTE
WHERE Filter = 1
Results:
Name Status Timestamp
Joe 1 12.11.2015 00:00:00
Joe 2 13.11.2015 00:00:00
Mark 2 26.09.2015 00:00:00
Mark 3 27.10.2015 00:00:00
Paul 1 17.08.2015 00:00:00
Paul 3 18.08.2015 00:00:00
See a live demo on rextester
This can be user on versions starting with 2005:
declare #t table (Name varchar(100), S int, T date);
insert into #t values
('Joe', 1 ,'2015-11-12'),
('Joe', 2 ,'2015-11-13'),
('Joe', 2 ,'2016-12-14'),
('Joe', 2 ,'2016-12-15'),
('Paul', 1 ,'2015-08-16'),
('Paul', 1 ,'2015-08-17'),
('Paul', 3 ,'2015-08-18'),
('Paul', 3 ,'2015-08-19'),
('Mark', 2 ,'2015-09-20'),
('Mark', 2 ,'2015-09-25'),
('Mark', 2 ,'2015-09-26'),
('Mark', 3 ,'2015-10-27');
with cte as
(
select *, ROW_NUMBER() over(partition by Name order by T) as rn
from #t
)
,cte1 as
(
select c1.Name, c1.S as S1, c1.T as T1, c2.S as S2, c2.T as T2
from cte c1 join cte c2
on c1.rn + 1 = c2.rn
and c1.Name = c2.Name
where c1.S <> c2.S
)
select Name,
case n
when 1 then S1
when 2 then S2
end as Status,
case n
when 1 then T1
when 2 then T2
end as Timestamp
from cte1 cross join (select 1 n union all select 2) nums;
And this is the same but for versions starting with 2012:
declare #t table (Name varchar(100), S int, T date);
insert into #t values
('Joe', 1 ,'2015-11-12'),
('Joe', 2 ,'2015-11-13'),
('Joe', 2 ,'2016-12-14'),
('Joe', 2 ,'2016-12-15'),
('Paul', 1 ,'2015-08-16'),
('Paul', 1 ,'2015-08-17'),
('Paul', 3 ,'2015-08-18'),
('Paul', 3 ,'2015-08-19'),
('Mark', 2 ,'2015-09-20'),
('Mark', 2 ,'2015-09-25'),
('Mark', 2 ,'2015-09-26'),
('Mark', 3 ,'2015-10-27');
with cte as
(
select name,
S as S1,
lead(S) over(partition by Name order by T) S2,
T as T1,
lead(T) over(partition by Name order by T) T2
from #t
)
,cte1 as
(
select *
from cte
where S1 <> S2
)
select Name,
case n
when 1 then S1
when 2 then S2
end as Status,
case n
when 1 then T1
when 2 then T2
end as Timestamp
from cte1 cross join (select 1 n union all select 2) nums;

Get the total Count of down line record using SQL Server CTE

How to Sum all down-line to their immediate up-line using SQL.
All Staff Sales are the Sales of Supervisor.
All Supervisor Sales are the Sales of Manager.
All Manager Sales are the Sales of Head.
Current data:
ID Name Role Level Reportingto Sales
---------------------------------------------------------------
1 Joe Head 1 NULL 0
2 Smith Manager 2 1 0
3 Mike Supervisor 3 2 0
4 Mitch Staff 4 3 10
5 Jen Staff 4 3 20
6 Ian Manager 2 1 0
7 Jess Supervisor 3 6 0
8 Rocky Staff 4 7 5
9 Jessica Supervisor 3 6 0
10 Rolly Staff 4 9 3
Ideal output
ID Name Role Level Reportingto Sales
---------------------------------------------------------------
1 Joe Head 1 NULL 38
2 Smith Manager 2 1 30
3 Mike Supervisor 3 2 30
4 Mitch Staff 4 3 10
5 Jen Staff 4 3 20
6 Ian Manager 2 1 8
7 Jess Supervisor 3 6 5
8 Rocky Staff 4 7 5
9 Jessica Supervisor 3 6 3
10 Rolly Staff 4 9 3
I'm using SQL Server 2016
Declare #YourTable table (ID int,Name varchar(25),Role varchar(25),Level int,Reportingto int,Sales int)
Insert into #YourTable values
(1 ,'Joe' ,'Head' ,1 ,NULL ,0),
(2 ,'Smith' ,'Manager' ,2 ,1 ,0),
(3 ,'Mike' ,'Supervisor' ,3 ,2 ,0),
(4 ,'Mitch' ,'Staff' ,4 ,3 ,10),
(5 ,'Jen' ,'Staff' ,4 ,3 ,20),
(6 ,'Ian' ,'Manager' ,2 ,1 ,0),
(7 ,'Jess' ,'Supervisor' ,3 ,6 ,0),
(8 ,'Rocky' ,'Staff' ,4 ,7 ,5),
(9 ,'Jessica' ,'Supervisor' ,3 ,6 ,0),
(10 ,'Rolly' ,'Staff' ,4 ,9 ,3)
Declare #Top int = null --<< Sets top of Hier Try 6
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Name) as varchar(500))
,ID
,Reportingto
,Lvl=1
,Name
,Role
,Sales
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(Reportingto ,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Name)) as varchar(500))
,r.ID
,r.Reportingto
,p.Lvl+1
,r.Name
,r.Role
,r.Sales
From #YourTable r
Join cteP p on r.Reportingto = p.ID)
Select A.ID
,A.Name
,A.Role
,A.Lvl
,A.Reportingto
,Sales = (Select sum(Sales) from cteP where Seq Like A.Seq+'%')
From cteP A
Order By A.Seq
Returns
You can use outer apply.
select t1.id,t1.name,t1.role,t1.level,t1.reportingto,coalesce(t2.sales,t1.sales) as val
from t t1
outer apply (select sum(sales) as sales
from t t2
where t1.id<=t2.id and t1.level<t2.level) t2
There might be a simpler way to do this, but it seems to me like you need to start at the 'staff' and recurse up:
;with cte as (SELECT [ID], [Name], [Role], [Level], [Reportingto], [Sales], Sales as Sum_Sales
FROM #Table1
WHERE [Role] = 'Staff'
UNION ALL
SELECT a.[ID], a.[Name], a.[Role], a.[Level], a.[Reportingto], a.[Sales], a.Sales + b.Sum_Sales AS Sum_Sales
FROM #Table1 a
JOIN cte b
ON b.Reportingto = a.ID
)
SELECT [ID], [Name], [Role], [Level], [Reportingto],SUM(Sum_Sales) as Sales
FROM cte
GROUP BY [ID], [Name], [Role], [Level], [Reportingto]
Assuming the table as KTemp I created a new column. Hope this helps.
SELECT *,
CASE
WHEN LEVEL=1 THEN
(SELECT sum(Sales)
FROM KTemp x
WHERE x.Level IN (1,
2,
3,
4) )
WHEN LEVEL=2 THEN
(SELECT sum(Sales)
FROM KTemp x
WHERE x.Level IN (2,
3,
4) )
WHEN LEVEL=3 THEN
(SELECT sum(Sales)
FROM KTemp x
WHERE x.Level IN (3,
4) )
WHEN LEVEL=4 THEN
(SELECT sum(Sales)
FROM KTemp x
WHERE x.Level IN (4) )
END AS [Up-line Sales]
FROM KTemp

Display order of a SQL Query without order by clause

I am writing a simple query in SQL Server:
Select
EmpId, EmpName, Sal
from
Emp
where
EmpId in (10,9,5,7,3,8);
I want to get the output in a same order which is given i.e; 10,9,5,7,3,8
Actually whatever I'll give the result will display with given order without
order by ascending or descending.
How can I do that? Please help.
No way to do this natively.
Try:
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (10,9,5,7,3,8)
ORDER BY CASE EmpId
WHEN 10 THEN 1
WHEN 9 THEN 2
WHEN 5 THEN 3
WHEN 7 THEN 4
WHEN 3 THEN 5
WHEN 8 THEN 6
ELSE 7
END;
You can use a table variable to pass the inputs. You must insert the records into this table variable in the desired order.
Declare #empids table(id int identity(1,1),empid int)
insert into #empids values(10),(9),(5),(7),(3),(8)
Select e.EmpId,e.empname,e.sal from Emp e
join #empids t on e.EmpId = t.empid
order by t.id
Try this.
You can use CHARINDEX function in an odd way: search for the id in the comma separated list and order the result by the position.
Consider this list for example 10,9,5,7,3,8... the substring 10 appears at 1st position while 9 appears at 4th. Just order by the substring position.
CREATE TABLE Emp
(EmpId int, EmpName varchar(100), Sal int)
;
INSERT INTO Emp
(EmpId, EmpName, Sal)
VALUES
(1, 'John', NULL),
(2, 'Jane', NULL),
(3, 'Smith', NULL),
(4, 'Doe', NULL),
(5, 'Ben', NULL),
(6, 'Steve', NULL),
(7, 'Andrew', NULL),
(8, 'Simon', NULL),
(9, 'Jack', NULL),
(10, 'Allen', NULL)
;
SELECT
EmpId, EmpName, Sal
FROM
Emp
WHERE
EmpId in (10,9,5,7,3,8)
ORDER BY
CHARINDEX(CONCAT(',', EmpId, ','), CONCAT(',', '10,9,5,7,3,8', ','))
;
Result:
EmpId | EmpName | Sal
------+---------+-----
10 | Allen | NULL
9 | Jack | NULL
5 | Ben | NULL
7 | Andrew | NULL
3 | Smith | NULL
8 | Simon | NULL
You can do this dynamically if your list is a comma-delimited-string. First, you must have a splitter function. Here is the DelimitedSplit8k written by Jeff Moden:
CREATE FUNCTION [dbo].[DelimitedSplit8K](
#pString VARCHAR(8000), #pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS (SELECT 1 FROM E1 a, E1 b)
,E4(N) AS (SELECT 1 FROM E2 a, E2 b)
,cteTally(N) AS(
SELECT TOP (ISNULL(DATALENGTH(#pString), 0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
,cteStart(N1) AS(
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString, t.N, 1) = #pDelimiter
),
cteLen(N1, L1) AS(
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter, #pString, s.N1),0) - s.N1, 8000)
FROM cteStart s
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
Then, you declare the list of empIds as csv string and use the splitter:
DECLARE #empIds VARCHAR(MAX) = '10,9,5,7,3,8';
SELECT e.EmpId, e.EmpName, e.Sal
FROM Emp e
INNER JOIN dbo.DelimitedSplit8K(#empIds, ',') s
ON s.Item = l.EmpId
ORDER BY s.ItemNumber
Following query will give you the exact result (without order by):
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (10)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (9)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (5)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (7)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (3)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (8)
If Ids is a variable passed as input parameter to a stored procedure, you can split it using CTE.
--#inputIDs VARCHAR(300) => '10,9,5,7,3,8'
;WITH MyIds AS
(
SELECT 1 AS Position, CONVERT(INT, LEFT(#inputIds, CHARINDEX(',', #inputIDs)-1)) AS MyId,
RIGHT(#inputIds, LEN(#inputIds) - CHARINDEX(',', #inputIDs)) AS Remainder
WHERE CHARINDEX(',', #inputIDs)>0
UNION ALL
SELECT Position +1 AS Position, CONVERT(INT, LEFT(Remainder, CHARINDEX(',', Remainder)-1)) AS MyId,
RIGHT(Remainder, LEN(Remainder) - CHARINDEX(',', Remainder)) AS Remainder
FROM MyIds
WHERE CHARINDEX(',', Remainder)>0
UNION ALL
SELECT Position +1 AS Position, CONVERT(INT, Remainder) AS MyId,
NULL AS Remainder
FROM MyIds
WHERE CHARINDEX(',', Remainder)=0
)
SELECT e.EmpId, e.EmpName, e.Sal
FROM Emp AS e INNER JOIN MyIds AS a ON e.EmpId = a.MyId
ORDER BY a.Position
The simplest way to achieve this is probably to use a table-valued constructor as a table expression containing the ids with a sort value, then order on this value:
Select
Emp.EmpId, Emp.EmpName, Emp.Sal
from
Emp
Inner Join
(
Values
(10, 1),
(9, 2),
(5, 3),
(7, 4),
(3, 5),
(8, 6)
)
EmpIds (EmpId, Sort)
On
Emp.EmpId = EmpIds.EmpId
Order By
EmpIds.Sort Asc

Need a query to insert 'level' into an adjacent list

I have a table like so
ID Node ParentID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 4
10 J 4
11 K 10
12 L 11
I need a query to generate a 'level' field that shows how many levels deep a particular node is. Example below
ID Node ParentID Level
1 A 0 1
2 B 1 2
3 C 1 2
4 D 2 3
5 E 2 3
6 F 3 4
7 G 3 4
8 H 3 4
9 I 4 5
10 J 4 5
11 K 10 6
12 L 11 7
Select Id,
Node,
ParentID,
Dense_Rank() Over(Order by ParentID) as Level
from Table_Name
SQL Fiddle Demo
You can use DENSE_RANK function
SELECT i.ID, p.Node, i.ParentID
,Dense_Rank() Over(Order by ParentID) as Level
FROM TableName AS i;
for more detail visit: http://blog.sqlauthority.com/2007/10/09/sql-server-2005-sample-example-of-ranking-functions-row_number-rank-dense_rank-ntile/
I think the correct way to do it will be to get the parent level and increment it by 1 when inserting the data since all other ways are expensive performance wise.
Something like:
;with tree (ID, ParentID, Level)
as (
select ID, ParentID, 1 from TableName where ParentID = 0
union all
select t.ID, t.ParentID, 1 + tree.Level
from Tree join TableName t on t.ParentID = Tree.ID
)
select ID, Level from Tree
Try this
CREATE TABLE #Table1
([ID] int, [Node] varchar(1), [ParentID] int)
;
INSERT INTO #Table1
([ID], [Node], [ParentID])
VALUES
(1, 'A', 0),
(2, 'B', 1),
(3, 'C', 1),
(4, 'D', 2),
(5, 'E', 2),
(6, 'F', 3),
(7, 'G', 3),
(8, 'H', 3),
(9, 'I', 4),
(10, 'J', 4),
(11, 'K', 10),
(12, 'L', 11)
;
;WITH CTE ([ID], [ParentID], [Node], [Level])
as (
SELECT [ID], [ParentID], [Node], 1 FROM #Table1 WHERE ParentID = 0
UNION all
select t.[ID], t.[ParentID], t.[Node], 1 + c.[Level]
from CTE c inner join #Table1 t ON t.[ParentID] = c.[ID]
)
select ID, [Node], [ParentID], [Level] from CTE
ORDER BY [Node]
DROP TABLE #Table1
Here, you need to set level by grouping ParentID then join both tables by ParentID.
WITH CTE (ParentID, Level)
AS (
SELECT ParentID
, Row_Number() OVER (ORDER BY ParentID) AS Level
FROM Table1
GROUP BY ParentID
)
SELECT t1.ID, t1.Node, t1.ParentID, CTE.Level
FROM Table1 t1
JOIN CTE ON t1.ParentID = CTE.ParentID;
See this SQLFiddle
Update: (for MySQL - just to help others)
To do the same in MySQL try to get row number like this:
SELECT t1.ID, t1.Node, t1.ParentID, Tbl.Level
FROM Table1 t1
JOIN
(
SELECT #Level:=#Level+1 AS Level , ParentID
FROM (SELECT DISTINCT ParentID FROM Table1) t
, (SELECT #Level:=0) r
ORDER BY ParentID
) Tbl
ON t1.ParentID = Tbl.ParentID;
See this SQLFiddle