CTE goes recursive and fails - sql-server

I have created a very small CTE recursive function but seems it goes recursive all the time and fails.
Please find details inline:
Table Name & Data:
insert into dbo.HierarchyEmployee
Values
(1,1, 'Name1'),
(2,1, 'Name2'),
(3,4, 'Name3'),
(4,5, 'Name4'),
(5,2, 'Name5'),
(6,4, 'Name6'),
(7,1, 'Name7'),
(8,8, 'Name8'),
(9,3, 'Name9'),
(10,1, 'Name10')
Ideal Result Set:
HierarchyParent HierarchyID Name
1 1 Name
1 2 Name2
1 7 Name7
1 10 Name10
I tried to achieve this through reclusive CTE as I wanted to look how recursion works.
Below is the query used:
WITH CTE_HierarchyEmployee
AS
(
SELECT H.HierarchyID,
H.HierarchyParent,
H.Name
FROM dbo.HierarchyEmployee H
WHERE H.HierarchyID = 1
UNION ALL
SELECT H.HierarchyID,
H.HierarchyParent,
H.Name
FROM
dbo.HierarchyEmployee H
INNER JOIN CTE_HierarchyEmployee CTEH
ON H.HierarchyParent = cteh.HierarchyID
)
SELECT * FROM CTE_HierarchyEmployee
Error:
Msg 530, Level 16, State 1, Line 41
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Appreciate you input on how to resolve the revulsion.

EDIT: I figure out the reason. Problem is in the sample data. The parent most HierarchyID = 1 is also having the ParentId = 1. In "NORMAL" situation this can't be the case, most of the times parentID of top most id is NULL. Since parent and HierarchyID are same (1) it is going into the loop of 1 is parent of 1.
2 ways you can solve the problem:
1. Update parentID of HierarchyID = 1 to NULL
2. Add extra where condition in Recursive query where H.HierarchyID <> 1
You just need to add OPTION (MAXRECURSION 0) at the end of the query to over come the limitation of 100 recursion. See:
WITH CTE_HierarchyEmployee
AS
(
SELECT H.HierarchyID,
H.HierarchyParent,
H.Name
FROM dbo.HierarchyEmployee H
WHERE H.HierarchyID = 1
UNION ALL
SELECT H.HierarchyID,
H.HierarchyParent,
H.Name
FROM
dbo.HierarchyEmployee H
INNER JOIN CTE_HierarchyEmployee CTEH
ON H.HierarchyParent = cteh.HierarchyID
)
SELECT * FROM CTE_HierarchyEmployee
OPTION (MAXRECURSION 0)

Related

SQL - JOIN SELECT TOP 1 with subquery returns NULL

I need to get TOP 1 row from a table so I used subquery when joining it. However, TOP 1 returns null value but if I remove TOP 1, it works. I need to only get one top result if by any chance the table will contain multiple rows with same primary key value.
SELECT Sem_ID AS SeminarId
FROM t_Seminar sem
JOIN
(
SELECT TOP 1 rhb.SeminarId AS SeminarId
FROM WinClient.ReportBrief rhb
) rh
ON Sem_ID = rh.SeminarId
WHERE sem.Sem_ID = #SeminarId
GROUP BY Sem_ID
Any help is appreciated!
Considering that you only return the value of SeminarId from ReportBrief, and that has to have the value of #SeminarId, (due to the clauses ON Sem_ID = rh.SeminarId and sem.Sem_ID = #SeminarId) it seems like an EXISTS would be far better here:
SELECT TOP 1 S.Sem_ID AS SeminarId
FROM t_Seminar S
WHERE S.Sem_ID = #SeminarId
AND EXISTS (SELECT 1
FROM WinClient.ReportBrief RB
WHERE S.Sem_ID = RB.SeminarId);
I also changed the TOP 1 in the outer query. It seems pointless to have a GROUP BY with no aggregation, and as there can only be 1 value, then a TOP 1 works fine, and is less overhead.
Try to Use top in the outer query
SELECT TOP 1 Sem_ID AS SeminarId
FROM t_Seminar sem
JOIN
(
SELECT rhb.SeminarId AS SeminarId
FROM WinClient.ReportBrief rhb
) rh
ON Sem_ID = rh.SeminarId
WHERE sem.Sem_ID = #SeminarId
and isnull(Sem_ID,'')<>''
GROUP BY Sem_ID

Why is this SQL case statement behaving like an OR statement?

