t-sql recursive query - sql

Based on an existing table I used CTE recursive query to come up with following data. But failing to apply it a level further.
Data is as below
id name parentid
--------------------------
1 project 0
2 structure 1
3 path_1 2
4 path_2 2
5 path_3 2
6 path_4 3
7 path_5 4
8 path_6 5
I want to recursively form full paths from the above data. Means the recursion will give the following output.
FullPaths
-------------
Project
Project\Structure
Project\Structure\Path_1
Project\Structure\Path_2
Project\Structure\Path_3
Project\Structure\Path_1\path_4
Project\Structure\Path_2\path_5
Project\Structure\Path_3\path_6
Thanks

Here's an example CTE to do that:
declare #t table (id int, name varchar(max), parentid int)
insert into #t select 1, 'project' , 0
union all select 2, 'structure' , 1
union all select 3, 'path_1' , 2
union all select 4, 'path_2' , 2
union all select 5, 'path_3' , 2
union all select 6, 'path_4' , 3
union all select 7, 'path_5' , 4
union all select 8, 'path_6' , 5
; with CteAlias as (
select id, name, parentid
from #t t
where t.parentid = 0
union all
select t.id, parent.name + '\' + t.name, t.parentid
from #t t
inner join CteAlias parent on t.parentid = parent.id
)
select *
from CteAlias

