Get all employees under manager with CTE - sql-server

I have a table which has employee details
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 NULL
5 2 3 NULL
6 2 2 NULL
7 1 1 NULL
8 5 0 NULL
Now, I have to start wil Employee Id 2 and found all it's low level hirerachy (i.e. 2, 4, 5, 6, 8) and assign them value same as "2" (i.e. EMP).
Expected output :
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 EMP
5 2 3 EMP
6 2 2 EMP
7 1 1 NULL
8 5 0 EMP
What I am trying:
; WITH LevelHire AS
(
SELECT EmpId, ManagerId,Level
FROM EmployeeTable
WHERE EmpId =2
UNION ALL
SELECT Lh.EmpId, RC.ManagerId, Lh.Level
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.EmpId
)
SELECT * FROM LevelHire
option (maxrecursion 0)
How can I achieve the same?

you can try something like this
;WITH EmployeeTable AS
(
SELECT 1 EmpId,0 ManagerId , 5 Level ,'CEO' Value
UNION ALL SELECT 2,1, 4,'EMP'
UNION ALL SELECT 3,1, 4,'ORG'
UNION ALL SELECT 4,2, 3,NULL
UNION ALL SELECT 5,2, 3,NULL
UNION ALL SELECT 6,2, 2,NULL
UNION ALL SELECT 7,1, 1,NULL
UNION ALL SELECT 8,5, 0,NULL
),LevelHire AS
(
SELECT EmpId, ManagerId,Level,Value
FROM EmployeeTable
WHERE EmpId = 2
UNION ALL
SELECT RC.EmpId, RC.ManagerId, Lh.Level,LH.Value
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.ManagerId
)
SELECT E.EmpId, E.ManagerId,E.Level,ISNULL(E.Value ,LH.Value) Value
FROM EmployeeTable E
LEFT JOIN LevelHire LH
ON E.EmpId = LH.EmpId

Time to learn about hierarchyid. First, some code:
IF object_id('tempdb.dbo.#employees') IS NOT NULL
DROP TABLE #employees;
go
WITH Employees AS (
SELECT *
FROM ( VALUES
( 1, NULL, 5, 'CEO'),
( 2, 1, 4, 'EMP'),
( 3, 1, 4, 'ORG'),
( 4, 2, 3, NULL ),
( 5, 2, 3, NULL ),
( 6, 2, 2, NULL ),
( 7, 1, 1, NULL ),
( 8, 5, 0, NULL )
) AS x ( EmpId, ManagerId, Level, Value )
), rcte AS (
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value,
CAST('/' + CAST(e.EmpId AS VARCHAR) + '/' AS VARCHAR(MAX)) AS h
FROM Employees AS e
WHERE e.ManagerId IS NULL
UNION ALL
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
m.h + CAST(e.EmpId AS VARCHAR) + '/' AS h
FROM Employees AS e
JOIN rcte AS m
ON e.ManagerId = m.EmpId
)
SELECT rcte.EmpId ,
rcte.ManagerId ,
rcte.Level ,
rcte.Value ,
CAST(rcte.h AS HIERARCHYID) AS h
INTO #employees
FROM rcte;
GO
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
e.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE m.EmpId = 1
SELECT m.EmpId ,
m.ManagerId ,
m.Level ,
m.Value ,
m.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE e.EmpId = 8
While I needed a recursive CTE to actually establish the hierarchy, any of the actual queries of the form "who does this person report to?" and "who reports to this person?" are ultimately satisfied from the persisted hierarchy in the #employees table. The two queries at the end show how to traverse the hierarchy in either direction. This sort of thing is important if your hierarchy is large (wide, deep, or both). You do need to maintain it when the org chart changes, but that's a one-time operation. The querying of the data should be fast because the lineage is persisted with the employee record.
Incidentally, your Level column is a bit odd to me. Specifically, it seems backwards (i.e. CEO has the highest level). I say this because if/when you add another level to the org chart, you'll need to re-level everyone from the CEO down. If you have the CEO have the lowest level, you just tack that level onto the bottom and don't have to re-level anyone.

Related

Simplyfing conditional in one single query in SQL Server 2008

