SQL Find Executive All Employees Reports To - sql-server

I have found lots of examples to create a employee reporting structure hierarchy. However, I want to take this a little further and for each employee find the executive they report up through whom reports directly to the CEO. Given the following data:
ID FullName HRLevel SupervisorID
----------- -------------------------------------------------- ---------- ------------
1 Pam Beesly CEO NULL
2 Angela Martin SVP 1
3 Kelly Kapoor SVP 1
4 Meredith Palmer SVP 1
5 Phyllis Vance AVP 3
6 Jan Levinson AVP 4
7 Erin Hannon Associate 5
8 Karen Filippelli Intern 5
I would like a list of all employees with an HRLevel of SVP and below to show the SVP level employee they report up to. So it could be their immediate supervisor or it could be 4 levels above them. Also, if an employee reports directly to the CEO I want that employee to be listed as the DepartmentHead.
Here is what I have so far:
;WITH cte_employees (ID, FullName, HRLevel, SupervisorID, SupervisorName) AS
(
SELECT ID
, FullName
, HRLevel
, SupervisorID
, CONVERT(VARCHAR(50), NULL) AS [SupervisorName]
FROM #Employees
WHERE HRLevel = 'SVP'
UNION ALL
SELECT e.ID
, e.FullName
, e.HRLevel
, e.SupervisorID
, c.FullName AS [SupervisorName]
FROM #Employees e
INNER JOIN cte_employees c
ON e.SupervisorID = c.ID
)
SELECT c2.ID
, c2.FullName
, c2.HRLevel
, c2.SupervisorID
, COALESCE(c2.SupervisorName, c2.FullName) AS [DepartmentHead]
FROM cte_employees c2;
Which yields the immediate supervisor, not the SVP DepartmentHead:
ID FullName HRLevel SupervisorID DepartmentHead
----------- -------------------------------------------------- ---------- ------------ --------------------------------------------------
2 Angela Martin SVP 1 Angela Martin
3 Kelly Kapoor SVP 1 Kelly Kapoor
4 Meredith Palmer SVP 1 Meredith Palmer
5 Phyllis Vance AVP 3 Kelly Kapoor
6 Jan Levinson AVP 4 Meredith Palmer
7 Erin Hannon Associate 5 Phyllis Vance
8 Karen Filippelli Intern 5 Phyllis Vance
The only difference in what I am getting and what I want is that Employee IDs 7 and 8 should have a SVP DepartmentHead of Kelly Kapoor.
Here is my dbfiddle showing this in action.

You are vert close:
IF OBJECT_ID('tempdb.dbo.#Employees ', 'U') IS NOT NULL DROP TABLE #Employees;
CREATE TABLE #Employees(ID INT,
FullName VARCHAR(50),
HRLevel VARCHAR(10),
SupervisorID INT)
INSERT INTO #Employees VALUES (1, 'Pam Beesly', 'CEO', NULL)
INSERT INTO #Employees VALUES (2, 'Angela Martin', 'SVP', 1)
INSERT INTO #Employees VALUES (3, 'Kelly Kapoor', 'SVP', 1)
INSERT INTO #Employees VALUES (4, 'Meredith Palmer', 'SVP', 1)
INSERT INTO #Employees VALUES (5, 'Phyllis Vance', 'AVP', 3)
INSERT INTO #Employees VALUES (6, 'Jan Levinson', 'AVP', 4)
INSERT INTO #Employees VALUES (7, 'Erin Hannon', 'Associate', 5)
INSERT INTO #Employees VALUES (8, 'Karen Filippelli', 'Intern', 5)
;WITH cte_employees (ID, FullName, HRLevel, SupervisorID, SupervisorName) AS
(
SELECT ID
, FullName
, HRLevel
, SupervisorID
, FullName AS [SupervisorName]
FROM #Employees
WHERE HRLevel = 'SVP'
UNION ALL
SELECT e.ID
, e.FullName
, e.HRLevel
, e.SupervisorID
, c.SupervisorName AS [SupervisorName]
FROM #Employees e
INNER JOIN cte_employees c
ON e.SupervisorID = c.ID
)
SELECT c2.ID
, c2.FullName
, c2.HRLevel
, c2.SupervisorID
, c2.SupervisorName AS [DepartmentHead]
FROM cte_employees c2;

