Get count of total employees under each manager (SQL Server) - sql

I need to get, for each of the five people in this table, the total employees reporting to each one of them (either directly or indirectly).
CREATE TABLE employees
([employee] nvarchar(30), [manager] nvarchar(30))
;
INSERT INTO employees
([employee], [manager])
VALUES
('Robert', null),
('Julian', 'Robert'),
('Sophie', 'Robert'),
('Lucy', 'Julian'),
('John', 'Lucy')
;
The output I expect is:
employee
subordinates
Robert
4
Julian
2
Lucy
1
Sophie
0
John
0
I have searched everywhere how to do this type of recursion/hierarchical query, and it seems it is very common to approach this kind of exercise to get the employee hierarchy only (which I managed to do already), but not the total count of employees under each manager.
The best I could get was the following, but it only gets the count for the top manager, while I need it for all employees in the company in a single table (from an answer to this post: SQL Query to get recursive count of employees under each manager).
WITH EmployeeCTE
AS (SELECT e.employee, e.manager as topmanager
FROM employees e, employees m
WHERE e.manager = m.employee AND m.manager IS NULL
UNION ALL
SELECT e.employee, mgr.topmanager
FROM employees e, EmployeeCTE mgr
WHERE e.manager = mgr.employee)
SELECT topmanager, count(*)
FROM EmployeeCTE AS u
GROUP BY topmanager;
Here is a Fiddle to this: http://sqlfiddle.com/#!18/380ba/2

The problem is with getting the zero subordinates for those without subordinates.
One way to get them is via a LEFT JOIN of all employees to the results of the recursive CTE.
WITH CTE_Employees AS (
SELECT employee
FROM employees
GROUP BY employee
)
, RCTE_Managers AS (
SELECT
manager as base_manager
, 1 as depth
, employee
, manager as direct_manager
FROM employees
WHERE manager IS NOT NULL
UNION ALL
SELECT
cte.base_manager
, cte.depth + 1
, emp.employee
, emp.manager
FROM RCTE_Managers cte
JOIN employees emp
ON emp.manager = cte.employee
)
SELECT
emp.employee
, COUNT(DISTINCT mgr.employee) AS subordinates
FROM CTE_Employees emp
LEFT JOIN RCTE_Managers mgr
ON mgr.base_manager = emp.employee
GROUP BY emp.employee
ORDER BY COUNT(mgr.employee) DESC, emp.employee;
employee
subordinates
Robert
4
Julian
2
Lucy
1
John
0
Sophie
0
Test on db<>fiddle here

Related

matching pair of employees in a same department

I'm trying to make a list of employees working in a same department like:
employeeName
department
employeeName
Tim
2
kim
Tim
2
Jim
Kim
2
Tim
Kim
2
Jim
Jim
2
Kim
Jim
2
Tim
Aim
3
Sim
Sim
3
Aim
But the only thing i can do for now is:
SELECT emp_name, dept_code
FROM employee
WHERE dept_code IN (SELECT dept_code FROM employee);
employeeName
department
Tim
2
Kim
2
Jim
2
Aim
3
Sim
3
How can I make a list pairing with the employee working in a same department? thanks gurus...
To first point that out: I dislike your idea to create such a result listing "pairs" twice and would prefer another, easier query whose results would be better to read. I will come back to this later in this answer.
But anyway, if you really want to produce the outcome you have shown, we can do this with CROSS JOIN. This builds all combinations of employees.
In the WHERE clause, we will set the conditions that they must work in the same department, but have different names:
SELECT
e1.emp_name AS employeeName,
e1.dept_code AS department,
e2.emp_name AS employeeName
FROM
employee e1
CROSS JOIN employee e2
WHERE
e1.dept_code = e2.dept_code
AND e1.emp_name <> e2.emp_name
ORDER BY e1.dept_code, e1.emp_name, e2.emp_name;
To come back to the idea to make this much easier and better to read: We can just use LISTAGG with GROUP BY to produce a comma-separated list of employees per department. I highly recommend to use this approach due to much better performance and readability.
This query will do on new Oracle DB's:
SELECT dept_code,
LISTAGG (emp_name,',') AS employees
FROM employee
GROUP BY dept_code;
On older Oracle DB's, we need to add a WITHIN GROUP clause:
SELECT dept_code,
LISTAGG (emp_name,',')
WITHIN GROUP (ORDER BY emp_name) AS employees
FROM employee
GROUP BY dept_code;
This will produce following result for your sample data:
DEPT_CODE
EMPLOYEES
2
Jim,Kim,Tim
3
Aim,Sim
Here we can try out these things: db<>fiddle
You will get all the pairs (A,B) and (B,A) of employees in the same department at the exclusion of all (A,A) with:
SELECT e1.emp_name AS first_emp_name, e1.dept_code, e2.emp_name AS second_emp_name
FROM employee e1
JOIN employee e2 ON e1.dept_code = e2.dept_code AND e1.emp_name <> e2.emp_name ;

