Recursive CTE query in SQL Server - sql

I've looked at many recursive CTE examples on this site already and I've tried applying this to my own data. I seem to have a few more joins than most examples but I think I'm getting there. The problem I want help with is that I get an error saying:
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
I have run the first select statement of the CTE and I've repeatedly run the second select statement (with different actual position codes) and for this employee there are six levels so I can't see why I get the error.
My one area of suspicion is that I have to apply the criteria 'relationship_type_id = 0' as this is the 'Reports To' relationship type. I had tried making this a left outer join but they aren't allowed. This means I don't get the top level manager using this query (i.e. the person with no manager themselves).
I've posted the code below and any help would be much appreciated.
WITH EmployeeHierarchy (EmployeeID, LastName, FirstName, PositionCode, ReportsTo, HierarchyLevel) AS
(
SELECT
p.employee_id as EmployeeID,
p.last_name as LastName,
p.first_name as FirstName,
pos.position_code as PositionCode,
r.to_position_code as ReportsTo,
1 as HierarchyLevel
FROM
--JOIN: Personal details
dbo.person p
--JOIN: Employment links a person to a post (could have more than one)
INNER JOIN
dbo.employment e ON e.employee_id = p.employee_id
--JOIN: details of the position held
INNER JOIN
dbo.position pos ON pos.position_code = e.position_code
--JOIN: Relationships between the positions, one position reports to another position etc.
-- There are several 'relationship types', we are only interested in relationship_type_id = 0
-- as this is the 'Reports to' relationship code. Others types include 'Managed by' etc.
INNER JOIN
dbo.relationship r ON r.from_position_code = pos.position_code AND r.relationship_type_id = 0
WHERE
--CRITERIA: Use my employee Id as a starting point for testing
p.employee_id = '10076395'
UNION ALL
-- Recursive step
SELECT
p2.employee_id as EmployeeID,
p2.last_name as LastName,
p2.first_name as FirstName,
pos2.position_code as PositionCode,
r2.to_position_code as ReportsTo,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM
dbo.person p2
INNER JOIN
dbo.employment e2 ON e2.employee_id = p2.employee_id
INNER JOIN
dbo.position pos2 ON pos2.position_code = e2.position_code
INNER JOIN
dbo.relationship r2 ON r2.from_position_code = pos2.position_code AND r2.relationship_type_id = 0
--JOIN: Link this query back to the base query
INNER JOIN
EmployeeHierarchy eh ON r2.from_position_code = eh.PositionCode
)
SELECT *
FROM EmployeeHierarchy
ORDER BY HierarchyLevel, LastName, FirstName

Give this a try.
I've moved the complex joins to a separate CTE to keep the recursive section as clean and as simple as possible. If nothing else this approach should help you pinpoint your issue.
; WITH employees AS (
SELECT person.employee_id
, person.last_name
, person.first_name
, position.position_code
, relationship.to_position_code
FROM dbo.person
INNER
JOIN dbo.employment
ON employment.employee_id = person.employee_id
INNER
JOIN dbo.position
ON position.position_code = employment.position_code
INNER
JOIN dbo.relationship
ON relationship.from_position_code = position.position_code
AND relationship.relationship_type_id = 0
)
, recursive_bit AS (
SELECT employee_id
, last_name
, first_name
, position_code
, to_position_code
FROM employees
WHERE employee_id = '10076395'
UNION ALL
SELECT employees.employee_id
, employees.last_name
, employees.first_name
, employees.position_code
, employees.to_position_code
FROM recursive_bit
INNER
JOIN employees
ON employees.position_code = recursive_bit.to_position_code
)
SELECT *
FROM recursive_bit

Please try :
WITH EmployeeHierarchy (EmployeeID, LastName, FirstName, PositionCode, ReportsTo, HierarchyLevel) AS
(
SELECT
p.employee_id as EmployeeID,
p.last_name as LastName,
p.first_name as FirstName,
pos.position_code as PositionCode,
r.to_position_code as ReportsTo,
1 as HierarchyLevel
FROM
--JOIN: Personal details
dbo.person p
--JOIN: Employment links a person to a post (could have more than one)
INNER JOIN dbo.employment e
ON e.employee_id = p.employee_id
--JOIN: details of the position held
INNER JOIN dbo.position pos
ON pos.position_code = e.position_code
--JOIN: Relationships between the positions, one position reports to another position etc.
-- There are several 'relationship types', we are only interested in relationship_type_id = 0
-- as this is the 'Reports to' relationship code. Others types include 'Managed by' etc.
INNER JOIN dbo.relationship r
ON r.from_position_code = pos.position_code
and r.relationship_type_id = 0
WHERE
--CRITERIA: Use my employee Id as a starting point for testing
p.employee_id = '10076395'
UNION ALL
-- Recursive step
SELECT
p2.employee_id as EmployeeID,
p2.last_name as LastName,
p2.first_name as FirstName,
pos2.position_code as PositionCode,
r2.to_position_code as ReportsTo,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM
dbo.person p2
INNER JOIN dbo.employment e2
ON e2.employee_id = p2.employee_id
INNER JOIN dbo.position pos2
ON pos2.position_code = e2.position_code
INNER JOIN dbo.relationship r2
ON r2.from_position_code = pos2.position_code
and r2.relationship_type_id = 0
--JOIN: Link this query back to the base query
INNER JOIN EmployeeHierarchy eh
ON r2.from_position_code = eh.ReportsTo
)
SELECT *
FROM EmployeeHierarchy
ORDER BY HierarchyLevel, LastName, FirstName