Related

left join and get the maximum datetime value

I have two tables ,
Table 1
Id Name
===========
1 Name1
2 Name2
3 Name3
Table 2
Id Tb1Id DateTime
=======================
1 1 20-09-2017
2 1 01-09-2018
3 2 01-09-2016
4 2 02-09-2015
5 3 06-09-2016
6 3 10-09-2019
I want to join those two tables by where Table1.Id = Table2.Tb1Id and get the maximum datetime value from Table2. The result should be like this .
Id Name DateTime
========================
1 Name1 01-09-2018
2 Name2 01-09-2016
3 Name3 10-09-2019
Try This
DECLARE #Table1 AS TABLE(Id INT,Name VARCHAR(20))
INSERT INTO #Table1
SELECT 1,'Name1' UNION ALL
SELECT 2,'Name2' UNION ALL
SELECT 3,'Name3'
DECLARE #Table2 AS TABLE(Id INT, Tb1Id INT,[DateTime] DATETIME)
INSERT INTO #Table2
SELECT 1,1,'2017-09-20' UNION ALL
SELECT 2,1,'2018-09-01' UNION ALL
SELECT 3,2,'2016-09-01' UNION ALL
SELECT 4,2,'2015-09-02' UNION ALL
SELECT 5,3,'2016-09-06' UNION ALL
SELECT 6,3,'2019-09-10'
SELECT t2.Tb1Id AS Id,
t1.Name,
MAX(t2.[DateTime]) AS [DateTime]
FROM #Table1 AS T1
INNER JOIN #Table2 AS t2
ON T1.Id = t2.Tb1Id
GROUP BY
t2.Tb1Id,
t1.Name
Result
Id Name DateTime
-----------------------------------
1 Name1 2018-09-01 00:00:00.000
2 Name2 2016-09-01 00:00:00.000
3 Name3 2019-09-10 00:00:00.000

Please advise my the stored procedure which is group by and total which different criteria

