i have a below described situation
EmpID Name SupervisorID
1 A 9
2 B 8
3 C 1
4 D 3
I need all the employees under supervisor ID 1
Here EmployeeID 3 is under 1 i need 4 is is also under 3
Following output is required.
EmpID Name SupervisorID
3 C 1
4 D 3
You need to Use Recursive CTE for this.Try this,
With CTE as
(
select EmpID,Name,SupervisorID from Emp
where SupervisorID =1
Union All
select a.EmpID,a.Name,a.SupervisorID from Emp as a
inner join CTE b on a.SupervisorID= b.EmpID
)
select * from CTE
Fiddle Demo Here
Please take a look at this question also, it is same like your question. Sql server CTE and recursion example
Nobody expects the Spanish Inquisition a HierarchyId. These days, whenever I see an org structure (like the one you have here), I reach for the HierarchyId datatype. In essence, it allows you to answer questions like "which value(s) are 'under' this one?" and "which value(s) does this one belong to?". Here's how I'd implement it:
alter table dbo.Employee add OrgStructure HierarchyId null;
with h as (
select EmployeeId, SupervisorId, '/' + cast(EmployeeId as varchar) + '/' as h
from dbo.Employee as e
where e.SupervisorId is null --employees w/o a supervisor
union all
select e.EmployeeId, e.SupervisorId, h.h + '/' + cast(EmployeeId as varchar) + '/'
from dbo.Employee as e
join h
on e.SupervisorId = h.SupervisorId
)
update e
set OrgStructure = h.h
from dbo.Employee as e
join h
on e.EmployeeId = h.EmployeeId;
create index [IX_Employee_OrgStructure] on dbo.Employee (OrgStructure)
Now that the heavy lifting is done, actually answering your problem is trivial:
select *
from dbo.Employee as supervisor
join dbo.Employee as reports
on reports.OrgStructure.IsDescendantOf(supervisor.OrgStructure)
where supervisor.EmployeeId = 1
The advantage that I see is that you're not calculating the hierarchy on the fly every time you need to answer this type of question. You do it once and you're done.
Related
I have a table which has both employees and team leaders.
I have below data
Declare #EMP AS TABLE(EmpID INT, EmpName VARCHAR(100),TeamLeadID INT)
INSERT INTO #EMP(EmpID, EmpName,TeamLeadID) VALUES
(1,'Ramesh',5)
,(2,'Mahesh',3)
,(3,'Vijay',3)
,(4,'Varma',5)
,(5,'Raj',5)
I want to list all employees along with thier teamleaderid and name. But the teamleaderid and name should be blank when the employeeid = teamleadid.
The result should be as follows.
EmpID EmpName TeamLeadID TeamLeadName
1 Ramesh 5 Raj
2 Mahesh 3 Vijay
3 Vijay NULL NULL
4 Varma 5 Raj
5 Raj NULL NULL
The code is as below.
SELECT E.EmpID,E.EmpName,TL.TeamLeadID,TL.EmpName AS TeamLeadName
FROM #Emp E
LEFT JOIN #Emp TL ON E.TeamLeadID = TL.EmpID AND E.EmpID ! = TL.EmpID
Is there any other way of rewriting this code with much performance (Mainly without using Not equal to at E.EmpID ! = TL.EmpID) and without using CTE?
If you want to avoid the NOT EQUAL in the WHERE clause, you can move your "logic" in the SELECT clause like this:
SELECT E.EmpID,E.EmpName,
IIF(TL.TeamLeadID=E.EmpID,NULL,TL.TeamLeadID) AS TeamLeadID ,
IIF(TL.EmpName=E.EmpName,NULL,TL.EmpName) AS TeamLeadName
FROM #Emp E
LEFT JOIN #Emp TL ON E.TeamLeadID = TL.EmpID
Whether it will be more performant is difficult to say. It depends on your data, and mostly affected records and number of teamleaders. Difference should be ridiculous if you don't have huge volumes
Another alternative is to use CASE WHEN clause in SELECT statement
SELECT E.EmpID,E.EmpName,
CASE
WHEN E.EmpID = TL.TeamLeadID
THEN NULL
ELSE TL.TeamLeadID
END AS TeamLeadID
,CASE
WHEN E.EmpID = TL.TeamLeadID
THEN NULL
ELSE TL.EmpName
END AS TeamLeadName
FROM #Emp E
LEFT JOIN #Emp TL ON E.TeamLeadID = TL.EmpID
(Select top 1 pvd.Code from PatientVisitDiags pvd
where pvd.PatientVisitId = pv.PatientVisitId
Order By pvd.Listorder) as "DX1",
(Select top 1 a.code from (Select top 2 pvd.Code,pvd.ListOrder from PatientVisitDiags pvd
where pvd.PatientVisitId = pv.PatientVisitId
Order By pvd.Listorder)a order by a.ListOrder DESC ) as "DX2",
(Select top 1 a.code from (Select top 3 pvd.Code,pvd.ListOrder from PatientVisitDiags pvd
where pvd.PatientVisitId = pv.PatientVisitId
Order By pvd.Listorder)a order by a.ListOrder DESC ) as "DX3",
(Select top 1 a.code from (Select top 4 pvd.Code,pvd.ListOrder from PatientVisitDiags pvd
where pvd.PatientVisitId = pv.PatientVisitId
Order By pvd.Listorder)a order by a.ListOrder DESC ) as "DX4",
(Select top 1 a.code from (Select top 5 pvd.Code,pvd.ListOrder from PatientVisitDiags pvd
where pvd.PatientVisitId = pv.PatientVisitId
Order By pvd.Listorder)a order by a.ListOrder DESC ) as "DX5"
The above code is what I am using currently (It is not optimal but is only being used once for a one time Data Export).
In the database that we are currently exporting from, there is a table PatientVisitDiags that has columns "ListOrder" and "Code". There can be between 1 and 5 codes. The ListOrder holds the number of that code. For example:
ListOrder|Code |
1 |M51.27 |
2 |M54.17 |
3 |G83.4 |
I am trying to export the Code to its corresponding Column in the new table(DX1,DX2..etc). If I sort by ListOrder I can get them in the order I need (Row 1 to DX1 | Row 2 to DX2 etc.) However when I run the above SQL code, If the source table only has 3 Codes DX4 and DX5 will repeat DX3. For Example:
DX1 |DX2 |DX3 |DX4 |DX5
M51.27 |M54.17 |G83.4 |G83.4 |G83.4
Is there a way to have TOP return NULL values if you Select TOP more than what is given? SQL Sever 2008 does not allow for OFFSET/FETCH, this is what I normally would have done given the option to select individual rows.
TL:DR
ID | Name
1 | Joe
2 | Eric
3 | Steve
4 | John
If I have a table like above and run
SELECT TOP 5 Name FROM Table
Is there anyway to return?
Joe
Eric
Steve
John
NULL
What you're really doing is pivoting. So pivot! Try this little query:
WITH Top5 AS (
SELECT TOP 5
Dx = 'DX' + Convert(varchar(11), Row_Number() OVER (ORDER BY pvd.Listorder)),
pvd.Code
FROM dbo.PatientVisitDiags pvd
WHERE pvd.PatientVisitId = #patientVisitId
)
SELECT *
FROM
Top5 t
PIVOT (Max(Code) FOR Dx IN (DX1, DX2, DX3, DX4, DX5)) p
;
To answer your second question about getting an unpivoted rowset, basically do the same thing but provide the 5 rows somehow and left join to the desired data.
WITH Data AS (
SELECT TOP 5
Seq = Row_Number() OVER(ORDER BY ID),
Name
FROM dbo.Table
ORDER BY ID
)
SELECT
n.Seq,
t.Name
FROM
(VALUES
(1), (2), (3), (4), (5) -- or a numbers-generating CTE perhaps
) n (Seq)
LEFT JOIN Top 5 t
ON n.Seq = t.Seq
;
Side note
The fact that you're doing this:
where pvd.PatientVisitId = pv.PatientVisitId
tells me you're not using ANSI joins. Stop. Don't do that any more. Put this join condition in the ON clause of a JOIN. It's the year 2016... why are you using join syntax from the last century?
Oh, and prefix the schema on the table names. Look it up--you'll find actual performance reasons why you should do that. It's not just about the time taken to find the correct schema, but also about the execution plan cache...
one at a time - answering the last question
create a table with a bunch of null
select top (5) col
from
(
select col from table1
union
select nulCol from nullTable
) tt
order by tt.col
I have a table PRODUCT that is basically set up so there is an PRODUCTID, PRODUCTNAME... it looks sort of like this
PRODUCTID PRODUCTNAME
100 PNAME1
101 PNAME2
102 PNAME3
Now I have to insert a record into new table PRODUCTMAPPING for each row in the PRODUCT.
so my new table PRODUCTMAPPING should look like this
PRODUCTMAPPINGID PRODUCTID
1 100
1 101
1 102
2 100
2 101
2 102
3 100
and so on ....
I tried doing while but I need it using Join.
Can we acheive this using joins ?
Thanks in advance.
One way;
select
row_number() over(partition by a.PRODUCTID order by a.PRODUCTID) PRODUCTMAPPINGID,
a.PRODUCTID
from PRODUCT a, PRODUCT b
Using LOOP
The following example specifies that the JOIN operation in the query is performed by a LOOP join.
Select sp.Name,spqh.quota
FROM Sales.SalesPersonQuotaHistory AS spqh
INNER LOOP JOIN Sales.SalesPerson AS sp
ON spqh.SalesPersonID = sp.SalesPersonID
WHERE sp.SalesYTD > 2500000.00;
GO
Refer this MSDN link
INSERT
INTO dbo.PRODUCTMAPPING
(
PRODUCTMAPPINGID
,PRODUCTID
)
SELECT pmv.PRODUCTMAPPINGID
,p.PRODUCTID
FROM dbo.Product p
CROSS JOIN
(
SELECT pm.ProductMappingID
FROM dbo.ProductMappingValues pmv -- WHERE DO THESE COME FROM?
) pmv
I have two tables, one for Employees and the second for records. I want to get total entries for each employee and order the results by max total entry like:
Daniel 3
David 1
tblEmployee:
EID Name
1 Daniel
2 David
tblEntry:
ID Column1 EID
1 XX 1
2 XX 1
3 XX 2
4 XX 1
try this:
select emp.EID,emp.Name,COUNT(etr.EID)
as total_entries from Employee emp join Entry etr
on emp.EID=etr.EID
group by emp.EID,emp.Name
You must use group by
select count(*) from tblEmployee ee, tblEntry e where ee.eid = e.eid group by ee.name
There are several variations on this, and you don't say what version of SQL Server you're using, but I like doing it this way:
;
using A
as (
select EID
, RecordCount = COUNT(*)
from tblEntry
group by
EID
)
select a.EID
, e.Name
, a.RecordCount
from A
join tblEmployee e
on A.EID = e.EID
order by
RecordCount desc
I like doing it this way rather than joining and then summarizing because you only have to group on the minimum number of fields. EID in tblEntry is likely to already have an index on it, while Name in tblEmployee may not.
I am new to recursive CTEs. I am trying to develop a CTE which will return all of the employees under each manager name. So I have two tables: people_rv and staff_rv
People_rv table contains all of the people, both managers and employees. Staff_rv only contains manager information. Uniqueidentifier staff values are stored in Staff_rv. Uniqueidentifier employee values are stored in people_rv. People_rv contains varchar first and last name values for both managers and employees.
But when I run the following CTE I get an error:
WITH
cteStaff (ClientID, FirstName, LastName, SupervisorID, EmpLevel)
AS
(
SELECT p.people_id, p.first_name, p.last_name, s.supervisor_id,1
FROM people_rv p JOIN staff_rv s on s.people_id = p.people_id
WHERE s.supervisor_id = '95E16819-8C3A-4098-9430-08F0E3B764E1'
UNION ALL
SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
INNER JOIN cteStaff r on s2.staff_id = r.ClientID
)
SELECT
FirstName + ' ' + LastName AS FullName,
EmpLevel,
(SELECT first_name + ' ' + last_name FROM people_rv p join staff_rv s on s.people_id = p.people_id
WHERE s.staff_id = cteStaff.SupervisorID) AS Manager
FROM cteStaff
OPTION (MAXRECURSION 0);
My output is:
Barbara G 1 Melanie K
Dawn P 1 Melanie K
Garrett M 1 Melanie K
Stephanie P 1 Melanie K
Amanda F 1 Melanie K
Amanda T 1 Melanie K
Stephanie G 1 Melanie K
Carlos H 1 Melanie K
So it is not iterating any more than the first level. Why not?
Melanie is the top most supervisor, but each of the persons in the leftmost column are also supervisors. So this query should also return level 2.
You may be in an infinite loop with your join. I would check how many levels you expect the table to actually go down. Generally you join a recursion on something similar to do
ID = ParentID
of something either contained in a table or in an expression. Keep in mind you can also create a CTE prior to a recursive CTE if you have to make up your relationship.
Here is an example that will self execute, it may help.
Declare #table table ( PersonId int identity, PersonName varchar(512), Account int, ParentId int, Orders int);
insert into #Table values ('Brett', 1, NULL, 1000),('John', 1, 1, 100),('James', 1, 1, 200),('Beth', 1, 2, 300),('John2', 2, 4, 400);
select
PersonID
, PersonName
, Account
, ParentID
from #Table
; with recursion as
(
select
t1.PersonID
, t1.PersonName
, t1.Account
--, t1.ParentID
, cast(isnull(t2.PersonName, '')
+ Case when t2.PersonName is not null then '\' + t1.PersonName else t1.PersonName end
as varchar(255)) as fullheirarchy
, 1 as pos
, cast(t1.orders +
isnull(t2.orders,0) -- if the parent has no orders than zero
as int) as Orders
from #Table t1
left join #Table t2 on t1.ParentId = t2.PersonId
union all
select
t.PersonID
, t.PersonName
, t.Account
--, t.ParentID
, cast(r.fullheirarchy + '\' + t.PersonName as varchar(255))
, pos + 1 -- increases
, r.orders + t.orders
from #Table t
join recursion r on t.ParentId = r.PersonId
)
, b as
(
select *, max(pos) over(partition by PersonID) as maxrec -- I find the maximum occurrence of position by person
from recursion
)
select *
from b
where pos = maxrec -- finds the furthest down tree
-- and Account = 2 -- I could find just someone from a different department
Your problem as far as I can tell is is you have no join connecting managers to their employees.
This join
INNER JOIN cteStaff r on r.StaffID = s2.staff_id
Just joins the same initial level 1 staffer back to himself.
UPDATE:
Still not quite right! You have a supervisor_id, but again you're still not actually using that to join back to the CTE.
So for each recursion of this CTE you need to (excluding the name join):
select {Level 1 Boss}, NULL (no supervisor)
union
select {new employee}, {that employee's boss}
So the join must connect the CTE's ClientID (the level 1 boss) to the second UNION query's supervisor field, which looks to be supervisor_id , not staff_id.
The JOIN to accomplish this second task is (from what I can tell of your staff_rv table schema):
SELECT p2.people_id, p2.first_name, p2.last_name, s2.supervisor_id, r.EmpLevel + 1
FROM people_rv p2 JOIN staff_rv s2 on s2.people_id = p2.people_id
INNER JOIN cteStaff r on s2.supervisor_id = r.ClientID
Note the bottom join joins the r.ClientID (the level 1 boss) to the staffer's supervisor_id field.
(NB: I think your staff_id and supervisor_id's mimic your people_id values from the people_rv table, so this join should work fine. But if they are different (i.e. a staffer's supervisor_id isn't that supervisor's people_id) then you'll need to write the join such that the staffer's supervisor_id can be joined to their people_id you're storing as ClientID in the CTE.)
Here's a good simple Recursive CTE to review (it may not be the answer, but someone else searching on how to make a recursive CTE may need it):
-- Recursive CTE
;
WITH Years ( myYear )
AS (
-- Base case
SELECT DATEPART(year, GETDATE())
UNION ALL
-- Recursive
SELECT Years.myYear - 1
FROM Years
WHERE Years.myYear >= 2002
)
SELECT *
FROM Years
Note that this probably won't solve your problem, but is a means to hopefully seeing where you're going wrong in the original query.
The default is 100 levels of recursion - you can set it to unlimited by using the MAXRECURSION query hint where you're selecting from your CTE:
...
FROM cteStaff
OPTION (MAXRECURSION 0);
From MSDN:
MAXRECURSION number
Specifies the maximum number of recursions allowed for this query. number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied. If this option is not specified, the
default limit for the server is 100.
When the specified or default number for MAXRECURSION limit is reached during query execution, the query is ended and an error is
returned.
Because of this error, all effects of the statement are rolled back. If the statement is a SELECT statement, partial results or no
results may be returned. Any partial results returned may not include
all rows on recursion levels beyond the specified maximum recursion
level.