Try something like this:
WITH Recursive AS
(
SELECT
ID,
CAST(PathName AS VARCHAR(500)) AS 'FullPaths',
1 AS 'Level'
FROM
dbo.YourTable
WHERE
ParentID = 0
UNION ALL
SELECT
tbl.ID,
CAST(r.FullPaths + '\' + tbl.PathName AS VARCHAR(500)) AS 'FullPaths',
r.Level + 1 AS 'Level'
FROM
dbo.YourTable tbl
INNER JOIN
Recursive r ON tbl.ParentID = r.ID
)
SELECT * FROM Recursive
ORDER BY Level, ID
Output:
ID FullPaths Level
1 project 1
2 project\structure 2
3 project\structure\path_1 3
4 project\structure\path_2 3
5 project\structure\path_3 3
6 project\structure\path_1\path_4 4
7 project\structure\path_2\path_5 4
8 project\structure\path_3\path_6 4

try this:
DECLARE #YourTable table (id int, nameof varchar(25), parentid int)
INSERT #YourTable VALUES (1,'project',0)
INSERT #YourTable VALUES (2,'structure',1)
INSERT #YourTable VALUES (3,'path_1',2)
INSERT #YourTable VALUES (4,'path_2',2)
INSERT #YourTable VALUES (5,'path_3',2)
INSERT #YourTable VALUES (6,'path_4',3)
INSERT #YourTable VALUES (7,'path_5',4)
INSERT #YourTable VALUES (8,'path_6',5)
;WITH Rec AS
(
SELECT
CONVERT(varchar(max),nameof) as nameof,id
FROM #YourTable
WHERE parentid=0
UNION ALL
SELECT
CONVERT(varchar(max),r.nameof+'\'+y.nameof), y.id
FROM #yourTable y
INNER jOIN Rec r ON y.parentid=r.id
)
select * from rec
output:
nameof
-----------------------------------------------
project
project\structure
project\structure\path_1
project\structure\path_2
project\structure\path_3
project\structure\path_3\path_6
project\structure\path_2\path_5
project\structure\path_1\path_4
(8 row(s) affected)

Something like
;WITH MyCTE AS
(
SELECT
name AS FullPaths, id
FROM
MyTable
WHERE
parentid = 0 /*Normally it'd be IS NULL with an FK linking the 2 columns*/
UNION ALL
SELECT
C.FullPaths + '\' + M.name, M.id
FROM
MyCTE C
JOIN
MyTable M ON M.parentid = C.id
)
SELECT FullPaths FROM MyCTE

You'll have to change the name of #test table I was using.
WITH cte(id, name, parentid) AS
(
SELECT id, convert(varchar(128), name), parentid
FROM #test
WHERE parentid = 0
UNION ALL
SELECT t.id, convert(varchar(128), c.name +'\'+t.name), t.parentid
FROM #test t
INNER JOIN cte c
ON c.id = t.parentid
)
SELECT name as FullPaths
FROM cte
order by id

Related

Recursive CTE listing all ancestors as columns

I found many examples that show recursive CTEs that will list the path as a concatenated string and the depth of the level. For example:
CREATE TABLE #Test
(ID varchar(20),
ParentID varchar(20)
)
INSERT INTO #Test values ('100000','HEAD');
INSERT INTO #Test values ('100100','100000');
INSERT INTO #Test values ('100200','100100');
INSERT INTO #Test values ('100300','100200');
INSERT INTO #Test values ('100400','100300');
;WITH CTE AS
(
SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
FROM #Test
WHERE ParentID = 'HEAD'
UNION ALL
SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)), Level + 1
FROM #Test t
INNER JOIN CTE ON t.ParentID = CTE.ID
)
SELECT * FROM CTE
DROP table #Test
Is there a way to return each ancestor in each row in a separate column? So instead of the current result:
ID ParentId Path Level
100000 HEAD 100000 0
100100 100000 100000>100100 1
100200 100100 100000>100100>100200 2
100300 100200 100000>100100>100200>100300 3
100400 100300 100000>100100>100200>100300>100400 4
It would be:
Id Parent L0 L1 L2 L3 L4
100000 HEAD 100000 Null Null Null Null
100100 100000 100000 100100 Null Null Null
100200 100100 100000 100100 100200 Null Null
100300 100200 100000 100100 100200 100300 Null
100400 100300 100000 100100 100200 100300 100400
Assumption is that I know the max level of depth, in this example the max level is 5. SQL Server ver. 12.0, and DB Compatibility is at 120.
If you have a known or maximum number of levels, consider a bit of JSON.
If not 2016+ ... there is a similar XML approach.
Example
;WITH CTE AS
(
SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
FROM #Test
WHERE ParentID = 'HEAD'
UNION ALL
SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)), Level + 1
FROM #Test t
INNER JOIN CTE ON t.ParentID = CTE.ID
)
SELECT A.ID
,A.ParentID
,B.*
,A.Level
FROM CTE A
Cross Apply (
Select L0 = trim(JSON_VALUE(S,'$[0]'))
,L1 = trim(JSON_VALUE(S,'$[1]'))
,L2 = trim(JSON_VALUE(S,'$[2]'))
,L3 = trim(JSON_VALUE(S,'$[3]'))
,L4 = trim(JSON_VALUE(S,'$[4]'))
,L5 = trim(JSON_VALUE(S,'$[5]'))
From ( values ( '["'+replace(path,'>','","')+'"]' ) ) A(S)
) B
Returns
UPDATE: The XML Approach
;WITH CTE AS
(
SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
FROM #Test
WHERE ParentID = 'HEAD'
UNION ALL
SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)), Level + 1
FROM #Test t
INNER JOIN CTE ON t.ParentID = CTE.ID
)
SELECT A.ID
,A.ParentID
,B.*
,A.Level
FROM CTE A
Cross Apply (
Select L0 = xDim.value('/x[1]','varchar(50)')
,L1 = xDim.value('/x[2]','varchar(50)')
,L2 = xDim.value('/x[3]','varchar(50)')
,L3 = xDim.value('/x[4]','varchar(50)')
,L4 = xDim.value('/x[5]','varchar(50)')
,L5 = xDim.value('/x[6]','varchar(50)')
From (Select Cast('<x>' + replace(Path,'>','</x><x>')+'</x>' as xml) as xDim) as A
) B
as everyone mentioned you need to know number of levels you have , but here is the implementation with PIVOT:
;WITH CTE AS
(
SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
FROM #Test
WHERE ParentID = 'HEAD'
UNION ALL
SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)), Level + 1
FROM #Test t
INNER JOIN CTE ON t.ParentID = CTE.ID
)
select
*
from (
Select Id , ParentID,
'L'+ CAST(Row_Number() Over (Partition By ID Order By ID) AS VARCHAR)AS Col,
Split.value
, level
From CTE
Cross apply string_split(path,'>') as Split
) AS tbl
Pivot (Max(value) For Col IN ([L1],[L2],[L3],[L4],[L5])) AS Pvt

get all child from an parent id

