Get all parent's Id including the root's Id with CTE - sql

From EmployeeId 8 I want to get all his parents EmployeeId's which are these EmployeeId's 7,5,3,2,1
CREATE TABLE EmployeeTree(
EmployeeId int,
EmployeeName nvarchar(100),
EmployeeType nvarchar(40),
EmployeeParentId int
)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (1, 'Mickey Mouse', 'CEO', NULL)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (2, 'Minnie Mouse', 'President', 1)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (3, 'Donald Duck', 'Division Head', 2)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (4, 'Daffy Duck', 'Division Head', 2)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (5, 'Scrooge McDuck', 'Department Head', 3)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (6, 'Huey McDuck', 'Team Lead', 5)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (7, 'Dewey McDuck', 'Team Lead', 5)
INSERT INTO EmployeeTree (EmployeeId, EmployeeName, EmployeeType, EmployeeParentId)
VALUES (8, 'Louie McDuck', 'Team Member', 7)
With this code I run into recursive problems:
DECLARE #EmployeeId INT
SET #EmployeeId = 8;
WITH x(Id) AS (SELECT #EmployeeId UNION ALL SELECT dbo.EmployeeTree.EmployeeId
FROM dbo.EmployeeTree INNER JOIN x ON EmployeeTree.EmployeeId = x.Id )
Select * FROM x JOIN dbo.EmployeeTree as e ON e.EmployeeParentId = x.Id
What do I have to correct in my CTE that I get all parent employee ids for the employeeId 8 ?

You can achieve that using this CTE query. Note that the anchor query also selects from your source table and both employee ID and parent ID are returned in each iteration so that the parent can be joined each time.
DECLARE #EmployeeId INT
SET #EmployeeId = 8;
WITH x AS (
SELECT EmployeeId, EmployeeParentId
FROM dbo.EmployeeTree
WHERE EmployeeId = #EmployeeId
UNION ALL
SELECT dbo.EmployeeTree.EmployeeId, dbo.EmployeeTree.EmployeeParentId
FROM dbo.EmployeeTree
INNER JOIN x ON EmployeeTree.EmployeeId = x.EmployeeParentId )
Select EmployeeId FROM x
where EmployeeId <> #EmployeeId

Related

Get all employees that belong to exact same location list as the passed in employee