Related

Stored procedure that selects from multiple group the results then order each group in descending order?

Here is the list of tables:
My Tables
I will like to create a stored procedure that will use the athleteId as parameter for the query and select all the columns in the result table and a few columns in each of the other tables Group the results by the EventName of the Event Table then order the groups by the Mark in descending order from the Result table and select the last record for each event.
I have search the net and tried various queries but cant seem to get the results I need.
This is hat I created. It retrieves all the info I need but I just don't seem to be able to group, order and select the lowest mark.
SELECT
a.Id as AthleteId, a.FirstName AS FirstName, a.LastName AS LastName,
a.BirthDate AS BirthDate, a.IsMale AS Male,
a.Phone AS Phone, a.Email AS Email,
p.FirstName AS ParentName, p.LastName AS ParentSurname,
p.Phone AS ParentPhone, p.Email AS ParentEmail,
ad.Street1 AS Street1, ad.Street2 AS Street2, ad.Town AS Town,
ad.Parish AS Parish, ad.Country AS Country,
s.SchoolName AS School, s.Phone AS SchoolPhone,
s.[Location] AS SchoolLocation,
c.FirstName AS CoachName, c.LastName AS CoachSurname,
c.Phone AS CoachPhone, c.Email AS CoachEmail,
m.MeetName AS MeetName,
m.StartDate AS StartDate, m.EndDate AS EndDate, m.[Location] AS MeetLocation,
e.EventName AS EventName,
r.Mark AS EventMark, r.Wind AS Wind, r.PerfDate AS PerfDate
FROM
dbo.Result r
INNER JOIN
dbo.[Event] e ON e.Id = r.EventId
INNER JOIN
dbo.Meet m ON m.Id = r.MeetId
INNER JOIN
dbo.Athlete a
JOIN
dbo.Parent p ON p.Id = a.ParentId
JOIN
dbo.[Address] ad ON ad.Id = a.AddressId
JOIN
dbo.Coach c ON c.Id = a.CoachId
JOIN
dbo.School s ON s.Id = a.SchoolId
ON a.Id = r.AthleteId
WHERE
r.AthleteId = #AthleteId
When I try adding the group by it shows an error.
Can anyone help me with this?
A quick-and-dirty way to do this in SQL Server is:
select top (1) with ties . . .
. . .
. . .
order by row_number() over (partition by r.eventid order by r.mark desc)

SQL explicit join filter condition

Using this relational schema, patient ID and staff ID are foreign keys with id.staff and pid.patient being unique keys:
staff(ID, fname, lname, role)
patient(pID, pFname, pLname, bdate, address, phone)
appointment(aptID, patientID, staffID, aptDate, aptTime)
procedures(procNum, pName, price)
aptDetail(aptID, procNo)
So say if I wanted to list the names of patients with appointments with a specific staff member, i.e John Smith, how would I do that explicitly?
I've managed implicitly, but I know this is kind of frowned upon, but I can't reach it without using WHERE statements.
Any help would be appreciated, any time I try and use INNER JOIN I seem to hit a wall if it's not a simple join.
Is this the type of query you're looking for?
select distinct pFname, pLname
from patient p
join appointment a on p.pID = a.patientID
join staff s on a.staffID = s.ID
where s.fname = 'John' and s.lname = 'Smith'
You can use inner join
select
a.pID
, a.pFname
, a.pLname
, a.bdate
, a.address
, a.phone
, b.aptDate
, b.aptTime
, c.fname
, c.lname
, c.role
from patient a
INNER JOIN appointment b on b.patientID = a.pID
INNER JOIN staff c on b.staffID = c.ID on concat(fname, ' ', lname ) ='John Smith'
Something like the following should work fine:
SELECT p.*
FROM appointment AS a
INNER JOIN staff AS s ON a.staffID = s.pID
INNER JOIN patient AS p ON a.patientID = p.pID
WHERE s.ID = <yourstaffid>;
select staff.fname, staff.role,
patient.pfname, plname, appoitment.aotdate
from staff, patient, appointment
where patient.pid=appointment.patientId
and staff.id=appointment.staffid

