sql server 2008 CTE Error - sql-server

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

Related

Recursive query SQL Server not working as expected

thanks in advance for you help. I'm still quite new to MS SQL db but I was wondering why my recursive query for MSSQL below does not return the value i'm expecting. I've done my research and at the bottom is the code I came up with. Lets say I have the following table...
CategoryID ParentID SomeName
1 0 hmm
2 0 err
3 0 woo
4 3 ppp
5 4 ttt
I'm expecting the query below to return 3 4 5. I basically wanted to get the list of category id's heirarchy below it self inclusive based on the category id I pass in the recursive query. Thanks for you assistance.
GO
WITH RecursiveQuery (CategoryID)
AS
(
-- Anchor member definition
SELECT a.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS a
WHERE a.ParentID = CategoryID
UNION ALL
-- Recursive member definition
SELECT b.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS b
INNER JOIN RecursiveQuery AS d
ON d.CategoryID = b.ParentID
)
-- Statement that executes the CTE
SELECT o.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS o
INNER JOIN RecursiveQuery AS d
ON d.CategoryID = 3
GO
If you want tree from specific root:
DECLARE #rootCatID int = 3
;WITH LessonsTree (CatID)
AS
(
SELECT a.CategoryID
FROM [EducationDatabase].[dbo].[LessonCategory] AS a
WHERE a.CategoryID = #rootCatID ---<<<
UNION ALL
SELECT b.CategoryID
FROM LessonsTree as t
INNER JOIN [EducationDatabase].[dbo].[LessonCategory] AS b
ON b.ParentID = t.CatID
)
SELECT o.*
FROM LessonsTree t
INNER JOIN [EducationDatabase].[dbo].[LessonCategory] AS o
ON o.CategoryID = t.CatID
As stated in the comments, the anchor isn't restricted. Easiest solution is to add the criterium in the anchor
with RecursiveQuery (theID)
AS
(
SELECT a.ParentID --root id=parentid to include it and to prevent an extra trip to LessonCategory afterwards
FROM [LessonCategory] AS a
WHERE a.ParentID = 3 --restriction here
UNION ALL
SELECT b.CategoryID
FROM [LessonCategory] AS b
INNER JOIN RecursiveQuery AS d
ON d.theID = b.ParentID
)
SELECT* from RecursiveQuery
Another option is to have the recursive query be general (no restricted anchor) and have it keep the rootid as well. Then the query on the cte can restrict on the rootid (the first option is probably better, this second one is mainly suitable if you are created some sort of root-view)
with RecursiveQuery
AS
(
SELECT a.ParentID theID, a.ParentID RootID
FROM [LessonCategory] AS a
UNION ALL
SELECT b.CategoryID, d.RootID
FROM [LessonCategory] AS b
INNER JOIN RecursiveQuery AS d
ON d.theID = b.ParentID
)
SELECT theID from RecursiveQuery where RootID = 3

How to use GROUPING function in SQL common table expression - CTE