hi i need a query to do this
my table data
ID ParentID DATA
--------------------------------
1 -1 a
2 1 b
3 2 c
4 3 d
5 3 f
and what ineed a query that take a ID as parameter and return all recursively childs and Itself
parameter : (ID=2)
return must be :
ID ParentID DATA
--------------------------------
2 1 b
3 2 c
4 3 d
5 3 f
Try this:
;with temp as (
select id, parentId, data from t
where id = 2
union all
select t.id, t.parentId, t.data from t
join temp on temp.id = t.parentId
)
select * from temp
Fiddle here.
This should do it for you:
create table #temp
(
id int,
parentid int,
data varchar(1)
)
insert #temp (id, parentid, data) values (1, -1, 'a')
insert #temp (id, parentid, data) values (2,1, 'b')
insert #temp (id, parentid, data) values (3,2, 'c')
insert #temp (id, parentid, data) values (4,3, 'd')
insert #temp (id, parentid, data) values (5,3, 'f')
; with cte as (
select id, parentid, data, id as topparent
from #temp
union all
select child.id, child.parentid, child.data, parent.topparent
from #temp child
join cte parent
on parent.id = child.parentid
)
select id, parentid, data
from cte
where topparent = 2
drop table #temp
EDIT or you can put the WHERE clause inside the first select
create table #temp
(
id int,
parentid int,
data varchar(1)
)
insert #temp (id, parentid, data) values (1, -1, 'a')
insert #temp (id, parentid, data) values (2,1, 'b')
insert #temp (id, parentid, data) values (3,2, 'c')
insert #temp (id, parentid, data) values (4,3, 'd')
insert #temp (id, parentid, data) values (5,3, 'f')
; with cte as (
select id, parentid, data, id as topparent
from #temp
WHERE id = 2
union all
select child.id, child.parentid, child.data, parent.topparent
from #temp child
join cte parent
on parent.id = child.parentid
)
select id, parentid, data
from cte
drop table #temp
Results:
id parentid data
2 1 b
3 2 c
4 3 d
5 3 f
declare #ID int = 2;
with C as
(
select ID, ParentID, DATA
from YourTable
where ID = #ID
union all
select T.ID, T.ParentID, T.DATA
from YourTable as T
inner join C
on T.ParentID = C.ID
)
select ID, ParentID, DATA
from C
Try on SE-Data
try this.
select * from table where id= 2 or parentid = 2

Recursive select in SQL