How to simplify below T-SQL statement in one single query?
IF #OrderByDescription = 1
BEGIN
SELECT d.DeptId, Description
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY Description
END
ELSE
BEGIN
SELECT d.DeptId, Description
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY
CASE WHEN o.[Order] IS NULL THEN 1
ELSE 0
END, o.[Order]
END
Move your IF condition into a CASE, then nest your other CASE inside of it.
SELECT
d.DeptId
,Description
FROM
Dept AS d
LEFT JOIN
DeptOrder AS o
ON
p.DeptId = o.DeptId
WHERE
d.DeptId IN
( 3, 7, 9, 10, 17, 20 )
ORDER BY
CASE
WHEN #OrderByDescription = 1 THEN Description
ELSE CASE
WHEN o.[Order] IS NULL THEN 1
ELSE 0
END
END
,o.[Order];
Try this:
SELECT d.DeptId, Description
FROM Dept d LEFT JOIN DeptOrder o on p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY
CASE
WHEN #OrderByDescription = 1 THEN Description
ELSE 1
END,
CASE
WHEN o.[Order] IS NULL THEN 1
ELSE 0
END,
o.[Order]
Try This.
DECLARE #OrderByDescription INT = 1;
WITH CTE_Order
AS (
SELECT d.DeptId
,Description
,CASE
WHEN #OrderByDescription = 1
THEN ROW_NUMBER() OVER (
ORDER BY Description
)
ELSE ROW_NUMBER() OVER (
ORDER BY CASE
WHEN o.[Order] IS NULL
THEN 1
ELSE 0
END
,o.[Order]
)
END RowNum
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (
3
,7
,9
,10
,17
,20
)
)
SELECT *
FROM CTE_Order
ORDER BY RowNum

Merge Segment Ranges Based on Grouped Value

I have a table of segments with a beginning point, an ending point, and a value like so:
Bmp | Emp | SomeVal
0 1 1
1 2 1
2 3 2
3 4 2
4 5 1
I would like to merge (summarize) these records so they look like so:
Bmp | Emp | SomeVal
0 2 1
2 4 2
4 5 1
I've simplified my data set for the purpose of this question. The end result is I need unique rows grouped by the SomeVal column (in my real data set, there are about 20 columns) with the segments stitched together from Bmp to Emp, but not overlapping.
I've tried the following:
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
SELECT MIN(Bmp) AS Bmp, Max(Emp) AS Emp, SomeVal FROM #tbl
GROUP BY SomeVal
Unfortunately, it comes out like so which is wrong:
Bmp | Emp | SomeVal
0 5 1
2 4 2
My query above only works if the values of SomeVal do not repeat. How can I fix my sql?
Minimum required version is SQL 2008.
You may using ROW_NUMBER() function to correlate begin group row with end group row.
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
;WITH
[Begins] AS
(
SELECT Bmp, SomeVal, ROW_NUMBER() OVER (ORDER BY Bmp) AS OrderNumber
FROM #tbl AS [Begin]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Prev]
WHERE [Prev].Emp = [Begin].Bmp AND [Prev].SomeVal = [Begin].SomeVal)
),
[Ends] AS
(
SELECT Emp, SomeVal, ROW_NUMBER() OVER (ORDER BY Emp) AS OrderNumber
FROM #tbl AS [End]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Next]
WHERE [Next].Bmp = [End].Emp AND [Next].SomeVal = [End].SomeVal)
)
SELECT [Begins].Bmp, [Ends].Emp, [Begins].SomeVal
FROM [Begins]
INNER JOIN [Ends]
ON [Begins].OrderNumber = [Ends].OrderNumber;
Here is another option...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Bmp INT NOT NULL PRIMARY KEY CLUSTERED,
Emp INT NOT NULL,
SomeVal INT NOT NULL
);
INSERT #TestData (Bmp, Emp, SomeVal)
SELECT 0, 1, 1 UNION ALL
SELECT 1, 2, 1 UNION ALL
SELECT 2, 3, 2 UNION ALL
SELECT 3, 4, 2 UNION ALL
SELECT 4, 5, 1;
--==============================================
WITH
cte_GroupStart AS (
SELECT
td.Bmp,
td.Emp,
td.SomeVal,
GroupStart = CASE WHEN td.SomeVal = LAG(td.SomeVal, 1) OVER (ORDER BY td.Bmp) THEN NULL ELSE ROW_NUMBER() OVER (ORDER BY td.Bmp) END
FROM
#TestData td
),
cte_FillGroup AS (
SELECT
gs.Bmp,
gs.Emp,
gs.SomeVal,
AggGroup = MAX(gs.GroupStart) OVER (ORDER BY gs.Bmp) -- ROWS UNBOUNDED PRECEDING is implied and should work as expected 2008
FROM
cte_GroupStart gs
)
SELECT
Bmp = MIN(fg.Bmp),
Emp = MAX(fg.Emp),
fg.SomeVal
FROM
cte_FillGroup fg
GROUP BY
fg.AggGroup,
fg.SomeVal
ORDER BY
fg.AggGroup;

Selecting Records On the basis of a group in sql server