I have the below T-SQL CTE code where i'm trying to do some row grouping on four columns i.e Product, ItemClassification, Name & Number.
;WITH CTE_FieldData
AS (
SELECT
CASE(GROUPING(M.CodeName))
WHEN 0 THEN M.CodeName
WHEN 1 THEN 'Total'
END AS Product,
CASE(GROUPING(KK.ItemClassification))
WHEN 0 THEN KK.[ItemClassification]
WHEN 1 THEN 'N/A'
END AS [ItemClassification],
CASE(GROUPING(C.[Name]))
WHEN 0 THEN ''
WHEN 1 THEN 'Category - '+ '('+ItemClassification+')'
END AS [Name],
CASE(GROUPING(PYO.Number))
WHEN 0 THEN PYO.Number
WHEN 1 THEN '0'
END AS [Number],
ISNULL(C.[Name],'') AS ItemCode,
MAX(ISNULL(PYO.Unit, '')) AS Unit,
MAX(ISNULL(BT.TypeName, '')) AS [Water Type],
MAX(ISNULL(PYO.OrderTime, '')) AS OrderTime,
MAX(ISNULL(BUA.Event, '')) AS Event,
MAX(ISNULL(PYO.Remarks, '')) AS Remarks,
GROUPING(M.CodeName) AS ProductGrouping,
GROUPING(KK.ItemClassification) AS CategoryGrouping,
GROUPING(C.[Name]) AS ItemGrouping
FROM CTable C INNER JOIN CTableProducts CM ON C.Id = CM.Id
INNER JOIN MyData R ON R.PId = CM.PId
INNER JOIN MyDataDetails PYO ON PYO.CId = C.CId AND PYO.ReportId = R.ReportId
INNER JOIN ItemCategory KK ON C.KId = KK.KId
INNER JOIN Product M ON R.ProductId = M.ProductId
INNER JOIN WaterType BT ON PYO.WId = BT.WId
INNER JOIN WaterUnit BUA ON PYO.WUId = BUA.WUId
WHERE R.ReportId = 4360
GROUP BY M.CodeName, KK.ItemClassification, C.Name, PYO.Number
WITH ROLLUP
)
SELECT
Product,
[Name] AS Category,
Number,
Unit as ItemCode,
[Water Type],
OrderTime,
[Event],
[Comment]
FROM CTE_FieldData
Below are the issues/problems with the data being returned by the script above and they are the ones i'm trying to fix.
At the end of each ItemClassification grouping, i extra record is being added yet it does not exist in the table. (See line number 4 & 10 in the sample query results screenshot attached).
I want the ItemClassification grouping in column 2 to be at the beginning of the group not at the end of the group.
That way, ItemClassification "Category- (One)" would be at line 1 not the current line 5.
Also ItemClassification "Category- (Two)" would be at line 5 not the current line 11
Where the "ItemClassification" is displaying i would like to have columns (Number, ItemCode, [Water Type], [OrderTime], [Event], [Comment]) display null.
In the attached sample query results screenshot, those would be rows 11 & 5
The last row (13) is also unwanted.
I'm trying to understand SQL CTE and the GROUPING function but i'm not getting things right.
It looks like this is mostly caused by WITH ROLLUP and GROUPING. ROLLUP allows you to make essentially a sum line for your groupings. When you have WITH ROLLUP, it will give you NULL values for all of your non-aggregated fields in your select statement. You use GROUPING() in conjunction with ROLLUP to then label those NULL's as 'Total' or '0' or 'Category' as your query does.
1) Caused by GROUPING and ROLLUP. Take away both and this should be resolved.
2) Not sure what determines your groups and what would be defined as beginning or end. Order BY should suffice
3) Use ISNULL or CASE WHEN. If the Item Classification has a non null or non blank value, NULL each field out.
4) Take off WITH ROLLUP.

CTE goes recursive and fails

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)

Insert Statement Error Message