I have an issue I just can't get my head around. I know what I want, just simply can't get it out on the screen.
What I have is a table looking like this:
Id, PK UniqueIdentifier, NotNull
Name, nvarchar(255), NotNull
ParentId, UniqueIdentifier, Null
ParentId have a FK to Id.
What I want to accomplish is to get a flat list of all the id's below the Id I pass in.
example:
1 TestName1 NULL
2 TestName2 1
3 TestName3 2
4 TestName4 NULL
5 TestName5 1
The tree would look like this:
-1
-> -2
-> -3
-> -5
-4
If I now ask for 4, I would only get 4 back, but if I ask for 1 I would get 1, 2, 3 and 5.
If I ask for 2, I would get 2 and 3 and so on.
Is there anyone who can point me in the right direction. My brain is fried so I appreciate all help I can get.
declare #T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)
insert into #T values
(1, 'TestName1', NULL),
(2, 'TestName2', 1),
(3, 'TestName3', 2),
(4, 'TestName4', NULL),
(5, 'TestName5', 1)
declare #Id int = 1
;with cte as
(
select T.*
from #T as T
where T.Id = #Id
union all
select T.*
from #T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte
Result
Id Name ParentId
----------- -------------------- -----------
1 TestName1 NULL
2 TestName2 1
5 TestName5 1
3 TestName3 2
Here's a working example:
declare #t table (id int, name nvarchar(255), ParentID int)
insert #t values
(1, 'TestName1', NULL),
(2, 'TestName2', 1 ),
(3, 'TestName3', 2 ),
(4, 'TestName4', NULL),
(5, 'TestName5', 1 );
; with rec as
(
select t.name
, t.id as baseid
, t.id
, t.parentid
from #t t
union all
select t.name
, r.baseid
, t.id
, t.parentid
from rec r
join #t t
on t.ParentID = r.id
)
select *
from rec
where baseid = 1
You can filter on baseid, which contains the start of the tree you're querying for.
Try this:
WITH RecQry AS
(
SELECT *
FROM MyTable
UNION ALL
SELECT a.*
FROM MyTable a INNER JOIN RecQry b
ON a.ParentID = b.Id
)
SELECT *
FROM RecQry
Here is a good article about Hierarchy ID models. It goes right from the start of the data right through to the query designs.
Also, you could use a Recursive Query using a Common Table Expression.
I'm guessing that the easiest way to accomplish what you're looking for would be to write a recursive query using a Common Table Expression:
MSDN - Recursive Queries Using Common Table Expressions

SQL grouping by parent child

If I had this structure with the columns:
Primary_Key, Name, Parent_Primary_ID, DISPLAY_ORDER
1 Event NULL 1
2 News NULL 2
3 Event_List 1 1
4 Event_Detail 1 2
5 News_List 2 1
6 News_Details 2 2
how would you return data like:
1 Event
3 Event_List
4 Event_Detail
2 News
5 News_List
6 News_Detail
Thanks
Rob
If SQL Server 2005+
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
WITH Hierarchy
AS (SELECT *,
path = CAST(DISPLAY_ORDER AS VARCHAR(100))
FROM #YourTable
WHERE Parent_Primary_ID IS NULL
UNION ALL
SELECT y.Primary_Key,
y.Name,
y.Parent_Primary_ID,
y.DISPLAY_ORDER,
CAST(path + '.' + CAST(y.DISPLAY_ORDER AS VARCHAR) AS VARCHAR(100))
FROM #YourTable y
JOIN Hierarchy h
ON h.Primary_Key = y.Parent_Primary_ID)
SELECT Primary_Key,
Name
FROM Hierarchy
ORDER BY path
Try (asumming standardish sql is supported)
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
select
primary_key = t1.primary_key,
name = t1.name
from
#YourTable t1
left join #YourTable t2 on t1.parent_primary_id = t2.Primary_Key
order by
coalesce(t2.DISPLAY_ORDER,t1.DISPLAY_ORDER,0),
case
when t2.Primary_Key is null then 0
else t1.DISPLAY_ORDER
end
I don't see any grouping in your results. Unless you are trying to do something you aren't telling us I would use the query below:
SELECT Primary_Key, Name FROM YourTable
I don't see how you are ordering those results so I didn't try to order them.

SQL Server + Select only two records for each masterID in a table

I got a child table that contains 1 to n records linked to the master table via a MasterID column.
How can I select from the child table only the first 5 records for each MasterID?
Using Sql Server CTE and ROW_NUMBER you could try using
DECLARE #ParentTable TABLE(
ID INT
)
INSERT INTO #ParentTable SELECT 1
INSERT INTO #ParentTable SELECT 2
INSERT INTO #ParentTable SELECT 3
DECLARE #ChildTable TABLE(
ID INT,
ParentID INT
)
INSERT INTO #ChildTable SELECT 1, 1
INSERT INTO #ChildTable SELECT 2, 1
INSERT INTO #ChildTable SELECT 3, 1
INSERT INTO #ChildTable SELECT 4, 1
INSERT INTO #ChildTable SELECT 5, 1
INSERT INTO #ChildTable SELECT 6, 1
INSERT INTO #ChildTable SELECT 7, 1
INSERT INTO #ChildTable SELECT 8, 2
INSERT INTO #ChildTable SELECT 9, 2
INSERT INTO #ChildTable SELECT 10, 3
INSERT INTO #ChildTable SELECT 11, 3
;WITH RowNums AS(
SELECT pt.ID ParentID,
ct.ID ChildID,
ROW_NUMBER() OVER (PARTITION BY pt.ID ORDER BY ct.ID) RowNum
FROM #ParentTable pt INNER JOIN
#ChildTable ct ON pt.ID = ct.ParentID
)
SELECT ParentID,
ChildID
FROM RowNums
WHERE RowNum <= 5
Try a regular join where the constraint is a subquery pulling the TOP 5 from the child table. In untested pseudocode:
SELECT A.MasterID, B.*
FROM MasterTable A
JOIN ChildTable B
ON A.MasterID = B.MasterID
AND B.ChildID IN (SELECT Top 5 ChildID FROM ChildTable
WHERE MasterID = A.MasterID ORDER BY Whatever)