I have answered one of the interview questions as below.
There are two tables (employee and Department).
Show report No. of people(count) and total salary where IT Dept. salary from 250 to 500 and Sales Dept. salary from 250 to 1000 and Marketing Dept. salary from 250 to 1500.
Sample expected result below
Marketing 0 0.00
Information Technology 1 250.00
Sales 2 1200.00
Employee table
EmpID EmpName DeptID Salary
1 Mike 1 1000.00
2 Paul 1 1500.00
3 John 1 2000.00
4 Joe 2 500.00
5 Kim 3 2000.00
6 Lim 3 2500.00
7 Sam 2 700.00
8 Mario 1 250.00
Department table
DeptID DeptCode DeptName
1 IT Information Technology
2 ST Sales
3 MT Marketing
My Answer:
ALTER PROCEDURE [dbo].[TheseAndThat]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT dd.DeptName, ISNULL(TT.c,0) AS StaffCount , ISNULL(TT.s,0) AS TotalSalary FROM [dbo].[Department] dd
LEFT JOIN
(
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 500 AND d.DeptID = 1
GROUP BY e.DeptID, d.DeptCode
UNION
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 1000 AND d.DeptID = 2
GROUP BY e.DeptID, d.DeptCode
UNION
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 1500 AND d.DeptID = 3
GROUP BY e.DeptID, d.DeptCode
) TT
ON dd.DeptCode = TT.dcode
ORDER BY TT.c
END
I'm not sure my answer is correct. However, the result seems to be ok.
Please advise.
If I was you I will go this this query (only 1 time scan to employee table)
SELECT d.DeptName, ISNULL(e.NoEmp,0) AS NoEmp, ISNULL(SumSalary,0) AS SumSalary
FROM [dbo].[Department] AS d
LEFT JOIN (
SELECT DeptID, COUNT(EmpID) As NoEmp, SUM (Salary) AS SumSalary
FROM [dbo].[Employee]
WHERE Salary BETWEEN 250 AND CASE WHEN DeptID = 1 THEN 500
WHEN DeptID = 2 THEN 1000
WHEN DeptID = 3 THEN 1500
END
GROUP BY DeptID) AS e ON d.DeptID = e.DeptID
WHERE d.DeptID IN(1,2,3)
First, the code to setup temp tables:
declare #employees table
(
EmpID int,
EmpName varchar(100),
DeptID int,
Salary decimal
)
declare #departament table
(
DeptID int,
DeptCode char(2),
DeptName varchar(100)
)
insert into #employees values (1,'Mike',1,1000.00)
insert into #employees values (2,'Paul',1,1500.00)
insert into #employees values (3,'John',1,2000.00)
insert into #employees values (4,'Joe',2,500.00)
insert into #employees values (5,'Kim',3,2000.00)
insert into #employees values (6,'Lim',3,2500.00)
insert into #employees values (7,'Sam',2,700.00)
insert into #employees values (8,'Mario',1,250.00)
insert into #departament values (1, 'IT', 'Information Technology')
insert into #departament values (2, 'ST', 'Sales')
insert into #departament values (3, 'MT', 'Marketing')
Now, the report:
select DeptName, COALESCE(d2.DeptID, 0), COALESCE(Salaries,0) from #departament d2
left join
(
select COUNT(*) as DeptID, SUM(Salary) as Salaries from #departament d
inner join #employees e on d.DeptID = e.DeptID
where
(d.DeptID = 1 and e.Salary between 250 and 500)
or
(d.DeptID = 2 and e.Salary between 250 and 1000)
or
(d.DeptID = 3 and e.Salary between 250 and 1500)
group by d.DeptID) as sums on sums.DeptID = d2.DeptID
An alternative (uses the same temp tables as #GustavoF answer):
DECLARE #Input TABLE( DeptID INT, SalaryRangeMin decimal, SalaryRangeMax decimal )
INSERT INTO #Input
VALUES
( 1, 250, 500),
( 2 ,250 ,1000 ),
( 3 ,250 , 1500 )
SELECT D.DeptName, COUNT(EmpID) as EmployeeCount, ISNULL( SUM( e.Salary ), 0.0 ) as TotalSalary
FROM #Input AS I
INNER JOIN #departament AS D ON I.DeptID = D.DeptID
LEFT JOIN #Employees AS E ON I.DeptID = E.DeptID AND E.Salary BETWEEN I.SalaryRangeMin AND I.SalaryRangeMax
GROUP BY E.DeptID, D.DeptCode, D.DeptName
ORDER BY TotalSalary ASC
Output:
DeptName EmployeeCount TotalSalary
--------------------------- ------------- -------------
Marketing 0 0
Information Technology 1 250
Sales 2 1200

Building a snapshot table from audit records

I have a Customer table with the following structure.
CustomerId Name Address Phone
1 Joe 123 Main NULL
I also have an Audit table that tracks changes to the Customer table.
Id Entity EntityId Field OldValue NewValue Type AuditDate
1 Customer 1 Name NULL Joe Add 2016-01-01
2 Customer 1 Phone NULL 567-54-3332 Add 2016-01-01
3 Customer 1 Address NULL 456 Centre Add 2016-01-01
4 Customer 1 Address 456 Centre 123 Main Edit 2016-01-02
5 Customer 1 Phone 567-54-3332 843-43-1230 Edit 2016-01-03
6 Customer 1 Phone 843-43-1230 NULL Delete 2016-01-04
I have a CustomerHistory reporting table that will be populated with a daily ETL job. It has the same fields as Customer Table with additional field SnapShotDate.
I need to write a query that takes the records in Audit table, transforms and inserts into CustomerHistory as seen below.
CustomerId Name Address Phone SnapShotDate
1 Joe 456 Centre 567-54-3332 2016-01-01
1 Joe 123 Main 567-54-3332 2016-01-02
1 Joe 123 Main 843-43-1230 2016-01-03
1 Joe 123 Main NULL 2016-01-04
I guess the solution would involve a self-join on Audit table or a recursive CTE. I would appreciate any help with developing this solution.
Note: Unfortunately, I do not have the option to use triggers or change the Audit table schema. Query performance is not a concern since this will be a nightly ETL process.
You can use below script.
DROP TABLE #tmp
CREATE TABLE #tmp (
id INT Identity
, EntityId INT
, NAME VARCHAR(10)
, Address VARCHAR(100)
, Phone VARCHAR(20)
, Type VARCHAR(10)
, SnapShotDate DATETIME
)
;with cte1 as (
select AuditDate, EntityId, Type, [Name], [Address], [Phone]
from
(select AuditDate, EntityId, Type, Field, NewValue from #Audit) p
pivot
(
max(NewValue)
for Field in ([Name], [Address], [Phone])
) as xx
)
insert into #tmp (EntityId, Name, Address, Phone, Type, SnapShotDate)
select EntityId, Name, Address, Phone, Type, AuditDate
from cte1
-- update NULLs columns with the most recent value
update #tmp
set Name = (select top 1 Name from #tmp tp2
where EntityId = tp2.EntityId and Name is not null
order by id desc)
where Name is null
update #tmp
set Address = (select top 1 Address from #tmp tp2
where EntityId = tp2.EntityId and Address is not null
order by id desc)
where Address is null
update #tmp
set Phone = (select top 1 Phone from #tmp tp2
where EntityId = tp2.EntityId and Phone is not null
order by id desc)
where Phone is null
To Create Test Data, use below script
CREATE TABLE #Customer (
CustomerId INT
, NAME VARCHAR(10)
, Address VARCHAR(100)
, Phone VARCHAR(20)
)
INSERT INTO #Customer
VALUES (1, 'Joe', '123 Main', NULL)
CREATE TABLE #Audit (
Id INT
, Entity VARCHAR(50)
, EntityId INT
, Field VARCHAR(20)
, OldValue VARCHAR(100)
, NewValue VARCHAR(100)
, Type VARCHAR(10)
, AuditDate DATETIME
)
insert into #Audit values
(1, 'Customer', 1, 'Name' ,NULL ,'Joe' ,'Add' ,'2016-01-01'),
(2, 'Customer', 1, 'Phone' ,NULL ,'567-54-3332' ,'Add' ,'2016-01-01'),
(3, 'Customer', 1, 'Address' ,NULL ,'456 Centre' ,'Add' ,'2016-01-01'),
(4, 'Customer', 1, 'Address' ,'456 Centre' ,'123 Main' ,'Edit' ,'2016-01-02'),
(5, 'Customer', 1, 'Phone' ,'567-54-3332' ,'843-43-1230' ,'Edit' ,'2016-01-03'),
(6, 'Customer', 1, 'Phone' ,'843-43-1230' ,NULL ,'Delete' ,'2016-01-04'),
(7, 'Customer', 2, 'Name' ,NULL ,'Peter' ,'Add' ,'2016-01-01'),
(8, 'Customer', 2, 'Phone' ,NULL ,'111-222-3333' ,'Add' ,'2016-01-01'),
(8, 'Customer', 2, 'Address' ,NULL ,'Parthenia' ,'Add' ,'2016-01-01')
Result
EntityId Name Address Phone Type SnapShotDate
1 Joe 456 Centre 567-54-3332 Add 2016-01-01
1 Joe 123 Main 843-43-1230 Edit 2016-01-02
1 Joe 123 Main 843-43-1230 Edit 2016-01-03
1 Joe 123 Main 843-43-1230 Delete 2016-01-04