Good Morning All
My boss helped me design a query where it populates 1.37 million lines of random data, he has now asked me to insert/update the results into a blank table. But for some reason I cannot get it to work.
The three columns are ArrivalDate, PitchType_Skey and Site_Skey. But when I run my query (See below) I get an error message and I don't know why. Can you help?
Msg 121, Level 15, State 1, Line 2
The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.
Query:
USE Occupancy
INSERT INTO Bookings (ArrivalDate, Site_Skey, PitchType_Skey)
SELECT
Time.Date, Site.Site_Skey, Site.SiteWeighting, PitchType.PitchType_Skey,
PitchType.PitchTypeWeighting,
RAND(checksum(NEWID())) * Site.SiteWeighting * PitchType.PitchTypeWeighting AS Expr1
FROM
Capacity
INNER JOIN
Site ON Capacity.Site_Skey = Site.Site_Skey
INNER JOIN
PitchType ON Capacity.PitchType_Skey = PitchType.PitchType_Skey
INNER JOIN
Time
INNER JOIN
AGKey ON Time.ArrivalDayWeighting = AGKey.[Key] ON Capacity.StartDate <= Time.Date AND Capacity.EndDate >= Time.Date
CROSS JOIN
(SELECT 0 AS col1
UNION ALL
SELECT 1 AS col1) AS aaav
WHERE
(Time.CalendarYear = 2010)
AND (RAND(checksum(NEWID())) * Site.SiteWeighting * PitchType.PitchTypeWeighting >= 1.22)
Thanks
Wayne
The error message give you the answer. You have more items in your SELECT list (6)
Time.Date
Site.Site_Skey
Site.SiteWeighting
PitchType.PitchType_Skey
PitchType.PitchTypeWeighting
RAND(checksum(NEWID())) * Site.SiteWeighting * PitchType.PitchTypeWeighting AS Expr1
Than you do in your INSERT list (3)
ArrivalDate
Site_Skey
PitchType_Skey
Either remove some columns from your SELECT list or add some to your INSERT list.
As you haven't given the complete structure of your Bookings table I can only guess that you will need to do this
USE Occupancy
INSERT INTO Bookings
(
ArrivalDate,
Site_Skey,
PitchType_Skey
)
SELECT
Time.Date,
Site.Site_Skey,
PitchType.PitchType_Skey
FROM
Capacity
INNER JOIN Site ON Capacity.Site_Skey = Site.Site_Skey
INNER JOIN PitchType ON Capacity.PitchType_Skey = PitchType.PitchType_Skey
INNER JOIN Time
INNER JOIN AGKey ON Time.ArrivalDayWeighting = AGKey.[Key] ON Capacity.StartDate <= Time.Date AND Capacity.EndDate >= Time.Date
CROSS JOIN
(
SELECT 0 AS col1
UNION ALL
SELECT 1 AS col1
) AS aaav
WHERE
Time.CalendarYear = 2010
AND (RAND(checksum(NEWID())) * Site.SiteWeighting * PitchType.PitchTypeWeighting >= 1.22)
I have found the solution and I cant believe how easy it was, I just un-ticked the boxes I didn't want on the Query Designer.

Join the table valued function in the query

I have one table vwuser. I want join this table with the table valued function fnuserrank(userID). So I need to cross apply with table valued function:
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
For each userID it generates multiple records. I only want the last record for each empid that does not have a Rank of Term(inated). How can I do this?
Data:
HistoryID empid Rank MonitorDate
1 A1 E1 2012-8-9
2 A1 E2 2012-9-12
3 A1 Term 2012-10-13
4 A2 E3 2011-10-09
5 A2 TERM 2012-11-9
From this 2nd record and 4th record must be selected.
In SQL Server 2005+ you can use this Common Table Expression (CTE) to determine the latest record by MonitorDate that doesn't have a Rank of 'Term':
WITH EmployeeData AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY empId, ORDER BY MonitorDate DESC) AS RowNumber
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
WHERE Rank != 'Term'
)
SELECT *
FROM EmployeeData AS ed
WHERE ed.RowNumber = 1;
Note: The statement before this CTE will need to end in a semi-colon. Because of this, I have seen many people write them like ;WITH EmployeeData AS...
You'll have to play with this. Having trouble mocking your schema on sqlfiddle.
Select bar.*
from
(
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
where rank != 'TERM'
) foo
left join
(
SELECT *
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empId = bar.empId
and foo.MonitorDate > bar.MonitorDate
where bar.empid is null
I always need to test out left outers on dates being higher. The way it works is you do a left outer. Every row EXCEPT one per user has row(s) with a higher monitor date. That one row is the one you want. I usually use an example from my code, but i'm on the wrong laptop. to get it working you can select foo., bar. and look at the results and spot the row you want and make the condition correct.
You could also do this, which is easier to remember
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
) foo
join
(
select empid, max(monitordate) maxdate
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empid = bar.empid
and foo.monitordate = bar.maxdate
I usually prefer to use set based logic over aggregate functions, but whatever works. You can tweak it also by caching the results of your TVF join into a table variable.
EDIT:
http://www.sqlfiddle.com/#!3/613e4/17 - I mocked up your TVF here. Apparently sqlfiddle didn't like "go".
select foo.*, bar.*
from
(
SELECT f.*
FROM vwuser AS a
join fnuserrank f
on a.empid = f.empid
where rank != 'TERM'
) foo
left join
(
SELECT f1.empid [barempid], f1.monitordate [barmonitordate]
FROM vwuser AS b
join fnuserrank f1
on b.empid = f1.empid
where rank != 'TERM'
) bar
on foo.empId = bar.barempid
and foo.MonitorDate > bar.barmonitordate
where bar.barempid is null

Resources