Find distinct values from the generated result set in SQL

Two select statements union together with multiple inner joins.
Just need to find out the distinct elements out of this result sets.
I could have write distinct when selecting columns but this is not what I want.
SELECT
employeeid, employeename
FROM
employee AS emp
INNER JOIN
department AS dep ON emp.employeeid = dep.employeeid
INNER JOIN
company AS comp ON emp.companyid = comp.companyid
UNION
SELECT
employeeid, employeename
FROM
employer AS emp
INNER JOIN
category AS cat ON emp.employeeid = cat.employeeid
INNER JOIN
business AS busi ON emp.companyid = busi.companyid
SELECT DISTINCT employeeid, employeename
FROM
(
SELECT
employeeid, employeename
FROM
employee AS emp
INNER JOIN
department AS dep ON emp.employeeid = dep.employeeid
INNER JOIN
company AS comp ON emp.companyid = comp.companyid
UNION
SELECT
employeeid, employeename
FROM
employer AS emp
INNER JOIN
category AS cat ON emp.employeeid = cat.employeeid
INNER JOIN
business AS busi ON emp.companyid = busi.companyid
) AS t

SQL Server query listing duplicate values

I have a SQL query that pulls a list of contacts from a small database; it pulls the data just fine. However the problem with the SQL is the fact that it will list the same contact multiple times depending on the number of emails and address associated with the contact.
It's only supposed to list it once. Here's the SQL I've written:
SELECT DISTINCT
Contact_Main.contactid, first_name, last_name, p_title,
prefix, suffix, email, address1, address2,
city, state, zip, Contact_Address_Type.addresstype,
Contact_Email_Types.emailtype,
addressid, emailid, Contact_Address_Type.addresstypeid,
Contact_Email_Types.emailtypeid,
(SELECT COUNT(addressid)
FROM Contact_Address
WHERE contactid = Contact_Main.contactid) as num_addresses,
(SELECT COUNT(emailid)
FROM Contact_Email
WHERE contactid = Contact_Main.contactid) as num_emails
FROM Contact_Main
LEFT JOIN Contact_Email ON Contact_Main.contactid =
Contact_Email.contactid
LEFT JOIN Contact_Email_Types ON Contact_Email_Types.emailtypeid =
Contact_Email.emailtypeid
LEFT JOIN Contact_Address ON Contact_Address.contactid =
Contact_Main.contactid
LEFT JOIN Contact_Address_Type ON Contact_Address_Type.addresstypeid =
Contact_Address.addresstypeid
ORDER BY last_name, first_name;
I'm sure it's a simple fix and that I need to make some part of this a sub query but for some reason I can't figure it out.
so this works fine ?
SELECT DISTINCT
cm.contactid
, first_name
, last_name
, p_title
, prefix
, suffix
, email
, address1
, address2
, city
, state
, zip
, cat.addresstype
, cet.emailtype
, addressid
, emailid
, cat.addresstypeid
, cet.emailtypeid
--, (SELECT COUNT(addressid)
-- FROM Contact_Address
-- WHERE contactid = cm.contactid) as num_addresses
--, (SELECT COUNT(emailid)
-- FROM Contact_Email
-- WHERE contactid = cm.contactid) as num_emails
FROM Contact_Main cm
LEFT JOIN Contact_Email ce ON cm.contactid =
ce.contactid
LEFT JOIN Contact_Email_Types cet ON cet.emailtypeid =
ce.emailtypeid
LEFT JOIN Contact_Address ON ca.contactid =
cm.contactid
LEFT JOIN Contact_Address_Type ON cat.addresstypeid =
ca.addresstypeid
ORDER BY cm.last_name, cm.first_name;
Okay, so when I was at work I asked my boss what was the deal with SQL code. We both took a look at it and he determined that it was returning every possible outcome. After messing around with a simulation in SQL Access it turns out we had to do a LEFT OUTER JOIN instead of just a LEFT JOIN.
SELECT DISTINCT Contact_Main.contactid, first_name, last_name, p_title,
prefix, suffix, email, address1, address2,
city, state, zip, Contact_Address_Type.addresstype,
Contact_Email_Types.emailtype,
addressid, emailid, Contact_Address_Type.addresstypeid,
Contact_Email_Types.emailtypeid,
(SELECT COUNT(addressid) FROM Contact_Address
WHERE contactid = Contact_Main.contactid) as num_addresses,
(SELECT COUNT(emailid) FROM Contact_Email
WHERE contactid = Contact_Main.contactid) as num_emails
FROM Contact_Main
LEFT OUTER JOIN Contact_Email
ON Contact_Main.contactid = Contact_Email.contactid
LEFT OUTER JOIN Contact_Email_Types
ON Contact_Email_Types.emailtypeid = Contact_Email.emailtypeid
LEFT OUTER JOIN Contact_Address
ON Contact_Address.contactid = Contact_Main.contactid
LEFT OUTER JOIN Contact_Address_Type
ON Contact_Address_Type.addresstypeid = Contact_Address.addresstypeid
ORDER BY last_name, first_name;
And that did the trick. Thanks for all the suggestions!

