I have a table which stores company information and their parent company in a regular hierarchical manner, with companyid, parentid and name.
I just learn CTE query in Sql Server and write this query
WITH tableR (ParentCompanyID, CompanyID, Levels)
AS
(
-- Anchor member definition
SELECT e.ParentCompanyID, e.CompanyID, 0 As Levels
FROM tblCompany AS e
WHERE ParentCompanyID in (9)
UNION ALL
-- Recursive member definition
SELECT e.ParentCompanyID, e.CompanyID, Levels + 1
FROM tblCompany AS e
INNER JOIN tableR AS d
ON e.ParentCompanyID = d.CompanyID
)
-- Statement that executes the CTE
SELECT tabler.Levels, tableR.CompanyID, (left('--------------', (tabler.Levels* 2)) + c.CompanyName) as CName,c.ParentCompanyID
FROM tableR Left join tblcompany c on tableR.CompanyId=c.CompanyID
This works fine, except that it first list the Child of ID=9, then it list 1st level child and then level 2 .. and so on, but what I need is to have Child data come just under their parent, so
L0
L1
L2
L1-1
L2-1
....
Is it possible to do? Because if not then I have to do it recursively in C# code I am using.
I try this as well
WITH tableR (ParentCompanyID, CompanyID, Levels, RowNumber)
AS
(
-- Anchor member definition
SELECT e.ParentCompanyID, e.CompanyID, 1 As Levels, CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)) as RowNumber
FROM tblCompany AS e
WHERE ParentCompanyID in (9)
UNION ALL
-- Recursive member definition
SELECT e.ParentCompanyID, e.CompanyID, Levels + 1, CAST(Concat(d.RowNumber, CAST((Row_Number() Over (Order by e.CompanyName) ) as VARCHAR(MAX)) ) as VARCHAR(MAX)) as RowNumber
FROM tblCompany AS e
INNER JOIN tableR AS d
ON e.ParentCompanyID = d.CompanyID
)
-- Statement that executes the CTE
SELECT tabler.Levels, RowNumber, tableR.CompanyID, (left('--------------', ((tabler.Levels - 1)* 2 )) + c.CompanyName) as CName,c.ParentCompanyID
FROM tableR Left join tblcompany c on tableR.CompanyId=c.CompanyID order by RowNumber
But it fails with if any Level has more than 9 records.
Try this solution:
DECLARE #Company TABLE
(
CompanyID INT PRIMARY KEY,
Name NVARCHAR(50) NOT NULL,
ParentCompanyID INT NULL
);
INSERT #Company (CompanyID,Name,ParentCompanyID)
VALUES
(8,N'Tomaten',NULL),
(9,N'NON ĂNŞI chars',NULL),
(10,N'Bananen',NULL),
(11,N'Child #1',9),
(12,N'Child #2',9),
(13,N'Child #1.1',11),
(14,N'Child #1.2',11);
DECLARE #ParentCompanyID INT = 9;
WITH RecComp
AS
(
SELECT crt.CompanyID,
crt.Name,
crt.ParentCompanyID,
1 AS Lvl,
N'/' + CONVERT(NVARCHAR(4000),crt.CompanyID) + N'/' AS CompanyNode_AsChar
FROM #Company crt
WHERE crt.ParentCompanyID = #ParentCompanyID
UNION ALL
SELECT cld.CompanyID,
cld.Name,
cld.ParentCompanyID,
prt.Lvl + 1,
prt.CompanyNode_AsChar + CONVERT(NVARCHAR(4000), cld.CompanyID) + N'/'
FROM RecComp prt -- parent
INNER JOIN #Company cld ON prt.CompanyID = cld.ParentCompanyID
)
SELECT *,
CONVERT(HIERARCHYID, CompanyNode_AsChar) AS CompanyNode
FROM RecComp
ORDER BY CompanyNode;
Results:
CompanyID Name ParentCompanyID Lvl CompanyNode_AsChar CompanyNode
--------- ---------- --------------- --- --------------------- -----------
11 Child #1 9 1 /11/ 0xAE
13 Child #1.1 11 2 /11/13/ 0xAF6C
14 Child #1.2 11 2 /11/14/ 0xAF74
12 Child #2 9 1 /12/ 0xB2
Note: SQL Azure Supports Hierarchyid Data Type
Well, the thing ios, you have no ORDER BY clause.
Why not at least try
-- Statement that executes the CTE
SELECT tabler.Levels, tableR.CompanyID, (left('--------------', (tabler.Levels* 2)) + c.CompanyName) as CName,c.ParentCompanyID
FROM tableR Left join tblcompany c on tableR.CompanyId=c.CompanyID
ORDER BY tableR.Levels
That said, the display/UI sectioin should probably be taken car of by the UI output, and not by your query.
This almost seems like you wish to diusplay it in a TreeView rather.
Okay, finally I found a solution to issue. In order to get critics and if I am right to help other here it is
WITH tableR (ParentCompanyID, CompanyID, Levels, RowNumber, RowNumber2)
AS
(
-- Anchor member definition
SELECT e.ParentCompanyID, e.CompanyID, 1 As Levels, CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)) as RowNumber,
CAST(
(Left('000', 3-Len(CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)))) + CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)))
As VARCHAR(MAX)
) AS RowNumber2
FROM tblCompany AS e
WHERE ParentCompanyID in (370)
UNION ALL
-- Recursive member definition
SELECT e.ParentCompanyID, e.CompanyID, Levels + 1, CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)) as RowNumber1,
CAST(
Concat(d.RowNumber2,
Left('000', 3-Len(CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx)))),
CAST((Row_Number() Over (Order by e.CompanyName) ) as Varchar(MAx))
) as VARCHAR(MAX)) as RowNumber2
FROM tblCompany AS e
INNER JOIN tableR AS d
ON e.ParentCompanyID = d.CompanyID
)
-- Statement that executes the CTE
SELECT tabler.Levels, RowNumber, RowNumber2, tableR.CompanyID, (left('--------------', ((tabler.Levels - 1)* 2 )) + c.CompanyName) as CName,c.ParentCompanyID
FROM tableR Left join tblcompany c on tableR.CompanyId=c.CompanyID order by RowNumber2, CName
Now, here is the explanation:
I add a ROW_NUMBER Function of SQL Server this simply add the counter to each row of query, so it add seperate counter to Anchor query and Recursive query.
But as we have to arrange them, in order I append Parent/Anchor query value to Child, so Anchor query goes 1, 2, 3.. but child goes 11, 12 ... 21 ...
Then I Cast them as String, because in String Order you will have 1, 2, 21, 3 rather than 1, 2, 3, 21 .. so that works fine for me.
Problem Known: it get bowled over when you hit Anchor query output > 10 rows, or infact any row more than 10 rows, as then anchor query give ID as 11, and child goes 111 and confuse the output.
Solution for above problem: I modify my query to use LEFT and append 000, so if I see that I can max of 100 child I put 3 zero if you see 4 then use 0000 and change 3 to 4 in query.
BUT: I strongly recommend answer above as that is the way to do it.
I would like to share this
if you want to order de data... alphabetic and Child data come just under their parent.. create a baseCTE, use row_number instead of CompanyID, call the Anchor query from Base CTE
BASE CTE
ROW_NUMBER() OVER ( PARTITION BY ParentCompanyID ORDER BY CompanyName) as [row_number]
Anchor query
N'/' + CONVERT(NVARCHAR(4000),[row_number]) + N'/' AS CompanyNode_AsChar
Recursive query
prt.CompanyNode_AsChar + CONVERT(NVARCHAR(4000), [row_number]) + N'/'
Related
I am trying to identify recursive/circular reference in my data for which I need recursive cte.
For example I have table that contains Product_ID and Inner_Product_ID. I want results when Product_ID A is inner to Product_ID B, which is inner to Product_ID C, which is inner to Product_ID A.
Sample data
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
1 2
3 4
Expected output
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
I have tried basic query with cte but not sure how to implement recursive cte for this problem:
;WITH RNCTE
AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY pr1.PRODUCT_ID
ORDER BY pr1.PRODUCT_ID
) rn
FROM
TableName pr1),
cte
AS ( SELECT *
FROM RNCTE
WHERE RNCTE.rn = 1
UNION ALL
SELECT *
FROM cte c
JOIN RNCTE r
ON r.PRODUCT_ID = c.PRODUCT_ID
AND r.rn = c.rn + 1)
SELECT *
FROM cte;
try this - it walks through the linked records, and finds if the 'walk' eventually terminates, or not. If it lasts for more than the number of records in the table, then it must be a loop. 'Efficient' I am not sure of that!
;WITH UCNT AS (SELECT count(0) c from products),
RNCTE
AS (SELECT 1 as Levle, Product_ID, INNER_PRODUCT_ID FROM Products
UNION ALL
SELECT levle + 1, P.Product_ID, P.INNER_PRODUCT_ID
FROM RNCTE R
JOIN Products P
ON P.PRODUCT_ID = R.INNER_PRODUCT_ID
WHERE levle <= (SELECT c + 2 FROM UCNT))
--when the recursion count levle exceeds the count of records in the table,
--we must have recursion, because
--termination has to otherwise occur. The most extreme case is
--that all records are linked, with termination
--after this, we have to be in a 'loop'
SELECT TOP 1 with ties * FROM RNCTE order by levle desc
option (maxrecursion 0)
I think you don't need to use CTE or RECUSRIVE CTE :
SELECT pr1.*
FROM TableName pr1
WHERE EXISTS (SELECT 1 FROM TableName pr2 WHERE pr2.INNER_PRODUCT_ID = pr1.PRODUCT_ID);
I have a table with hierarchical data:
This is a sample of data with id, parent id, name, code (which is sometimes not filled), level and isroot column. In real scenario there will be more levels than just 2 but now let's look at the simplified example.
What I need to do is to loop over all records and find rows where id is not filled at any level of the hierarchy:
Rows with id ranging should be returned from 6 till 10 because they do not have code filled in at any point of the hierarchy
Rows from 1 till 5 should not be returned because code was mentioned somewhere in the hierarchy.
How should one solve this problem with T-SQL?
The only solution that came to my mind was recursivity (cte or WHILE) but I what I was trying to implement was too complex and did not solve the problem.
Slightly different than #DhruvJoshi's answer, since it may be useful:
WITH recCTE AS
(
SELECT
id,
parent_id,
CASE WHEN CODE IS NOT NULL THEN 1 ELSE 0 END as code_check,
1 as depth,
CAST(id as VARCHAR(50)) as path
FROM table
WHERE isRootLevel = 1
UNION ALL
SELECT
table.id,
table.parent_id,
CASE WHEN CODE IS NOT NULL OR reccte.code_check = 1 THEN 1 ELSE 0 END,
depth + 1 as depth,
reccte.path + CAST(table.id AS varchar(10)) as path
FROM
recCTE
INNER JOIN table ON
recCTE.ID = table.parent_id
WHERE depth < 20 /*just in case you start cycling/endless looping*/
)
SELECT * FROM recCTE where code_check = 0 ORDER BY path, depth;
Here is another example, for anyone who may still struggling with hierarchical data (like me).
Say we got a following hierarchical structure:
CEO
|-- Sales Director
│ |-- Sales Manager 1
│ `-- Sales Manager 2
`-- Technical Director
|-- Product Manager
|-- R&D Team Lead
`-- QA Team Lead
Using recursive cte to get the level for each node:
with cte as (
select id, parentId, roleName, 1 as lvl from roles where id = 1 -- root node
union all
select r.id, r.parentId, r.roleName, cte.lvl+1 as lvl from roles r -- child nodes
inner join cte on cte.id = r.parentid
)
select * from cte;
To get the path for each node:
with cte as (
select id, roleName, cast(roleName as varchar(200)) as hierPath
from roles where id = 1
union all
select r.id, r.rolename, cast(cte.hierPath + ' / ' + r.rolename as varchar(200)) as hierPath
from roles r
inner join cte on cte.id = r.parentid
)
select * from cte;
Using row_number() and power() to get the sorted hierarchy tree result (parent followed by all of its children, then all the children of each one of those and so on):
with cte as (
select id, roleName, cast(roleName as varchar(200)) as hierPath, 1 as lvl,
row_number()over(partition by parentid order by roleName) / power(10.0,1) as sortNo
from roles where id = 1
union all
select r.id, r.rolename, cast(cte.hierPath + ' / ' + r.rolename as varchar(200)) as hierPath, cte.lvl+1 as lvl,
cte.sortNo + row_number()over(partition by r.parentid order by r.roleName) / power(10.0,cte.lvl+1) as sortNo
from roles r
inner join cte on cte.id = r.parentid
)
select * from cte
order by sortNo;
Setup test data:
create table roles (
id int not null,
parentId int,
roleName varchar(50) not null
);
insert into roles
(id, parentId, roleName)
values
(1, null, 'CEO'),
(2, 1, 'Sales Director'),
(3, 1, 'Technical Director'),
(4, 2, 'Sales Manager 1'),
(5, 2, 'Sales Manager 2'),
(6, 3, 'Product Manager'),
(7, 3, 'R&D Team Lead'),
(8, 3, 'QA Team Lead');
Query like this should work:
; with cte as
(
select id, parent_id,code,parent_id as RootId from tableT where IsRootLevel=1
UNION ALL
select T2.id,T2.parent_id,T2.code,T1.RootId as RootId from tableT T2 join
cte T1 on T1.id=T2.parent_id and IsRootLevel=0
)
,
cte2 as
(select id,MAX(case when code ='' then NULL else code end) over( partition by RootId) as code from cte)
select T1.* from tableT T1 left join cte2 T2
on T1.id=T2.id
where T2.code is NULL
See working demo
I have a hierarchical data structure stored using materialized paths.
Table:Files
node parentNode name path
100 NULL f1 /f1/
101 100 f2 /f1/f2/
102 101 f3 /f1/f2/f3/
I have the node column as primary key(clustered)
Now if I want to find the ancestors of f3, given the path, I do something like this:
SELECT * FROM Files WHERE '/f1/f2/f3/' LIKE [path] + '%'
The problem with this is, the execution plan uses a clustered index scan( which I think SQL server defaults to for table scans)
Is there anyway I can find the ancestors of a node, given the path in a more efficient manner, preferably not using a CTE? I also have a depth column at my disposal if required.
If you have slow moving hierarchies, consider adding Range Keys. They facilitate navigation, filtration, and/or aggregration without the need of recursion.
The Range keys indicate ownership between X and Y. The range keys are especially helpful when dealing with large hierarchies (180K nodes).
The following is a simplified example, but may help.
Sample Hier Build
--Drop Table #MyHier
Declare #YourTable table (id int,ParentId int,Name varchar(50))
Insert into #YourTable values
(11, NULL,'A')
,(12, 11 ,'B')
,(13, 12 ,'F')
,(14, 13 ,'C')
,(15, 13 ,'D')
,(16, 11 ,'E')
,(17, 12 ,'G')
,(18, NULL ,'M')
,(19, 18 ,'N')
,(20, 18 ,'O')
,(21, 20 ,'P')
Declare #Top int = null --<< Sets top of Hier Try 3
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Name) as varchar(500))
,ID
,ParentId
,Lvl=1
,Name
,Path = cast('/'+Name+'/' as varchar(500))
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentId ,-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.ParentId
,p.Lvl+1
,r.Name
,cast(p.path + '/'+r.Name+'/' as varchar(500))
From #YourTable r
Join cteP p on r.ParentId = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select A.R1
,B.R2
,A.ID
,A.ParentId
,A.Lvl
,Name = Replicate(#Nest,A.Lvl-1) + A.Name
,Path
Into #MyHier
From cteR1 A
Join cteR2 B on A.ID=B.ID
Select Full Hier
-- Get The Full Hier
Select *
From #MyHier
Order By R1
Returns
Get Ancestors
-- Get Ancestors of a Node
Declare #GetAncestors int = 15
Select A.*
From #MyHier A
Join (Select R1 From #MyHier Where ID=#GetAncestors) B
on B.R1 between A.R1 and A.R2
Order By A.R1
Returns
Select Descendants
-- Get Descendants of a Node
Declare #GetDesendants int = 12
Select A.*
From #MyHier A
Join (Select R1,R2 From #MyHier Where ID=#GetDesendants) B
on A.R1 between B.R1 and B.R2
Order By A.R1
Returns
I have table described bellow from which I need to select all rows with [Value] greater for example at least 5 points than [Value] from previous row (ordered by [Id]). Starting with first row of [Id] 1, desired output would be:
[Id] [Value]
---------------
1 1
4 12
8 21
Code:
declare #Data table
(
[Id] int not null identity(1, 1) primary key,
[Value] int not null
);
insert into #Data ([Value])
select 1 [Value]
union all
select 5
union all
select 3
union all
select 12
union all
select 8
union all
select 9
union all
select 16
union all
select 21;
select [t1].*
from #Data [t1];
Edit:
So, based on JNevill's and Hogan's answers I end with this:
;with [cte1]
as (
select [t1].[Id],
[t1].[Value],
cast(1 as int) [rank]
from #Data [t1]
where [t1].[Id] = 1
union all
select [t2].[Id],
[t2].[Value],
cast(row_number() over (order by [t2].id) as int) [rank]
FROM [cte1] [t1]
inner join #Data [t2] on [t2].[value] - [t1].[value] > 5
and [t2].[Id] > [t1].[Id]
where [t1].[rank] = 1
)
select [t1].[Id],
[t1].[Value]
from [cte1] [t1]
where [t1].[rank] = 1;
which is working. Alan Burstein answer is correct too (but applicable only on MSSQL 2012+ - due to LAG fc). I will do some performance tests (I'm on 2016 version) and will see performance over my real data (approx. 30 millions of records).
If you are on 2012+ you can use LAG which will provide a better performing solution that a recursive CTE. I'm including your sample data so you can just copy/paste/test...
-- Your sample data
DECLARE #Data TABLE
(
Id int not null identity(1, 1) primary key,
Value int not null
);
insert into #Data ([Value])
select 1 [Value] union all select 5 union all select 3 union all select 12 union all
select 8 union all select 9 union all select 16 union all select 21;
-- Solution using window functions
WITH
prevRows AS
(
SELECT t1.Id, t1.Value, prevDiff = LAG(t1.Value, 1) OVER (ORDER BY t1.id) - t1.Value
FROM #Data t1
),
NewPrev AS
(
SELECT t1.Id, t1.Value, NewDiff = Value - LAG(t1.Value,1) OVER (ORDER BY t1.id)
FROM prevRows t1
WHERE prevDiff <= -5 OR prevDiff IS NULL
)
SELECT t1.Id, t1.Value
FROM NewPrev t1
WHERE NewDiff >= 5 OR NewDiff IS NULL;
I believe the best way to pull this off is using a recursive CTE. A Recursive CTE is a special type of CTE that refers back to itself. It's made up of two parts.
The recursive seed/anchor which establishes the beginning of the recursion. In your case, record with ID=1.
The recursive term/member which is the statement that refers back to itself by the name of the CTE. Here we pull through the next record that is greater than 5 from the previous found record according to the ID sorted ascending.
Code:
WITH RECURSIVE recCTE AS
(
/*Select first record for recursive seed/anchor*/
SELECT
id,
value,
cast(1 as INT) as [rank]
FROM table
WHERE id = 1
UNION ALL
/*find the next value that is more than 5 from the current value*/
SELECT
table.id,
table.value
ROW_NUMBER() OVER (ORDER BY id)
FROM
recCTE INNER JOIN table
ON table.value - recCTE.value > 5
AND table.id > recCTE.id
WHERE recCTE.[rank]=1
)
SELECT id, value FROM recCTE;
I've made use of the Row_Number() Window Function to find the rank of the matching record by ID sorted Ascending. With the WHERE clause in the recursive term we only grab the first found record that is 5 more than the previous found record. Then we head into the next recursive step.
You can do it with a recursive CTE
with find_values as
(
-- Find first value
SELECT Value
FROM #Table
ORDER BY ID ASC
FETCH FIRST 1 ROW ONLY
UNION ALL
-- Find next value
SELECT Value
FROM #Table
CROSS JOIN find_values
WHERE Value >= find_values.Value + 5
ORDER BY ID ASC
FETCH FIRST 1 ROW ONLY
)
SELECT *
FROM find_values
I have a table with a recursive hierarchy (i.e. ID, ParentID). For any item in this hierachy, I want to be able to bring back a list of everything UP AND DOWN the hierarchy along with the level for each row. Assume that a parent can only ever have a single child.
Take for example the following:
ID ParentID
--------------
1 NULL
2 1
3 2
4 NULL
5 4
6 5
Given ID 1, 2, or 3, I want to return:
ID ParentID Level
-----------------------
1 NULL 1
2 1 2
3 2 3
I've done this before, but I can't remember how. I know the solution involves a CTE, I just can't get it right! Any help is appreciated.
;with cte as
(
select *, 1 as level from #t where id = #yourid
union all
select t.*, level - 1
from cte
inner join #t t on cte.parent = t.id
),
cte2 as
(
select * from cte
union all
select t.*, level+1
from cte2
inner join #t t on cte2.id = t.parent
)
select id,parent, ROW_NUMBER() over (order by level) level
from ( select distinct id, parent, level from cte2) v
The most barebones version of the CTE query I could come up with is:
WITH Ancestry (AncestorID, DescendantID)
AS
(
SELECT
ParentID, ID
FROM
dbo.Location
WHERE
ParentID IS NOT NULL
UNION ALL
SELECT
P.AncestorID, C.ID
FROM
dbo.Location C
JOIN
Ancestry P on C.ParentID = P.DescendantID
)
SELECT * FROM Ancestry
The result is a list of all Ancestor/Descendant relationships that exist in the table.
The final "SELECT * FROM Ancestry" can be replaced with something more complex to filter, order, etc.
To include reflexive relationships, the query can be modified by adding two lines to the final SELECT statement:
SELECT * FROM Ancestry
UNION
SELECT ID, ID FROM dbo.Location
;WITH Recursive_CTE AS (
SELECT
child.ExecutiveId,
CAST(child.ExecutiveName as varchar(100)) BusinessUnit,
CAST(NULL as bigint) ParentUnitID,
CAST(NULL as varchar(100)) ParentUnit,
CAST('' as varchar(100)) LVL,
CAST(child.ExecutiveId as varchar(100)) Hierarchy,
1 AS RecursionLevel
FROM Sales_Executive_level child
WHERE ExecutiveId = 4000 --your Id which you want to get all parent node
UNION ALL
SELECT
child.ExecutiveId,
CAST(LVL + child.ExecutiveName as varchar(100)) AS BusinessUnit,
child.ParentExecutiveID,
parent.BusinessUnit ParentUnit,
CAST('' + LVL as varchar(100)) AS LVL,
CAST(Hierarchy + ':' + CAST(child.ExecutiveId as varchar(100)) as varchar(100)) Hierarchy,
RecursionLevel + 1 AS RecursionLevel
FROM Recursive_CTE parent
INNER JOIN Sales_Executive_level child ON child.ParentExecutiveID = parent.ExecutiveId
)
SELECT * FROM Recursive_CTE ORDER BY Hierarchy
OPTION (MAXRECURSION 300);