Hierarchical data run infinitely in SQL Server 2008

I am using SQL Server 2008.In my databse one table like:
------------------------------
id parentId name
------------------------------
1 NULL india
2 1 gujrat
3 1 Maharastra
4 1 rajsthan
5 2 ahmedabad
6 2 rajkot
7 NULL USA
8 7 newyork
1 3 mumbai
1 3 goa
1 4 jaipur
1 7 californiya
Here i want to get this data with it's level in hierarchy, so i create query like:
with RecursiveTable_CTE (Id,Parentid,Name_cte,Hlevel)
as
(
select id , parentId, name, 0 as Hlevel from treeTable where id = 1
union all
select t.id,t.parentId,t.name, Hlevel + 1 as LEVEL from treeTable as t inner join RecursiveTable_CTE as rtc on t.id = rtc.Parentid
)
select * from RecursiveTable_CTE option (maxrecursion 0)
and also try
with RecursiveTable_CTE (Id,Parentid,Name_cte,Hlevel)
as
(
select id , parentId, name, 0 as Hlevel from treeTable where parentId is null
union all
select t.id,t.parentId,t.name, Hlevel + 1 as LEVEL from treeTable as t inner join RecursiveTable_CTE as rtc on rtc.id = t.parentId
)
select * from RecursiveTable_CTE option (maxrecursion 0)
but both result infinite loop.
can any one help me?
I think the query you're after is this:
with RecursiveTable_CTE (Id,Parentid,Name_cte,Hlevel)
as
(
select id , parentId, name, 0 as Hlevel
from treeTable
where parentId is null
union all
select t.id,t.parentId,t.name, rtc.Hlevel + 1 as Hlevel
from treeTable as t
inner join RecursiveTable_CTE as rtc on t.Parentid = rtc.id
)
select * from RecursiveTable_CTE
The problem is though that I think your data is not correct and id and parentId are swapped in some rows. I used this data:
insert into treeTable values (1, NULL, 'india')
insert into treeTable values (2, 1, 'gujrat')
insert into treeTable values (3, 1, 'Maharastra')
insert into treeTable values (4, 1, 'rajsthan')
insert into treeTable values (5, 2, 'ahmedabad')
insert into treeTable values (6, 2, 'rajkot')
insert into treeTable values (7, NULL, 'USA')
insert into treeTable values (8, 7 , 'newyork')
insert into treeTable values (9, 1 , 'mumbai')
insert into treeTable values (10, 1 , 'goa')
insert into treeTable values (11, 1 , 'jaipur')
insert into treeTable values (12, 7 , 'californiya')
SQL Fiddle demo
Here in your case ther 1 is top level but again 1 has parents 3,4,7.
your infinite loop is due to your wrong herarchy.
id parentId name
------------------------------
1 NULL india
1 3 mumbai
1 4 jaipur
1 7 californiya
You have duplicates in the ID column, Is that a typo? If you don't have duplicates
CREATE TABLE states
(
[id] INT,[parentid] INT,[name] VARCHAR(11)
);
INSERT INTO states
([id],[parentid],[name])
VALUES (1,NULL,'india'),
(2,1,'gujrat'),
(3,1,'Maharastra'),
(4,1,'rajsthan'),
(5,2,'ahmedabad'),
(6,2,'rajkot'),
(7,NULL,'USA'),
(8,7,'newyork'),
(9,3,'mumbai'),
(10,3,'goa'),
(11,4,'jaipur'),
(12,7,'californiya');
WITH recursivetable_cte (id, parentid, name_cte, hlevel)
AS (SELECT id,parentid,name,0 AS Hlevel
FROM states
WHERE parentid IS NULL
UNION ALL
SELECT t.id,t.parentid,t.name,hlevel + 1 AS LEVEL
FROM states AS t
INNER JOIN recursivetable_cte AS rtc
ON t.parentid = rtc.id)
SELECT *
FROM recursivetable_cte
OPTION (maxrecursion 0)
#Szymom is right.id should be unique.though in this query no need of changing table data
see the change,
;with RecursiveTable_CTE (Id,Parentid,Name_cte,Hlevel)
as
(
select id , parentId, name, 0 as Hlevel from #t where id = 1 **and parentId is null**
union all
select t.id,t.parentId,t.name, Hlevel+ 1 as LEVEL from #t as t
inner join RecursiveTable_CTE as rtc on t.Parentid = rtc.id **and t.id<>1**
)
select * from RecursiveTable_CTE