SQL Server 2005 - Nested recursive query :(

I have a query that I need to execute that I do not know how to structure.
I have a table called Employees. I have another table called Company. There is a third table called Files. As you can imagine, a Company has Employees, and Employees have Files.
I need to list out all of the Employees in my database. The challenge is, I need to list the total number of Files in the same company as the Employee. I have tried variations on the following without any luck:
SELECT
e.FirstName,
e.LastName,
e.Company,
(SELECT COUNT(*) FROM Files f WHERE f.EmployeeID IN (SELECT [ID] FROM Employees e2 WHERE e2.CompanyID=e.CompanyID)) as 'FileCount'
FROM
Employees e
What am I doing wrong? Thank you!
Try this:
SELECT
e.FirstName,
e.LastName,
e.Company,
(
SELECT COUNT(*)
FROM Files f
JOIN Employees e2 ON f.EmployeeID = e2.id
WHERE e2.CompanyID = e.CompanyID
) as 'FileCount'
FROM
Employees e
There are a lot of ways to get that. If the performance is a concern, this is more optimal according to estimated execution plan costs.
SELECT
e.FirstName,
e.LastName,
e.Company,
COUNT(f.FileId)
FROM
Employees e
INNER JOIN Files f ON e.EmployeeID = f.EmployeeID
GROUP BY
e.FirstName,
e.LastName,
e.Company
A solution with no correlation in SELECT clause. Probably quicker...
SELECT
e.FirstName,
e.LastName,
e.Company,
foo.FileCount
FROM
Employees e
JOIN
(
SELECT
COUNT(*) AS FileCount, --OR COUNT(DISTINCT something) ?
e2.Company, f.EmployeeID
FROM
Files f JOIN Employees e2 ON f.EmployeeID = e2.id
GROUP BY
e2.Company, f.EmployeeID
) foo ON e.Company = foo.Company AND e.id = foo.EmployeeID
How about:
SELECT
e.FirstName,
e.LastName,
e.Company,
select count(*) from Files f, Employees e where f.EmployeeID=e.EmployeeID and e.CompanyID=emp.CompanyID
FROM
Employees emp
WITH FilesPerCompany (CompanyID, NumberOfFiles)
AS (SELECT c.ID AS CompanyID,
COUNT(*) AS NumberOfFiles
FROM Companies c
INNER JOIN Employees e ON c.ID = e.CompanyID
INNER JOIN Files f ON e.ID = f.EmployeeID
GROUP BY c.ID
)
SELECT e.FirstName,
e.LastName,
e.Company,
COALESCE(s.NumberOfFiles, 0) AS NumberOfFilesPerCompany
FROM Employees e
LEFT JOIN FilesPerCompany s
ON s.CompanyID = e.CompanyID
The following statement uses recursive joins to iterate down employees who manage other employees who manage other employees .... etc. Our structure is a little convoluted as the management structure is role based which actually allows an employee to have more than 1 manager. You can add a reference to Files within this recursion.
WITH Manager as
(SELECT c.Forenames + ' ' + c.Surname as Employee,
c2.Forenames + ' ' + c2.Surname AS Manages,
c.accountid AS AccountID, c.[Status] AS [Status]
FROM [intranet].[dbo].[tblContact] c
LEFT JOIN tblContactPost cp ON cp.contactid = c.contactid
LEFT JOIN tblPost p ON p.ParentRoleId = cp.RoleID AND p.ParentPostArea = cp.PostArea AND p.ParentPostNo = cp.PostNo
INNER JOIN tblContactPost cp2 ON cp2.RoleId = p.RoleId AND cp2.PostArea = p.PostArea AND cp2.PostNo = p.PostNo
INNER JOIN tblContact c2 ON c2.ContactID = cp2.ContactId
)
,jn AS
(SELECT Employee, Manages
FROM Manager
Where AccountID = 'ad\lgardner' AND [Status] = 'A'
UNION ALL
SELECT c.Employee, c.Manages
FROM jn as p JOIN Manager AS c
ON c.Employee = p.Manages
)
SELECT jn.Employee, jn.Manages
From jn
Order BY 1