I have a table called EmployeeLocationAssn:
CREATE TABLE EmployeeLocationAssn (
[EmployeeLocationAssnId] [int] IDENTITY(1,1) NOT NULL,
[EmployeeId] [int] NOT NULL,
[LocationId] [int] NOT NULL
)
This table contains data for employees and their associated locations.
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (1, 1)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (1, 2)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (2, 1)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (2, 2)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (3, 1)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (3, 2)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (4, 1)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (4, 2)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (4, 3)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (4, 4)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (5, 3)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (5, 4)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (6, 1)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (6, 2)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (6, 3)
INSERT INTO EmployeeLocationAssn (EmployeeId, LocationId) VALUES (6, 4)
I want to get all employees that have the exactly the same location list as passed in employee id.
Example:
If the user passes EmployeeId = 1, then the query should return all employees that have the same locations.
Output:
#EmployeeId = 1
1
2
3
Employees 4 and 6 has locations 1, 2, 3 & 4. It doesn't exactly match with location 1 & 2 that Employee 1 has and Employee 5 has a completely different location list (3, 4).
#EmployeeId = 4
4
6
Employees 1, 2, and 3 has locations 1 & 2. It doesn't exactly match with locations 1, 2, 3 & 4 that Employee 4 has and Employee 5 has a partial location list (3, 4). Only Employee 4 & 6 has the same location list (1, 2, 3, 4).
#EmployeeId = 5
5
Employees 1, 2, and 3 has locations 1 & 2. It doesn't exactly match with locations 3 & 4 that Employee 5 has and Employee 4 & 6 has a bigger location list (1, 2, 3, 4).
I started writing a query but got all confused, here is what I have which of course is not correct.
DECLARE #EmployeeId int = 1
Select ELA.EmployeeId, ELA.LocationId from EmployeeLocationAssn ELA
Where not exists
(Select ELA.LocationId from EmployeeLocationAssn ELA2 where ELA2.EmployeeId = #EmployeeId
EXCEPT
Select ELA.LocationId from EmployeeLocationAssn ELA3 where ELA3.EmployeeId = ELA.EmployeeId)
and ELA.EmployeeId <> #EmployeeId;
You can use string_agg (if using SQL Server 2017+) to compare the Employees:
declare #EmployeeId int = 1;
with cte as (
select E.EmployeeId
, string_agg(E.LocationId,',') within group (order by LocationId asc) LocationGroup
from EmployeeLocationAssn E
group by E.EmployeeId
)
select EmployeeId
from cte
where LocationGroup = (select LocationGroup from cte where EmployeeId = #EmployeeId);
Or for xml path if using a lower version:
declare #EmployeeId int = 1;
with cte as (
select E.EmployeeId,
substring(
(
select ',' + convert(varchar(12),E1.LocationId) as [text()]
from #EmployeeLocationAssn E1
where E1.EmployeeId = E.EmployeeId
order by E1.LocationId
for xml path ('')
), 2, 1000) LocationGroup
from #EmployeeLocationAssn E
group by E.EmployeeId
)
select EmployeeId
from cte
where LocationGroup = (select LocationGroup from cte where EmployeeId = #EmployeeId);
dbfiddle
Here's an alternative query that works for your criteria, checking the Locations match those of the selected employee, are not in those not used by the selected employee, and the number of locations match.
declare #EmployeeId int=1
;with x as (
select locationid, Count(*) over() Qty
from EmployeeLocationAssn
where employeeid=#EmployeeId group by LocationId
)
select distinct EmployeeId
from x join EmployeeLocationAssn e on e.LocationId=x.LocationId
where e.employeeid not in (
select EmployeeId
from EmployeeLocationAssn e2
where LocationId not in (select locationId from x)
)
and x.qty=(select Count(*) from EmployeeLocationAssn e3 where e3.EmployeeId=e.EmployeeId)

How to make hierarchy path from bottom SQL Server

I'm trying to make reverse hierarchy path.
This is for path manager-subordinate
WITH CTE_Path AS
(
SELECT
P1.EmployeeID, P1.ManagerID,
CAST(FirstName + LastName AS NVARCHAR(MAX)) AS PathHierarchy,
1 AS level
FROM
EmployeesManagers AS P1
WHERE
ManagerID IS NULL
UNION ALL
SELECT
P2.EmployeeID, P2.ManagerID,
CONCAT(CAST(C.PathHierarchy AS NVARCHAR(MAX)), N' > ',
CAST(P2.FirstName+LastName AS NVARCHAR(MAX))), level + 1
FROM
EmployeesManagers AS P2
JOIN
CTE_Path AS C ON C.EmployeeID = P2.ManagerID
)
SELECT PathHierarchy
FROM CTE_Path
Data for this
CREATE TABLE dbo.EmployeesManagers
(
EmployeeID int NOT NULL PRIMARY KEY,
FirstName varchar(50) NOT NULL,
LastName varchar(50) NOT NULL,
ManagerID int NULL
)
GO
INSERT INTO EmployeesManagers VALUES (101, 'Ken', 'Sánchez', NULL)
INSERT INTO EmployeesManagers VALUES (102, 'Terri', 'Duffy', 101)
INSERT INTO EmployeesManagers VALUES (103, 'Roberto', 'Tamburello', 101)
INSERT INTO EmployeesManagers VALUES (104, 'Rob', 'Walters', 102)
INSERT INTO EmployeesManagers VALUES (105, 'Gail', 'Erickson', 102)
INSERT INTO EmployeesManagers VALUES (106, 'Jossef', 'Goldberg', 103)
INSERT INTO EmployeesManagers VALUES (107, 'Dylan', 'Miller', 103)
INSERT INTO EmployeesManagers VALUES (108, 'Diane', 'Margheim', 105)
INSERT INTO EmployeesManagers VALUES (109, 'Gigi', 'Matthew', 105)
INSERT INTO EmployeesManagers VALUES (110, 'Michael', 'Raheem', 106)
And I need subordinate-manager-manager1 - ...
What to change here? Anchor or recursive part?
Is it possible to create function where you can give argument EmployeeID and it will return path employee-manager-manager1-ceoOfCompany?
You can reverse the logic by starting where employees are not a manager and then tweak the join in the second part of the recursive subquery:
WITH CTE AS (
SELECT P1.EmployeeID, P1.ManagerID, CAST(FirstName+LastName AS NVARCHAR(MAX)) AS PathHierarchy, 1 as level
FROM EmployeesManagers AS P1
WHERE NOT EXISTS (SELECT 1 FROM EmployeesManagers em2 WHERE EM2.ManagerID = p1.EmployeeId)
UNION ALL
SELECT P2.EmployeeID, P2.ManagerID, CONCAT(CAST(C.PathHierarchy AS NVARCHAR(MAX)), N' > ', CAST(P2.FirstName+LastName AS NVARCHAR(MAX))), level+1
FROM CTE c JOIN
EmployeesManagers P2
ON P2.EmployeeID = C.ManagerID
)
SELECT PathHierarchy
FROM CTE;
Here is a db<>fiddle.

Selecting data across mulitple tables in Oracle SQL

I am new to SQL and I am trying to lists all organizations by name in ascending order with the maximum salary across all employees who belong to the organization. I have the Schema Below. Can anyone offer any help? I am also trying to lists all employees by name and if the employee is a manager of at least one organization, provide a row in the results with the name
of each org he/she manages else null.
SELECT Org.name, MAX(Employee.salary) AS "Highest salary"
From Org, Employee
GROUP BY Org.name;
The above code gets me a list of all the Org Names with the global max salary, but I am looking for the max of each Org. I think I may need to use some joins but I am not very familiar.
CREATE TABLE Employee
(
employeeId numeric(9) not null,
name varchar2(100) unique,
salary numeric(9) not null,
CONSTRAINT employeeId_pk PRIMARY KEY (employeeId)
);
CREATE TABLE Org
(
orgId numeric(9) not null,
name varchar2(100) not null unique,
managerId numeric(9) not null,
CONSTRAINT orgId_pk PRIMARY KEY (orgId),
CONSTRAINT managerId_fk FOREIGN KEY (managerId)
REFERENCES Employee(employeeId)
);
CREATE TABLE EmployeeOrg
(
employeeId numeric(9) not null,
orgId numeric(9) not null,
CONSTRAINT employeeId_orgId_pk PRIMARY KEY (employeeId, orgId),
CONSTRAINT employeeId_fk FOREIGN KEY (employeeId)
REFERENCES Employee(employeeId),
CONSTRAINT orgId_fk FOREIGN KEY (orgId)
REFERENCES Org(orgId)
);
INSERT ALL
INTO Employee (employeeId, name, salary) VALUES (123, 'Jim', 123)
INTO Employee (employeeId, name, salary) VALUES (456, 'Bill', 1456)
INTO Employee (employeeId, name, salary) VALUES (789, 'Frank', 456)
INTO Employee (employeeId, name, salary) VALUES (987, 'Sara', 45668)
INTO Employee (employeeId, name, salary) VALUES (654, 'Liz', 4456)
INTO Employee (employeeId, name, salary) VALUES (321, 'Morgan', 4556)
SELECT * FROM dual;
INSERT ALL
INTO Org (orgId, name, managerId) VALUES (1, 'Sales', 123)
INTO Org (orgId, name, managerId) VALUES (2, 'HR', 789)
INTO Org (orgId, name, managerId) VALUES (3, 'E Suite', 987)
INTO Org (orgId, name, managerId) VALUES (4, 'Marketing', 654)
SELECT * FROM dual;
INSERT ALL
INTO EmployeeOrg (employeeId, orgId) VALUES (123, 1)
INTO EmployeeOrg (employeeId, orgId) VALUES (789, 2)
INTO EmployeeOrg (employeeId, orgId) VALUES (987, 3)
INTO EmployeeOrg (employeeId, orgId) VALUES (654, 4)
INTO EmployeeOrg (employeeId, orgId) VALUES (123, 4)
INTO EmployeeOrg (employeeId, orgId) VALUES (456, 1)
INTO EmployeeOrg (employeeId, orgId) VALUES (321, 2)
INTO EmployeeOrg (employeeId, orgId) VALUES (789, 4)
INTO EmployeeOrg (employeeId, orgId) VALUES (456, 2)
SELECT * FROM dual;
EDIT (from OP's comment)
The below code gets me a list of all the Org Names with the global max salary, but I am looking for the max of each Org. I think I may need to use some joins but I am not very familiar.
SELECT Org.name, MAX(Employee.salary) AS "Highest salary"
From Org, Employee
GROUP BY Org.name
select o.orgId, min(o.name) as orgName, max(e.salary) as maxSalary
from Org o
inner join EmployeeOrg eo on eo.orgId = o.orgId
inner join Employee e on e.employeeId = eo.employeeId
group by o.orgId
order by orgName;
select e.name as empName, o.name as orgName
from Employee e left outer join Org o on o.managerId = e.employeeId
order by empName, orgName;

Can we correct, improvise and/or optimize this solution to a common SQL: Displays two oldest employees in each department of an organization

Below, created a (SQL server table variable) schema, inserted dummy data (for testing) and attempted to write a query to display two (replaceable by N) employees from every department who has stayed with the company longest. Can someone please help in improving, correcting and/or optimizing this solution. (feel free to execute the code in SQL management studio as-it-is to run to execute and see results if needed)
--declare employee variable to hold employee data
DECLARE #employee TABLE
(
id int,
name varchar(50),
startdate datetime,
enddate datetime,
departmentid int
)
--declare department variable to hold department data
DECLARE #department TABLE
(
id int,
name varchar(50)
)
--insert dummy department data
INSERT INTO #department VALUES (1, 'IT'),
(2, 'SALES'),
(3, 'HR')
--insert dummy employee data
INSERT INTO #employee VALUES (1, 'mikhail', '01/01/2005', '01/01/2013', 1),
(2, 'david', '01/01/2006', '01/01/2012', 1),
(3, 'andrew', '01/01/2002', null, 1),
(4, 'will', '01/01/2013', null, 1),
(5, 'dave', '01/01/2006', '01/01/2012', 2),
(6, 'mike', '01/01/2002', '01/01/2012', 2),
(7, 'brad', '01/01/2011', null, 2),
(8, 'thomas', '01/01/2002', '01/01/2003', 3),
(9, 'anthony', '01/01/2015', null, 3),
(10, 'vincent', '01/01/2002', null, 3),
(11, 'bobby', '01/01/2002', '01/01/2003', 3);
--declare variable to old intermediate data
DECLARE #hold TABLE (rowid int, ename varchar(50), timew int, dname varchar(50))
-- insert intermediate data
INSERT INTO #hold
SELECT row_number() OVER (PARTITION BY dname ORDER BY timew DESC) rowid, ename, timew, dname
FROM
(
SELECT E.name ename, E.startdate startdate, E.enddate enddate,
CASE
WHEN E.enddate is null then datediff(DAY, E.startdate, getdate())
ELSE datediff(DAY, E.startdate, E.enddate)
END timew, D.name dname
FROM #employee E inner join #department D ON E.departmentid = D.id
) PART
-- final result
SELECT ename, dname FROM #hold WHERE rowid < 3
You can combine some of the logic and make the query smaller but other than that you seem to have a good understanding of how it should be done.
SELECT ename, dname
FROM
(
SELECT e.name ename,
d.name dname,
ROW_NUMBER() OVER (PARTITION BY d.name ORDER BY datediff(DAY, E.startdate, COALESCE(e.enddate, getdate())) desc) rn
FROM #employee e
INNER JOIN #department d ON e.departmentid = d.id
) t
WHERE rn < 3
One thing I might suggest would be to consider using DENSE_RANK instead of ROW_NUMBER if you want to include ties
SELECT ename, dname
FROM
(
SELECT e.name ename,
d.name dname,
DENSE_RANK() OVER (PARTITION BY d.name ORDER BY datediff(DAY, E.startdate, COALESCE(e.enddate, getdate())) desc) rnk
FROM #employee e
INNER JOIN #department d ON e.departmentid = d.id
) t
WHERE rnk < 3
More information on Ranking Functions

How do you join tables sharing the same column?

I made an SQL Fiddle and what I would like to do is join these two queries by using the departmentid.
What I would like to show is the departmentname and not_approved_manager.
Would it be best to use a union or join in this case?
Tables
create table cserepux
(
status int,
comment varchar(25),
departmentid int,
approveddate datetime
);
insert into cserepux (status, comment, departmentid, approveddate)
values (1, 'testing1', 1, NULL), (1, 'testing2', 1, NULL),
(1, 'testing2', 2, NULL), (0, 'testing2', 1, NULL),
(0, 'tesitng2', 1, NULL), (0, 'testing2', 1, NULL),
(0, 'tesitng2', 1, NULL), (0, 'testing3', 2, NULL),
(0, 'testing3', 3, NULL);
create table cseDept
(
departmentid int,
department_name varchar(25)
);
insert into cseDept (departmentid,department_name)
values (1, 'department one'), (2, 'department two'),
(3, 'department three'), (4, 'department four');
Query
select
departmentid,
COUNT(*) AS 'not_approved_manager'
from
cserepux
where
approveddate is null
group by
departmentid
SELECT * FROM cseDept
You need to do a join. A union will not get you what you want.
select d.department_name, COUNT(*) AS 'not_approved_manager'
from cserepux c
inner join cseDept d on c.departmentid = d.departmentid
where approveddate is null
group by d.department_name
Do you need just a join and a correct group by
select dep.department_name, COUNT(*) AS 'not_approved_manager'
from cseDept dep
join cserepux cs on cs.departmentid = dep.departmentid
where approveddate is null
group by dep.department_name
Fiddle: http://sqlfiddle.com/#!3/5cf4e/30
Since joins and group by are really basic things in SQL I can suggest you do take a look on some tutorials to get a bit more proficiency whit it. You can try SQL Server Central stairway articles series