Pagination and INNER JOIN

I have a situation which I need to do pagination along with INNET JOIN. Here is a similar scenario I have:
DECLARE #categories AS TABLE(
CatID INT,
CategoryName NVARCHAR(100)
);
DECLARE #fooTable AS TABLE(
ID INT,
CatID INT,
Name NVARCHAR(100),
MinAllow INT,
Price DECIMAL(18,2)
);
INSERT INTO #categories VALUES(1, 'Cat1');
INSERT INTO #categories VALUES(2, 'Cat2');
INSERT INTO #categories VALUES(3, 'Cat3');
INSERT INTO #categories VALUES(4, 'Cat4');
INSERT INTO #categories VALUES(5, 'Cat5');
INSERT INTO #fooTable VALUES(1, 1, 'Product1', 2, 112.2);
INSERT INTO #fooTable VALUES(3, 1, 'Product3', 5, 233.32);
INSERT INTO #fooTable VALUES(6, 1, 'Product6', 4, 12.43);
INSERT INTO #fooTable VALUES(7, 4, 'Product7', 4, 12.43);
INSERT INTO #fooTable VALUES(8, 5, 'Product8', 4, 12.43);
These are the records I have. As you can see, some categories do not have any products inside #fooTable. As a next step, we have the following SELECT statement:
SELECT * FROM #fooTable ft
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY CatID) AS RowNum, * FROM #categories
) AS cat ON (cat.CatID = ft.CatID);
It is a basic JOIN except that the output will also carry the row number of the categories. The result I got for this query is as follows:
ID CatID Name MinAllow Price RowNum CatID CategoryName
---- ------- ------------- ----------- --------- -------- -------- -------------
1 1 Product1 2 112.20 1 1 Cat1
3 1 Product3 5 233.32 1 1 Cat1
6 1 Product6 4 12.43 1 1 Cat1
7 4 Product7 4 12.43 4 4 Cat4
8 5 Product8 4 12.43 5 5 Cat5
When you look at the RowNum column, you will see that those values are not pagination friendly. So, when I try to paginate this table as follows, I got an incorrect output:
SELECT * FROM #fooTable ft
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY CatID) AS RowNum, * FROM #categories
)AS cat ON (cat.CatID = ft.CatID) AND (cat.RowNum BETWEEN 1 AND 2);
The real situation I have is similar to this one but that query is so complicated and I need to get it working with INNER JOIN. I hope I made it clear. Any idea how I got something like that working?
Edit
According to the above result of my first select query, I should be able to retrieve products whose CatID is 1 and 4 on my second query. That's what I aim.
One solution can be the next one:
SELECT x.*
FROM
(
SELECT ft.*,
cat.CategoryName,
DENSE_RANK() OVER (ORDER BY ft.CatID) AS Rnk
FROM #fooTable ft
INNER JOIN #categories cat ON (cat.CatID = ft.CatID)
) AS x
WHERE x.Rnk BETWEEN 1 AND 2
Results:
ID CatID Name MinAllow Price CategoryName Rnk
-- ----- -------- -------- ------- ------------ ---
1 1 Product1 2 112.20 Cat1 1
3 1 Product3 5 233.32 Cat1 1
6 1 Product6 4 12.43 Cat1 1
7 4 Product7 4 12.43 Cat4 2

Resources