Consider the following query:
declare #RentalId int = 1
SELECT
r.RentalId
,r.[Name]
,rt.TypeId
FROM dbo.Rental r
LEFT JOIN dbo.RentalRateType rt ON (
r.RentalId = rt.RentalId
AND rt.TypeId = (
case when rt.TypeId = 6 and coalesce(rt.[Max], rt.[Min]) is not null then 6
when rt.TypeId = 1 and coalesce(rt.[Max], rt.[Min] is not null then 1
else -1 end
))
WHERE r.RentalId = #RentalId
I'm attempting to return a single record/row. The particular rental in question has 2 records in the dbo.RentalRateType table, and when I run the above query, I get 2 results, but I want it to short circuit on the first match in the case where.
Basically, the end user can fill in multiple rate types, more than what you see in this example, and each of those types has a priority. 6 is the highest priority in the example.
So I'm getting this result:
RentalId | Name | TypeId
----------------------------
1 Super Car 6
1 Super Car 1
But if the type (6) exists, I would expect only the first row above returned.
I must be missing something silly. This works as expected:
case when 1=2 then 6
when 1=1 then 1
else -1 end
While I'm here, I'm open to a more efficient manner of handling this if exists.
Use an apply instead, these are an efficient way to get "top n" queries:
SELECT
r.RentalId
, r.[Name]
, oa.TypeId
FROM dbo.Rental r
OUTER APPLY (
SELECT TOP (1)
rt.TypeId
FROM dbo.RentalRateType rt
WHERE r.RentalId = rt.RentalId
ORDER BY
rt.TypeId DESC
) oa
WHERE r.RentalId = #RentalId

sql server 2008 CTE Error

Im trying to work with recursive CTE, but my query moves into an infinite loop. This is my code. What is the problem here?
With Family As
(
Select s.EmpID, S.Name, s.RepID , 0 as Depth
From TestingRec s
Where s.EmpID=s.RepID
Union All
Select s2.EmpID,S2.Name, s2.RepID , Depth + 1
From TestingRec s2
Inner Join Family
On s2.RepID = Family.EmpID
)
Select *
From Family
From the anchor part of the CTE:
Select s.EmpID, S.Name, s.RepID , 0 as Depth
From TestingRec s
Where s.EmpID=s.RepID
We can see that there are rows in TestingRec for which EmpID and RepID are equal. In the recursive part, we select rows which match an EmpID that we've already found:
Select s2.EmpID,S2.Name, s2.RepID , Depth + 1
From TestingRec s2
Inner Join Family
On s2.RepID = Family.EmpID
However, there's nothing here to prevent us re-matching those rows that the anchor part found and adding them into the result set again, just with a new depth assigned.
The fix may be as simple as a WHERE clause in the recursive part which has s2.RepID <> s2.EmpID
In your first query you get all the records where EmpId=RepId. When means for the initial records both empid and repid are equal.
Then in your anchor query(union all) you are refering empid=family.repid, which means here also you get the first query results. So i think you need to exclude the first level records in the second query. The following query might work
With Family As
(
Select s.EmpID, S.Name, s.RepID , 0 as Depth
From TestingRec s
Where s.EmpID=s.RepID
Union All
Select s2.EmpID,S2.Name, s2.RepID , Depth + 1
From TestingRec s2
Inner Join Family
On s2.RepID = Family.EmpID
WHERE s2.RepID <> s2.EmpID
)
Select *
From Family
Try this:
With Family As
(
Select s.EmpID, S.Name, s.RepID , 0 as Depth
From TestingRec s
Where s.EmpID=s.RepID
Union All
Select s2.EmpID,S2.Name, s2.RepID , Depth + 1
From TestingRec s2
Inner Join Family
On s2.RepID = Family.EmpID
And s2.repid <> s2.empid --This excludes the results from the first half
)
Select *
From Family

Select Count Top Inner Join and Where Clause in SQL

This is my Query:
SELECT TOP 3 tablestudentanswer.examid,
tablestudentanswer.studentid,
tablestudentanswer.itemno,
tablestudentanswer.studentanswer,
tablescore.score
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
WHERE tablestudentanswer.examid = 1
AND tablestudentanswer.itemno = 1
ORDER BY tablescore.score ASC
It returns this table:
ExamID StudentID ItemNo StudentAnswer Score
1006 1 1 A 25
1005 1 2 B 30
1004 1 3 A 35
What i want to do is it will return 2 if StudentAnswer='A' and 1 if StudentAnswer='B'
Guys there is nothing wrong with my query on top. What i am asking is what should I add in that query.
I have this which in my mind should return 2 but its an error.
Select COUNT(*) From (
Select Top 3 TableStudentAnswer.ExamID, TableStudentAnswer.StudentID, TableStudentAnswer.ItemNo, TableStudentAnswer.StudentAnswer, TableScore.Score
from TableStudentAnswer
Inner join TableScore on TableStudentAnswer.StudentID=TableScore.StudentID and TableStudentAnswer.ExamID=TableScore.ExamID
where TableStudentAnswer.ExamID=1 and TableStudentAnswer.ItemNo=1
Order By TableScore.Score Asc) where TableStudentAnswer.StudentAnswer = 'A'
It should return:
2
Please help me!
Will this do?
SELECT TOP 3 tablestudentanswer.examid,
tablestudentanswer.studentid,
tablestudentanswer.itemno,
tablestudentanswer.studentanswer,
tablescore.score,
case
when tablestudentanswer.studentanswer = 'A' then 2
when tablestudentanswer.studentanswer = 'B' then 1
else NULL
end as [MyColumn]
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
WHERE tablestudentanswer.examid = 1
AND tablestudentanswer.itemno = 1
ORDER BY tablescore.score ASC
Your question is a bit unclear. Perhaps you want the amount of answers for each?
count(1) over (partition by tablestudentanswer.studentanswer)
This will give you a column with the amount of all the answers with the given studentanswer to each of the rows in the result set. However, note that this could be quite slow. If you can, you're better off using a normal group by.
Do you mean you would like the query to return the number of answers? If so, using COUNT may help.
SELECT tablestudentanswer.studentid,
tablestudentanswer.studentanswer
COUNT(1) AS NumberOfAnswers
FROM tablestudentanswer
INNER JOIN tablescore
ON tablestudentanswer.studentid = tablescore.studentid
AND tablestudentanswer.examid = tablescore.examid
GROUP BY tablestudentanswer.studentid, tablestudentanswer.studentanswer
Please correct me if I am wrong.
By the way, why does your result table doesn't consist of itemno even though you have it in your SELECT statement?

TSQL - While loop within select?

In SQL server
Ok, so I'm working with a database table in which rows can have parent rows, which can then have parent rows of their own. I need to select the root 'row'. I don't know the best way to do this.
There is a field called ParentId, which links the row to the row with that ID. When the ParentId = 0, it is the root row.
This is my query now:
SELECT Releases.Name,WorkLog.WorkLogId
FROM WorkLog,Releases
WHERE
Releases.ReleaseId = WorkLog.ReleaseId
and WorkLogDateTime >= #StartDate
and WorkLogDateTime <= #end
I don't really need the Release Name of the child releases, I want only the root Release Name, so I want to select the result of a While loop like this:
WHILE (ParentReleaseId != 0)
BEGIN
#ReleaseId = ParentReleaseId
END
Select Release.Name
where Release.RealeaseId = #ReleaseId
I know that syntax is horrible, but hopefully I'm giving you an idea of what I'm trying to acheive.
Here is an example, which could be usefull:
This query is getting a lower element of a tree, and searching up to the parent of parents.
Like I have 4 level in my table -> category 7->5, 5->3, 3-> 1. If i give it to the 5 it will find the 1, because this is the top level of the three.
(Changing the last select you can have all of the parents up on the way.)
DECLARE #ID int
SET #ID = 5;
WITH CTE_Table_1
(
ID,
Name,
ParentID
)
AS(
SELECT
ID,
Name,
ParentID
FROM Table_1
WHERE ID = #ID
UNION ALL
SELECT
T.ID,
T.Name,
T.ParentID
FROM Table_1 T
INNER JOIN CTE_Table_1 ON CTE_Table_1.ParentID = T.ID
)
SELECT * FROM CTE_Table_1 WHERE ParentID = 0
something like this
with cte as
(
select id,parent_id from t where t.id=#myStartingValue
union all
select t.id,t.parent_id
from cte
join t on cte.parent_id = t.id where cte.parent_id<>0
)
select *
from cte
join t on cte.id=t.id where cte.parent_id = 0
and with fiddle : http://sqlfiddle.com/#!3/a5fa1/1/0
Using Andras approach, I edited the final select to directly give me the ID of the root release
WITH cte_Releases
(
ReleaseId,
ParentReleaseID
)
AS(
SELECT
ReleaseId,
ParentReleaseID
FROM Releases
Where ReleaseId = 905
UNION ALL
SELECT
R.ReleaseId,
R.ParentReleaseID
FROM Releases R
INNER JOIN cte_Releases ON cte_Releases.ParentReleaseID = R.ReleaseId
)
SELECT max(ReleaseId) as ReleaseId, min(ReleaseId) as RootReleaseId FROM cte_Releases
My problem now is I want to run through all #IDs (905 in that code) and join each record to a result

Resources