I need to create a SQL-Query that uses a recursive CTE to fetch records from TableA. (Tree-Structure). I pass him the "leaf" and want to know the way back to the root.
This works with a #SOME_ID Variable
;WITH cte_recursive AS
(
SELECT ID, SUB_ID FROM tableA
WHERE SUB_ID = #SOME_ID
UNION ALL
SELECT parent.ID, parent.SUB_ID
FROM tableA parent
INNER JOIN cte_recursive child ON child.ID = parent.SUB_ID
)
What I need to acchieve now is, that I take every record from TableB
and use tableB.SOME_ID for the CTE Expression and create an insert into TableC foreach record the CTE generates plus some fields from TableB
(cte_recursive.CHILD_ID, tableB.SomeValue, tableB.SomeOtherValue)
So my question here is, how do I pass the tableB.SOME_ID to the cte expression ?
So in TableA I got something like this:
ID, SUB_ID
1 , 2
2 , 3
2 , 4
2 , 5
5 , 6
7 , 8
8 , 9
If I pass him SUB_ID = 5, the CTE returns me the Records #1, #2, #3, #4, #5
as SUB_ID = 5 is a Child of a Child of a child... of ID = 1
You can create table valued function
create function ftBranchOf
(
#SOME_ID int -- actual type of #SOME_ID
)
returns table as return
(
WITH cte_recursive AS
(
SELECT ID, SUB_ID FROM tableA
WHERE SUB_ID = #SOME_ID
UNION ALL
SELECT parent.ID, parent.SUB_ID
FROM tableA parent
INNER JOIN cte_recursive child ON child.ID = parent.SUB_ID
)
select * from cte_recursive
)
And then use it in your query
insert into TableC (...)
select p.ID, b.SomeValue, b.SomeOtherValue
from TableB b
cross apply ftBranchOf(b.SOME_ID) p
I'm not sure what are you want, so just guessing
;WITH cte_tableB AS
(
SELECT * FROM tableB
)
, cte_recursive AS
(
SELECT ID, SUB_ID, SOME_ID FROM tableA
WHERE SUB_ID IN (SELECT SOME_ID FROM cte_tableB)
UNION ALL
SELECT parent.ID, parent.SUB_ID, SOME_ID
FROM tableA parent
INNER JOIN cte_recursive child ON child.ID = parent.SUB_ID
)
INSERT [YourTable] ([YourColumns...])
SELECT [YourColumns...]
FROM cte_recursive
INNER JOIN cte_tableB ON cte_recursive.SomeID = cte_tableB.SomeID
Related
How to find the count of children in each row?
For example:
1 ROW COUNT=1
2 ROW COUNT=0
...
and so on. In the next column
You can do this using recursive CTE, but it should be done with simple join. First, find the count of each node, excluding node without parents:
SELECT [ParentID]
,COUNT(*)
FROM MyTable
WHERE [ParentID] <> 0
GROUP BY [ParentID];
If this is OK, just join to the initial table:
SELECT *
FROM MyTable T1
LEFT JOIN
(
SELECT [ParentID]
,COUNT(*) AS [all_childs]
FROM MyTable
WHERE [ParentID] <> 0
GROUP BY [ParentID]
) T2
oN T1.[parentID] = T2.[ParentID];
This should be okay, you need isnull() function when ROW COUNT=0
SELECT
PA.ID,
PA.Title_Name,
ISNULL(P.COUNTT,0) CountOfID
FROM #My_Table PA
LEFT JOIN (
SELECT COUNT(*) COUNTT, Parent_ID from #My_Table GROUP BY Parent_ID
) as P on P.Parent_ID = PA.ID
I have 2 tables and want to compare them and modify tableA (set NameMod = 1) if it has different rows.
To compare tables I use:
select Id, Name from tableB
except
select Id, Name from tableA
And then I want to modify tableA:
update tableA Set NameMod = 1
where exists (
select Id, Name from tableB
except
select Id, Name from tableA
)
But I can only use EXISTS before the sub-query and in this case it updates all elements in table not different rows.
Could you try this:
MERGE TableA AS [Target]
USING TableB AS [Source]
ON [Target].[ID] = [Source].[ID]
AND [Target].[Name ] = [Source].[Name]
WHEN NOT MATCHED BY TARGET
THEN UPDATE SET NameMod = 1;
It is using the MERGE clause.
If you do not like the clause, you can use CTE like this:
;WITH IdsForUpdate ([id]) AS
(
SELECT DISTINCT Id
FROM
(
select Id, Name from tableB
except
select Id, Name from tableA
) DS([id], [name])
)
update tableA
Set NameMod = 1
FROM tableA A
INNER JOIN IdsForUpdate B
ON A.[id] = B.[id];
I want to retrieve the parentid of an id, if that parentid has a parent again retrieve it, and so on.
Kind of hierarchy table.
id----parentid
1-----1
5-----1
47894--5
47897--47894
am new to sql server and tried, some queries like:
with name_tree as
(
select id, parentid
from Users
where id = 47897 -- this is the starting point you want in your recursion
union all
select c.id, c.parentid
from users c
join name_tree p on p.id = c.parentid -- this is the recursion
)
select *
from name_tree;
It is giving me only one row.
and also I want to insert these records into a temporary table variable.
How can I do this. thanks in advance. sorry for asking the simple question(though not to me)
Try this to get all parents of a child
;with name_tree as
(
select id, parentid
from Users
where id = 47897 -- this is the starting point you want in your recursion
union all
select C.id, C.parentid
from Users c
join name_tree p on C.id = P.parentid -- this is the recursion
-- Since your parent id is not NULL the recursion will happen continously.
-- For that we apply the condition C.id<>C.parentid
AND C.id<>C.parentid
)
-- Here you can insert directly to a temp table without CREATE TABLE synthax
select *
INTO #TEMP
from name_tree
OPTION (MAXRECURSION 0)
SELECT * FROM #TEMP
Click here to view result
EDIT :
If you want to insert into a table variable, you can do something like:
-- Declare table varialbe
Declare #TABLEVAR table (id int ,parentid int)
;with name_tree as
(
select id, parentid
from #Users
where id = 47897 -- this is the starting point you want in your recursion
union all
select C.id, C.parentid
from #Users c
join name_tree p on C.id = P.parentid -- this is the recursion
-- Since your parent id is not NULL the recursion will happen continously.
-- For that we apply the condition C.id<>C.parentid
AND C.id<>C.parentid
)
-- Here you can insert directly to table variable
INSERT INTO #TABLEVAR
select *
from name_tree
OPTION (MAXRECURSION 0)
SELECT * FROM #TABLEVAR
Click here to view result
Your query is doing recursion but in opposite direction. So if you change starting point to:
where id = 1
then you will have user 1 and all his successors
you didn't mention the desired output and input.
However you can try like this,
Declare #t table (id int ,parentid int)
insert into #t
select 1,1 union all
select 5,1 union all
select 47894,5 union all
select 47897,47894
;With CTE as
(
select * from #t where id=1
union all
Select a.* from #t a inner join cte b
on b.id=a.parentid and
a.id<>b.id
)
select * from cte
I can achieve recursion by using SQL Server's With command (CTE).
WITH MyCTE(ParentID,ID,Name,Level)
AS
(
SELECT ManagerID AS ParentID, UserID AS ID, UserName AS Name, 0 AS Level
FROM USERS U
WHERE U.ManagerID IS NULL
UNION ALL
SELECT U.ManagerID AS ParentID, U.UserID AS ID, U.UserName AS Name, H.Level+1 AS Level
FROM USERS U
INNER JOIN MyCTE H ON H.ID = U.ManagerID
)
SELECT ParentID,ID FROM MyCTE
returns
ParentID ID
NULL 1
1 2
1 3
2 4
What I want to achieve is to reverse this result set. Namely,reversing the root node and the deepest child node as,
ParentID ID
NULL 4
4 2
2 1
3 1
Couldn't figure out how to programmatically implement this (preferably by using CTE), like by using a parameter to determine the recursion order etc. Any help is greatly appreciated, thanks.
Edit :
Modified this a bit inserting my first CTE's results into a temp table, then using another recursion I reverse the order as (I know "WHERE T.ID = (SELECT MAX(ID) FROM #tmp)" wont work in a real situation, I also gotta determine the deepest node with the "Level" column, just tried to simplify this for this example),
INSERT INTO #tmp
SELECT ParentID,ID,Level FROM MyCTE
WITH MyCTE2(ParentID,ID,Level)
AS
(
SELECT NULL AS ParentID, ID AS ID, 0 AS Level FROM #tmp T
WHERE T.ID = (SELECT MAX(ID) FROM #tmp)
UNION ALL
SELECT R2.ID AS ParentID, T.ParentID AS ID, R2.Level+1 FROM #tmp T
INNER JOIN MyCTE2 R2 ON R2.ID = T.ID
WHERE T.ParentID IS NOT NULL
)
Original Results (removed the 1,3 pair)
ParentID ID Level
NULL 1 0
1 2 1
2 4 2
Reversed results,
ParentID ID Level
NULL 4 0
4 2 1
2 1 2
Edit 2:
I did something like this,
SELECT TTT.ParentID,TTT.ID,TTT.Level FROM
(
SELECT ParentID,ID,Level FROM MyCTE2
UNION ALL
SELECT TT.ID AS ParentID,TT.ParentID AS ID,(SELECT Level+1 FROM #tmp WHERE ID=TT.ID)
AS Level FROM
(
SELECT ID FROM #tmp
EXCEPT
SELECT ID FROM MyCTE2
)T INNER JOIN #tmp TT ON TT.ID = T.ID
)TTT
ORDER BY TTT.Level
gives,
ParentID ID Level
NULL 4 0
4 2 1
2 1 2
3 1 2
This may contain errors, im not sure yet, just wanted to show to make sure that pair (3,1) is whther correct with level 2 ? Been thinking on this for quite a while now, I might make some silly mistakes.
Sample data
declare #T table
(
ParentID int,
ID int
)
insert into #T values
(NULL, 1),
(1 , 2),
(1 , 3),
(2 , 4)
Recursion from root:
;with C as
(
select ParentID, ID
from #T
where ParentID is null
union all
select T.ParentID, T.ID
from #T as T
inner join C
on T.ParentID = C.ID
)
select *
from C
Result
ParentID ID
----------- -----------
NULL 1
1 2
1 3
2 4
Recursion from leafs:
;with C as
(
select null as PParentID, ID, ParentID
from #T
where ID not in (select ParentID
from #T
where ParentID is not null)
union all
select C.ID, T.ID, T.ParentID
from #T as T
inner join C
on T.ID = C.ParentID
)
select distinct
PParentID as ParentID,
ID
from C
Result:
ParentID ID
----------- -----------
NULL 3
NULL 4
4 2
2 1
3 1
If you have many branches you will have duplicate rows as merge together. Using distinct takes care of that.
To get the levels correct you need to first calculate the level from top down. Store that in a table variable (or temp table) and then use that as the source for leaf->root recursion.
-- Primary key and unique is in there to get the indexes used in the recursion
declare #T2 table
(
ParentID int,
ID int,
Level int,
primary key (ID),
unique(ParentID, ID)
)
;with C as
(
select ParentID, ID, 0 as Level
from #T
where ParentID is null
union all
select T.ParentID, T.ID, Level + 1
from #T as T
inner join C
on T.ParentID = C.ID
)
insert into #T2
select ParentID, ID, Level
from C
;with C as
(
select null as PParentID, ID, ParentID, Level
from #T2
where ID not in (select ParentID
from #T2
where ParentID is not null)
union all
select C.ID, T.ID, T.ParentID, T.Level
from #T2 as T
inner join C
on T.ID = C.ParentID
)
select distinct
PParentID as ParentID,
ID,
max(Level) over() - Level as level
from C
Result:
ParentID ID level
----------- ----------- -----------
NULL 3 1
NULL 4 0
2 1 2
3 1 2
4 2 1
It is possible but a really bad idea to replace #T2 with a multi CTE query. It will kill performance because to first CTE will be rebuilt for each recursion. At least that is my guess of what is happening but believe me it is not fast.
I want to join the result set of common table expression with the existing table. The problem arise using the group by clause as given in the following query. Can anyone please tell me how to join those two tables?.
With CTEQuery
as
(SELECT StudentOnlineExamCourseAnswer.StudentID, StudentOnlineExamCourseAnswer.OnlineExamID, StudentOnlineExamCourseAnswer.CourseID,
StudentOnlineExamCourseAnswer.CentreID,
case QuestionBank.ComplexLevelID when 1 then (2) when 2 then (4) when 3 then (6) when 4 then (8) when 5 then (10) end as Mark
FROM QuestionBank INNER JOIN
StudentOnlineExamCourseAnswer ON QuestionBank.Answer = StudentOnlineExamCourseAnswer.Answer AND
QuestionBank.QuestionID = StudentOnlineExamCourseAnswer.QuestionID)
select StudentID, OnlineExamID ,CourseID , CentreID , sum(Mark) as 'Total Marks' from CTEQuery
group by StudentID, OnlineExamID ,CourseID , CentreID
You can define multiple CTEs for a single select, and each CTE can reference previously defined ones. So you can do:
With CTEQuery
as
(SELECT StudentOnlineExamCourseAnswer.StudentID, StudentOnlineExamCourseAnswer.OnlineExamID, StudentOnlineExamCourseAnswer.CourseID,
StudentOnlineExamCourseAnswer.CentreID,
case QuestionBank.ComplexLevelID when 1 then (2) when 2 then (4) when 3 then (6) when 4 then (8) when 5 then (10) end as Mark
FROM QuestionBank INNER JOIN
StudentOnlineExamCourseAnswer ON QuestionBank.Answer = StudentOnlineExamCourseAnswer.Answer AND
QuestionBank.QuestionID = StudentOnlineExamCourseAnswer.QuestionID)
, SummarizedCTE as (
select StudentID, OnlineExamID ,CourseID , CentreID , sum(Mark) as TotalMark from CTEQuery
group by StudentID, OnlineExamID ,CourseID , CentreID)
select <new query involving joining SummarizedCTE with the "other table" referenced in your discussion>
You need to create your CTE first, then use it in the next SELECT statement.
WITH cteTest (Column1) AS
(
SELECT column1
FROM table1
)
SELECT *
FROM cteTest
JOIN Table2
ON cteTest.column1 = Table2.column1
example
with demoCTE
as
(
select id from table1
) select * from demoCTE
join table2
on demoCTE.id= table2.id