How to mark employees which are also manager

I need to determine whether an employee is any other employee's manager.
Given this table:
Employee Employee's Manager
---------- ------------------
Bob CN=Lisa
Amanda CN=Lisa
James CN=Art
Frank CN=Amanda
Amy CN=Art
I need this:
Employee Employee's Manager Employee IS Manager
---------- ------------------ -------------------
Bob CN=Lisa N
Amanda <-- CN=Lisa Y <--
James CN=Art N
Frank CN=Amanda <-- N
Amy CN=Art N
Because Amanda appears in the "Employee's Manager" column in another employee's row, I need to derive this, adding the additional "Employee IS Manager" field.
I've gotten as far as this (wrong!) subquery for the additional "IS Manager" field, but I do not know how to add it as a column in a subquery:
select
a.* ,
(select 'Y' as IsManager
where exists (select * from Employees b where b.Manager like '%' + #x+ '%' )
)
from Employees a
But I do not know how to make #x refer to Amanda in the Employee column in the other row.
EDIT: I should note that I am not necessarily looking for a "subquery" solution. A JOIN solution, or any other kind of solution is fine for my purposes. Thanks.
You are close but you need a case expression:
select e.* ,
(case when exists (select 1
from Employees m
where m.Manager like '%=' + e.employee_manager
)
then 'Y' else 'N' end
) as isManager
from Employees e;
Note that I tweaked the logic for matching so "Anne" and Roseanne" do not get confused. If the manager always starts with 'CN=', then use like 'CN=' + instead.
You can also use outer apply to get your desired result. Here through outer apply we are getting 'Y' when an employee is also a manager other wise it's returning null. Coalesce() is used to convert null to 'N'.
Schema and insert statements:
create table Employees (Employee varchar(50),employee_Manager varchar(50));
insert into Employees values('Bob', 'CN=Lisa');
insert into Employees values('Amanda', 'CN=Lisa');
insert into Employees values('James', 'CN=Art' );
insert into Employees values('Frank', 'CN=Amanda');
insert into Employees values('Amy', 'CN=Art' );
Query:
select
a.*,coalesce(isManager,'N')[Employee IS Manager]
from Employees a outer apply(select 'Y' from Employees b where b.employee_manager='CN='+a.Employee)manager (isManager)
Output:
Employee
employee_Manager
Employee IS Manager
Bob
CN=Lisa
N
Amanda
CN=Lisa
Y
James
CN=Art
N
Frank
CN=Amanda
N
Amy
CN=Art
N
db<fiddle here

How to concatenate all entries returned by a joined subquery (related to the main table id) into a single combined column?

Let's say I have a main driver table called Employee with the following data and columns:
EMPLOYEE_ID EMPLOYEE_NAME EMPLOYEE_CAR EMPLOYEE_AGE
----------------------------------------------------------------
1 Mike Camry 35
2 Mark Civic 33
3 Helen Beetle 25
And I have another table called Employee_Feedback with the following data and columns:
EMPLOYEE_FEEDBACK_ID EMPLOYEE_ID FEEDBACK_COMMENT_TX
---------------------------------------------------------------
1 1 Very Good
2 1 Average
3 1 Phenomenal
4 2 Okay
5 2 NO Comment
6 3 Excellent
7 3 Hilarious
With the data from the Employee and Employee_Feedback tables, I want my query to be able to return all of the rows in Employee and also to concatenate all of the 'related' employee feedback_comments from Employee_feedback into a single column per employee. It would look like this:
EMPLOYEE_NAME EMPLOYEE_CAR EMPLOYEE_AGE Comments
------------------------------------------------------------------------
Mike Camry 35 Very Good Average Phenomenal
Mark Civic 33 Okay No Comment
Helen Beetle 25 Excellent Hilarious
What would be a good query to do this? I've tried the following with no success:
select employee_name
, employee_car
, employee_age
, listagg(feedback_comment_Tx, ' ') within group (order by e.employee_id)
from employee e
join employee_feedback ef ON ef.employee_id = e.employee_id;
Am I missing a minor detail? Thanks in advance!
select employee_name
, employee_car
, employee_age
, listagg(feedback_comment_Tx, ' ') within group (order by ef.EMPLOYEE_FEEDBACK_ID)
from employee e
join employee_feedback ef ON ef.employee_id = e.employee_id
group by e.employee_id;
You need to add a group by clause at the end :
group by employee_name, employee_car, employee_age
Edit : including your filters in the comment, you may try the following :
select employee_name
, employee_car
, employee_age
, listagg(feedback_comment_Tx, ' ') within group (order by e.employee_id)
from employee e
join employee_feedback ef ON ef.employee_id = e.employee_id
where nvl(initcap(feedback_comment_Tx),'No Comment') != 'No Comment'
group by employee_name, employee_car, employee_age;
Have you tried to add group by e.employee_id at the end of query like that
select employee_name
, employee_car
, employee_age
, listagg(feedback_comment_Tx, ' ') within group (order by e.employee_id)
from employee e join employee_feedback ef ON ef.employee_id = e.employee_id
group by e.employee_id

How can I add '-' before a name to denote hierarchical standings in a recursive stored procedure

I am attempting to create a tree view within SSMS, utilizing a recursive SP.
Here is my code...
#p_Renumber NVARCHAR(7)
AS
DECLARE #orgChart TABLE
(
HMY NUMERIC(18,0) NOT NULL,
RENUMBER NVARCHAR(7) NOT NULL,
MANAGER_ID NVARCHAR(7) NULL,
ORGLEVEL NUMERIC(18,0) NOT NULL
);
WITH OrgChartDown (HMY, RENUMBER, MANAGER_ID, ORGLEVEL)
AS
(
-- Anchor member definition
SELECT HMY, RENUMBER, MANAGER_ID, 0 AS ORGLEVEL
FROM dbo.Employees
WHERE RENUMBER = #p_Renumber
UNION ALL
-- Recursive member definition down tree
SELECT e.HMY, e.RENUMBER, e.MANAGER_ID, o.ORGLEVEL + 1
FROM dbo.Employees e
INNER JOIN OrgChartDown o ON e.MANAGER_ID = o.RENUMBER
WHERE e.[STATUS] = 1
)
INSERT INTO #orgChart
SELECT HMY, RENUMBER, MANAGER_ID, ORGLEVEL
FROM OrgChartDown;
WITH OrgChartUp (HMY, RENUMBER, MANAGER_ID, ORGLEVEL)
AS
(
-- Anchor member definition
SELECT HMY, RENUMBER, MANAGER_ID, 0 AS ORGLEVEL
FROM dbo.Employees
WHERE RENUMBER = #p_Renumber
UNION ALL
-- Recursive member definition up tree
SELECT e.HMY, e.RENUMBER, e.MANAGER_ID, o.ORGLEVEL - 1
FROM dbo.Employees e
INNER JOIN OrgChartUp o ON e.RENUMBER = o.MANAGER_ID
WHERE o.RENUMBER != o.MANAGER_ID --CEO's MANAGER_ID equals own RENUMBER
AND e.[STATUS] = 1
)
INSERT INTO #orgChart
SELECT HMY, RENUMBER, MANAGER_ID, ORGLEVEL
FROM OrgChartUp
WHERE RENUMBER != #p_Renumber;
SELECT e.HMY, e.RENUMBER, e.FIRST_NAME + ' ' + e.LAST_NAME AS "NAME",
e.JOB_TITLE, e.MANAGER_ID, o.ORGLEVEL
FROM #orgChart o
INNER JOIN Employees e ON o.HMY = e.HMY
ORDER BY o.ORGLEVEL, e.LAST_NAME
GO
I am currently getting results...
JOB_TITLE ORGLEVEL
President & CEO -2
Chief Administrative Officer -1
Senior Director IT Business Applications 0
Test Tile EMG --(test entity in database)-- 1
Senior Applications Developer 1
Senior Applications Developer 1
Contractor 1
Senior Applications Developer 1
Lead Systems Analyst 1
Lead Systems Analyst 1
(other fields hidden for information security/integrity)
My question is how do I add a dash ("-") as I go down this hierarchical data.
The logic behind it would be to take the ABS(the top most ORGLEVEL), so in this code example the answer would be: ABS(-2) = 2 and then add that to all ORGLEVELS, so that the President and CEO's ORGLEVEL would be equal to 0.
With this newly stored ORGLEVEL I would like to add dashes ("-") to correspond to the value. Meaning my data would look something like this...
JOB_TITLE ORGLEVEL
President & CEO 0
-Chief Administrative Officer 1
--Senior Director IT Business Applications 2
---Test Tile EMG --(test entity in database)-- 3
---Senior Applications Developer 3
---Senior Applications Developer 3
---Contractor 3
---Senior Applications Developer 3
---Lead Systems Analyst 3
---Lead Systems Analyst 3
I know there is a way to do this in the HTML code I am using to bind to my webform, but as this is a dynamic solution I would like to have this done within the stored procedure so it is a simple databind() to the gridview I am using. Let me know if you have any solutions to this problem.
You can try this in your query
replicate('-', (ABS((select min(o.ORGLEVEL) from #orgChart)) + o.ORGLEVEL)) + e.JOB_TITLE
This is a guess, in the absence of sample data, however, I would guess changing the following:
e.JOB_TITLE, e.MANAGER_ID, o.ORGLEVEL
to
REPLICATE('-',o.ORGLEVEL+2) + e.JOB_TITLE AS JOB_TITLE,
e.MANAGER_ID,
o.ORGLEVEL + 2 AS ORGLEVEL

sql query .. confused

I am a newbie and i am not sure how to go about this .. can anybody help me ?
Given the following tables ...
DEPARTMENT
DEPT_ID NAME
1 HR
2 Technical
3 Marketing
4 Sales
EMPLOYEE
ID NAME DEPT_ID SALARY MANAGER_ID
1 Alex 2 2000
2 Sally 1 2000
3 Amit 2 1500 1
4 Jason 1 1500 1
... ... ... ... ...
Using the DEPARTMENT and EMPLOYEE tables from earlier, write a SQL query to print the number of employees working in each department. The output should look something like this:
NAME COUNT
HR 2
Technical 2
Marketing 0
Sales 0
Please note that not all departments have been staffed yet, so some departments may not have any employees. We still want these departments to appear in the results, with a zero count.
Using the EMPLOYEE table from earlier, write a SQL query to print out the number of employees working under each manager. The output should look something like this:
NAME COUNT
Alex 2
Sally 0
You need to join the tables, group by department, and take the count of rows per department.
Try This Link
Solution to the first part of the problem:
select d.NAME, count(e.NAME)
from EMPLOYEE as e
right join DEPARTMENT as d
on e.DEPT_ID = d.DEPT_ID
group by d.DEPT_ID
Solution to the second part of the problem (I'm not sure whether this one is correct or not):
select NAME, count(NAME)
from EMPLOYEE
group by MANAGER_ID
Please check the sqlfiddle: http://sqlfiddle.com/#!2/5e802/13
Try this it may help you :
a)
select d.Name as DepartmentName, count(e.id) as EmployeeCount from employee as e, department as d where e.DEPT_ID = d.DEPT_ID group by d.Name;
b)
select a.name as Manager_Name,Count(a.name) as EmployeeCount from employee a, employee b where a.id=b.MANAGER_ID group by a.name;