I have a scenario in which there are two tables , one is MAIN and second is Child.
There are possible 4 status Types
1 = Not Started
2 = Started
3 = Running
4 = Stopped
I have random records in child for each main tables's row , I need to find out only the records count in which Child's table Only Status (1,4) used.
below I am attaching the script.
CREATE TABLE #Main
(
ID INT ,
CreateDateTime DATETIME ,
LLevel INT
)
CREATE TABLE #Child
(
ID INT ,
MainID INT ,
STATUS INT
)
-- Status(1= NotStarted, 2= Started ,3 = Ruunning ,4 = Stopped)
INSERT INTO #Main
SELECT 1 ,
'2015-12-24 18:48:41' ,
1
UNION ALL
SELECT 2 ,
'2015-12-24 18:49:59' ,
3
UNION ALL
SELECT 3 ,
'2015-12-24 18:51:01' ,
1
UNION ALL
SELECT 4 ,
'2015-12-24 18:53:11' ,
4
UNION ALL
SELECT 5 ,
'2015-12-24 18:57:11' ,
2
INSERT INTO #Child
SELECT 1 ,
1 ,
1 -- MIAN ID = 1
UNION ALL
SELECT 2 ,
1 ,
2
UNION ALL
SELECT 3 ,
1 ,
3
UNION ALL
SELECT 4 ,
2 ,
1 -- MIAN ID = 2
UNION ALL
SELECT 5 ,
2 ,
4
UNION ALL
SELECT 6 ,
3 ,
1 -- MIAN ID = 3
UNION ALL
SELECT 7 ,
3 ,
2
UNION ALL
SELECT 8 ,
3 ,
3
UNION ALL
SELECT 9 ,
4 ,
1 -- MIAN ID = 4
UNION ALL
SELECT 10 ,
4 ,
2
UNION ALL
SELECT 11 ,
4 ,
3
UNION ALL
SELECT 12 ,
5 ,
1 -- MIAN ID = 2
UNION ALL
SELECT 13 ,
5 ,
4
SELECT *
FROM #Main
SELECT *
FROM #Child
ORDER BY MainID ASC
DROP TABLE #Main
DROP TABLE #Child
I am attaching an image, these records i need , means count should be two.
This may not be the fastest way to do it, but the optimizer will make it close. Also, it is clear to see that is is correct and understand how it works using CTEs
WITH haveone as
(
SELECT DISTINCT MainID as ID
FROM #Child
WHERE STATUS = 1
), havefour as
(
SELECT DISTINCT MainID as ID
FROM #Child
WHERE STATUS = 4
), haveboth as
(
SELECT haveone.ID
FROM haveone
JOIN havefour ON haveone.ID = havefour.ID
), haveother as
(
SELECT DISTINCT MainID as ID
FROM #Child
WHERE STATUS NOT IN (1,4)
)
SELECT ID
FROM haveboth
WHERE ID NOT IN (SELECT ID FROM haveother)
I have figure out and produced result this way.
SELECT count(m.id) FROM #main m INNER JOIN #child c on m.id = c.mainid and c.status in (1,4)
and c.mainid not in (select mainid from #child where status in (2,3)) group by m.id
having count(m.id) = 2

Update a special id in hierarchical employee table

I have an update that has to be made. And Im really stuck.
This is a classic hierarchical employee table question, but with a twist.
Please look at this tree of users:
employees
(Completely unrelated to my problem, just something I found with google pictures)
Lets assume the following Id's:
RM1: EmpId 1, ParentId null
AM1: EmpId 2, ParentId 1
MGR1: EmpId 3, ParentId 2
MGR2: EmpId 4, ParentId 2
EMP1: EmpId 5, ParentId 3
EMP2: EmpId 6, ParentId 3
EMP3: EmpId 7, ParentId 4
EMP4: EmpId 8, ParentId 4
I need to add another column, lets call it parentSpecialId.
This id is the id of the user below root (AM1 and AM2).
All users below AM1 and AM2 should have the parentSpecialId set to the user below root.
Which gives us:
RM1: EmpId 1, ParentId null parentSpecialId null
AM1: EmpId 2, ParentId 1 parentSpecialId null
MGR1: EmpId 3, ParentId 2 parentSpecialId 2
MGR2: EmpId 4, ParentId 2 parentSpecialId 2
EMP1: EmpId 5, ParentId 3 parentSpecialId 2
EMP2: EmpId 6, ParentId 3 parentSpecialId 2
EMP3: EmpId 7, ParentId 4 parentSpecialId 2
EMP4: EmpId 8, ParentId 4 parentSpecialId 2
All I have is this CTE which gives me a result set with AM1 and AM2.
So I need to traverse all way down to EMPX and update parentSpecialId with Id 2 for
AM1 and the same for all users for AM2. Of course, it needs to by dynamic, in real life I have 12 of these users below root.
Does it make sense?
Here is my CTE:
WITH EmpsCTE AS
(
SELECT id, parent, name, 0 AS EmployeeLevel
FROM Employee
WHERE parent = 0
UNION ALL
SELECT e.id, e.parent, e.name, EmployeeLevel + 1
FROM EmpsCTE AS p
JOIN Employee AS e ON e.parent = p.id
)
SELECT id, parent, name, EmployeeLevel
From EmpsCTE where EmployeeLevel = 1
Oh, and I use Sql server 2008 R2
Sample data:
declare #T table
(
Name varchar(10),
EmpId int,
ParentId int,
ParentSpecialID int
);
insert into #T(Name, EmpId, ParentId) values
('RM1', 1, null),
('AM1', 2, 1),
('MGR1', 3, 2),
('MGR2', 4, 2),
('EMP1', 5, 3),
('EMP2', 6, 3),
('EMP3', 7, 4),
('EMP4', 8, 4);
Update statement:
with C as
(
select T3.EmpId,
T2.EmpId as ParentSpecialId
from #T as T1
inner join #T as T2
on T1.EmpId = T2.ParentId
inner join #T as T3
on T2.EmpId = T3.ParentId
where T1.ParentId is null
union all
select T.EmpId,
C.ParentSpecialId
from #T as T
inner join C
on T.ParentId = C.EmpId
)
update T
set ParentSpecialId = C.ParentSpecialId
from #T as T
inner join C
on T.EmpId = C.EmpId
To handle a tree of arbitrary depth:
declare #T table ( Name varchar(16), EmpId int, ParentId int );
insert into #T(Name, EmpId, ParentId) values
('RM1', 1, null),
('AM1', 2, 1),
('MGR1', 3, 2),
('MGR2', 4, 2),
('EMP1', 5, 3),
('EMP2', 6, 3),
('EMP3', 7, 4),
('EMP4', 8, 4),
('AM2', 9, 1),
('MGR3', 10, 9),
('EMP5', 11, 10),
('Brown Noser', 12, 11),
('Intern', 13, 12),
('Coop', 14, 13),
('Nephew', 15, 14),
('Contractor', 16, 15);
; with CTE as (
-- Start with the root(s).
select Name, EmpId, ParentId, 0 as Depth, Cast(NULL as Int) as parentSpecialId
from #T
where ParentId is NULL
union all
-- Add the direct reports one layer at a time.
select T.Name, T.EmpId, T.ParentId, CTE.Depth + 1, case when CTE.Depth = 1 then T.ParentId else CTE.parentSpecialId end
from CTE inner join
#T as T on T.ParentId = CTE.EmpID
where T.ParentId = CTE.EmpId
)
select *,
( select Name from CTE as R where R.EmpId = CTE.ParentId ) as ReportsTo,
( select Name from CTE as SC where SC.EmpId = CTE.parentSpecialId ) as SubCommander
from CTE
order by Depth, Name
With thanks to Mikael Eriksson for setting up the sample data!

Finding customers with identical orders

I need to find customers who have made identical orders. (Using T-SQL)
Order
OrderID Customerer
1 2
2 5
3 6
4 2
5 4
6 6
7 8
OrderLine
OrderLineID OrderID OrderDate OrderType Quantity Reference
1 1 01/01/2011 1 1 Coca Cola
2 1 01/01/2011 1 3 Tea
3 2 02/02/2011 2 1 Coffee
4 2 02/02/2011 2 2 Solo
5 2 03/02/2011 1 1 Soda
6 3 03/02/2011 1 3 Tea
7 3 03/02/2011 1 1 Coca Cola
8 4 05/06/2011 1 1 Beer
9 5 06/06/2011 2 1 Tea
10 5 06/06/2011 2 1 Coca Cola
11 6 07/07/2011 1 1 Coffee
12 6 07/07/2011 1 2 Solo
13 6 07/07/2011 1 1 Soda
14 6 07/07/2011 1 1 Beer
15 7 08/08/2011 1 1 Beer
Here orders with OrderID 1 and 3 are considered to be identical because the number for orderlines, "Quantity" and "Reference" are identical on both orders. Meaning that customer 2 and 6 have placed identical orders.
Order 5 are not identical to order 1 and 3 because Quantity differ.
Order 2 are not identical to order 6 because orderlines differ.
Order 4 and 7 are also identical.
I am searching for a ressult like this:
IdenticalOrders
OrderID CustomeerID
1 2
3 6
4 2
7 8
It seems like an easy task, but I just can't understand where to start.
(I am still new to t-sql :-) )
Here's one way.
SELECT O1.OrderID ,
O1.Customer ,
O2.OrderID ,
O2.Customer
FROM [Order] O1
JOIN [Order] O2 ON O1.OrderID < O2.OrderID
AND O1.Customer <> O2.Customer
WHERE NOT EXISTS ( SELECT Quantity ,
Reference
FROM OrderLine
WHERE O1.OrderID = OrderLine.OrderID
EXCEPT
SELECT Quantity ,
Reference
FROM OrderLine
WHERE O2.OrderID = OrderLine.OrderID )
AND NOT EXISTS ( SELECT Quantity ,
Reference
FROM OrderLine
WHERE O2.OrderID = OrderLine.OrderID
EXCEPT
SELECT Quantity ,
Reference
FROM OrderLine
WHERE O1.OrderID = OrderLine.OrderID )
You can also use XML PATH to simulate GROUP_CONCAT then JOIN the two result sets
DECLARE #T TABLE
(
OrderId INT PRIMARY KEY,
Customer INT ,
complete_order VARCHAR(MAX)
)
INSERT INTO #T
SELECT *
FROM [Order] O
CROSS APPLY ( SELECT CAST(Quantity AS VARCHAR(30))
+ '~' + Reference + '~~'
FROM OrderLine OL
WHERE OL.OrderID = O.OrderID
ORDER BY Reference ,
Quantity
FOR
XML PATH('')
) T ( complete_order )
SELECT T1.OrderId,
T1.Customer
FROM #T T1
WHERE EXISTS ( SELECT *
FROM #T T2
WHERE T1.Customer <> T2.Customer
AND T1.OrderId <> T2.OrderId
AND T1.complete_order = T2.complete_order )
This is an extension of Martin's second suggestion. This will show all matching combinations without any repetitions.
;With FmtOL(customer, orderid, complete_order) as
(
SELECT customer, orderid, complete_order
FROM Order O
cross apply ( SELECT CAST(Quantity AS VARCHAR(30))
+ '~' + Reference + '~~'
FROM OrderLine OL
WHERE OL.OrderID = O.OrderID
ORDER BY Reference ,
Quantity
FOR
XML PATH('')
) T ( complete_order )
)
SELECT T1.OrderId,
T1.Customer,
STUFF(C1.a, 1, 2, '') as [SameAs]
FROM FmtOL T1
Cross apply ( SELECT '; ' + 'Customer ' + Cast(T2.Customer as varchar(30))
+ '''s order ' + Cast(T2.OrderID as varchar(30))
FROM FmtOL T2
WHERE T1.Customer < T2.Customer
AND T1.OrderId < T2.OrderId
AND T1.complete_order = T2.complete_order
order by ';' + Cast(T2.Customer as varchar(30))
+ '''s order ' + Cast(T2.OrderID as varchar(30))
, t2.orderid
for xml path('')
) C1 (a)
where C1.a is not null
Results should look like this:
OrderId Customer SameAs
1 2 Customer 6's order 3
4 2 Customer 8's order 7
Here's the most simple approach.
-- sample table
create table x
(
LineId int identity(1, 1)
,InvoiceFk int
,ProductFk int
,Quantity int
)
-- sample data
insert into x
(InvoiceFk, ProductFk, Quantity) values
(11, 1, 1)
,(11, 2, 1)
,(11, 3, 1)
,(12, 1, 2)
,(12, 2, 2)
,(12, 3, 2)
,(13, 1, 3)
,(13, 2, 3)
,(13, 3, 3)
-- your order, probably from a parameter
declare #order table
(
InvoiceFk int
,ProductFk int
,Quantity int
)
insert into #order
(InvoiceFk, ProductFk, Quantity) values
(14, 1, 1) -- duplicate invoice 11
,(14, 2, 1)
,(14, 3, 1)
-- your order unique checksum
declare #orderCheck int
select #orderCheck = checksum_agg(checksum(ProductFk, Quantity))
from #order
-- test your order in existing data
declare #match int
select #match =
(
select TOP 1 InvoiceFk from
(
select
InvoiceFk
,checksum_agg(Col1) as Col2
from
(
select
InvoiceFk
,checksum(productfk, quantity) as Col1
from x
) as T1
group by
InvoiceFk
) as T2
where
T2.Col2 = #orderCheck
)
-- evaluate if your order is unique or not
if (#match is not null)
begin
print 'Identical to invoice: ' + Str(#match);
end
else
begin
print 'Order is unique';
end
-- clean up sample table
drop table x
Best of luck!

Resources