SQL Server : LEFT JOIN EMPLOYEE MANAGER relationship - sql

I have a SQL Server table employee:
MANAGERID is a self-referencing foreign key to EMPLOYEEID. I want to build a query which generates output of a 4th column called DESIGNATION.
The desired result looks like this:
The logic is
Designation must be “Associate” when no other employee is reporting to this employee
Designation must be “Manager” when one or more employees are reporting to this employee
Designation must be “Head” when one or more managers are reporting to this employee
How to achieve this in SQL Server?

You can just use case with not exists:
select e.*,
(case when managerid is null then 'head'
when not exists (select 1 from employee e2 where e2.managerid = e.id)
then 'associate'
else 'manager'
end) as designation
from employee e;
EDIT:
So, "head" seems to mean "manager's manager". That can be accomplished as:
select e.*,
(case when exists (select
from employee e2 join
employee e3
on e2.id = e3.managerid
where e2.managerid = e.id
)
then 'head'
when not exists (select 1 from employee e2 where e2.managerid = e.id)
then 'associate'
else 'manager'
end) as designation
from employee e;

Related

TROUBLE IN MULTIPLE SUBSELECT QUERY

I need to find the name, function, officename and the salary of all employee with the same function as PETER or a salary greater or equal than the salary of SANDERS. order by function and salary.
There are two tables: office and employee
table office contains:
officenumber
name
city
table employee contains:
employeenumber
name
function
manager
sal
officenumber
this is my current SQL query:
SELECT NAME,
FUNCTION,
SAL
FROM EMPLOYEE
WHERE FUNCTIE = (SELECT FUNCTION
FROM EMPLOYEE
WHERE NAME = 'PIETERS')
I'm stuck with the query.
Assuming this is SQL Server (you never specified), something like this should work.
SELECT
e.name,
e.function,
e.sal,
o.name AS officename
FROM employee e
JOIN office o ON e.officenumber = o.officenumber
WHERE
e.function = (SELECT function FROM employee WHERE name = 'PIETERS') OR
e.sal >= (SELECT sal FROM employee WHERE name = 'SANDERS')
ORDER BY e.function, e.salary
You'll have to tweak this a bit if you're working with MySQL or something else.
Three things you need to do here:
1. join the two tables, since you need results from both tables
2. filter the results according to the two criterias
3. order the results:
The first part is easy, just need to join them according to the officenumber:
select e.name, e.function, o.name as officeName, e.salary from
employee e inner join office o
on e.officenumber = o.officenumber
second part, simple where clause:
where e.function = (select function from employee where name = 'PETER')
or e.salary >= (select salary from employee where name = 'SANDERS')
and the last, ordering:
order by e.function, e.salary
Putting it all together:
select e.name, e.function, o.name as officeName, e.salary from
employee e inner join office o
on e.officenumber = o.officenumber
where e.function = (select function from employee where name = 'PETER')
or e.salary >= (select salary from employee where name = 'SANDERS')
order by e.function, e.salary

Get employee and his manager details without joins

I have a table Employee which contains Employee id and Manager id. I want to get Employee id, Employee's Manager id, and Employee's manager's Manager id.
I can get it using self join and other joins like
Select employe id, Manager id
from emplyee as a, employee as b
where a.manager_id = b.employee id
But is there a better way? Can we do this without joins, by only querying the table once?
If you don't consider a co-related subquery as a join you can do this:
Select e.employee_id,
e.manager_id,
(select manager_id
from employee e2
where e2.employee_id = e.manager_id) as employee_manager_id
from employee e;
But at some point you have to do some kind of "join".
you can use recursive CTE but still you have to use a join
Look at this example:
WITH MyCTE
AS (
SELECT EmpID, FirstName, LastName, ManagerID
FROM Employee
WHERE ManagerID IS NULL
UNION ALL
SELECT EmpID, FirstName, LastName, ManagerID
FROM Employee
INNER JOIN MyCTE ON Employee.ManagerID = MyCTE.EmpID
WHERE Employee.ManagerID IS NOT NULL
)
SELECT *
FROM MyCTE
please refer to this link for detail of CTE by Pinal Dave
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/

Select users belonging only to particular departments

I have the following table with two fields namely a and b as shown below:
create table employe
(
empID varchar(10),
department varchar(10)
);
Inserting some records:
insert into employe values('A101','Z'),('A101','X'),('A101','Y'),('A102','Z'),('A102','X'),
('A103','Z'),('A103','Y'),('A104','X'),('A104','Y'),('A105','Z'),('A106','X');
select * from employe;
empID department
------------------
A101 Z
A101 X
A101 Y
A102 Z
A102 X
A103 Z
A103 Y
A104 X
A104 Y
A105 Z
A106 X
Note: Now I want to show the employee who is only and only belongs to the department Z and Y.
So according to the condition the only employee A103 should be displayed because of he only belongs
to the department Z and Y. But employee A101 should not appear because he belong to Z,X, and Y.
Expected Result:
If condition is : Z and Y then result should be:
empID
------
A103
If condition is : Z and X then result should be:
empID
------
A102
If condition is : Z,X and Y then result should be:
empID
------
A101
Note: I want to do it in the where clause only (don't want to use the group by and having clauses), because I'm going to include this one in the other where also.
This is a Relational Division with no Remainder (RDNR) problem. See this article by Dwain Camps that provides many solution to this kind of problem.
First Solution
SQL Fiddle
SELECT empId
FROM (
SELECT
empID, cc = COUNT(DISTINCT department)
FROM employe
WHERE department IN('Y', 'Z')
GROUP BY empID
)t
WHERE
t.cc = 2
AND t.cc = (
SELECT COUNT(*)
FROM employe
WHERE empID = t.empID
)
Second Solution
SQL Fiddle
SELECT e.empId
FROM employe e
WHERE e.department IN('Y', 'Z')
GROUP BY e.empID
HAVING
COUNT(e.department) = 2
AND COUNT(e.department) = (SELECT COUNT(*) FROM employe WHERE empID = e.empId)
Without using GROUP BY and HAVING:
SELECT DISTINCT e.empID
FROM employe e
WHERE
EXISTS(
SELECT 1 FROM employe WHERE department = 'Z' AND empID = e.empID
)
AND EXISTS(
SELECT 1 FROM employe WHERE department = 'Y' AND empID = e.empID
)
AND NOT EXISTS(
SELECT 1 FROM employe WHERE department NOT IN('Y', 'Z') AND empID = e.empID
)
I know that this question has already been answered, but it was a fun problem to do and I tried to do it in a way that no one else has. Benefits of mine is that you can input any list of strings as long as each value has a comma afterwards and you don't have to worry about checking counts.
Note: Values must be listed in alphabetic order.
XML Solution with CROSS APPLY
select DISTINCT empID
FROM employe A
CROSS APPLY
(
SELECT department + ','
FROM employe B
WHERE A.empID = B.empID
ORDER BY department
FOR XML PATH ('')
) CA(Deps)
WHERE deps = 'Y,Z,'
Results:
empID
----------
A103
For condition 1:z and y
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'y' ) as y
on z.empID = y.empID
where z.empID Not in(select empID from employe where department = 'x' )
For condition 1:z and x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
where z.empID Not in(select empID from employe where department = 'y' )
For condition 1:z,y and x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
inner join (select empID from employe where department = 'y' ) as y on
y.empID=Z.empID
You can use GROUP BY with having like this. SQL Fiddle
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
Without GROUP BY and Having
SELECT empID
FROM employe E1
WHERE (SELECT COUNT(DISTINCT department) FROM employe E2 WHERE E2.empid = E1.empid and department IN ('Z','Y')) = 2
EXCEPT
SELECT empID
FROM employe
WHERE department NOT IN ('Z','Y')
If you want to use any of the above query with other tables using a join you can use CTE or a derived table like this.
;WITH CTE AS
(
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
)
SELECT cols from CTE join othertable on col_cte = col_othertable
try this
select empID from employe
where empId in (select empId from employe
where department = 'Z' and department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;
for If condition is : Z and Y
SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT='Z' AND
EMPID IN (SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT ='Y')AND
EMPID NOT IN(SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT NOT IN ('Z','Y'))
The following query works when you want employees from departments 'Y' and 'Z' and not 'X'.
select empId from employe
where empId in (select empId from employe
where department = 'Z')
and empId in (select empId from employe
where department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;
For your second case, simply replace not in with in in the last condition.
Try this,
SELECT a.empId
FROM employe a
INNER JOIN
(
SELECT empId
FROM employe
WHERE department IN ('X', 'Y', 'Z')
GROUP BY empId
HAVING COUNT(*) = 3
)b ON a.empId = b.empId
GROUP BY a.empId
Count must based on number of conditions.
You can too use GROUP BY and HAVING — you just need to do it in a subquery.
For example, let's start with a simple query to find all employees in departments X and Y (and not in any other departments):
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
I've used MySQL's GROUP_CONCAT() function as a convenient shortcut here, but you could get the same results without it, too, e.g. like this:
SELECT empID,
COUNT(DISTINCT department) AS all_depts,
COUNT(DISTINCT CASE
WHEN department IN ('X', 'Y') THEN department ELSE NULL
END) AS wanted_depts
FROM emp_dept GROUP BY empID
HAVING all_depts = wanted_depts AND wanted_depts = 2
Now, to combine this with other query condition, simply take a query that includes the other conditions, and join your employees table against the output of the query above:
SELECT empID, name, depts
FROM employees
JOIN (
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
) AS tmp USING (empID)
WHERE -- ...add other conditions here...
Here's an SQLFiddle demonstrating this query.
Ps. The reason why you should use a JOIN instead of an IN subquery for this is because MySQL is not so good at optimizing IN subqueries.
Specifically (as of v5.7, at least), MySQL always converts IN subqueries into dependent subqueries, so that the subquery must be re-executed for every row of the outer query, even if the original subquery was independent. For example, the following query (from the documentation linked above):
SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);
gets effectively converted into:
SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);
This may still be reasonably fast, if t2 is small and/or has an index allowing fast lookups. However, if (like in the original example above) executing the subquery might take a lot of work, the performance can suffer badly. Using a JOIN instead allows the subquery to only be executed once, and thus typically offers much better performance.
What about a self join? (ANSI Compliant - worked for 20+ years)
SELECT * FROM employee e JOIN employee e2 ON e.empid = e2.empid
WHERE e.department = 'x' AND e2.department ='y'
This shows that a101 and a104 both work in both departments.
Solution using where clause:
select distinct e.empID
from employe e
where exists( select *
from employe
where empID = e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3)
exists checks if there are records for specific EmpId that have total count of departments equal to conditional count of only matching departments and that it is also equal to the number of departments provided to the in clause. Also worth mentioning that here we apply having clause without the group by clause, on the whole set, but with already specified, only one empID.
SQLFiddle
You can achieve this without the correlated subquery, but with the group by clause:
select e.empId
from employe e
group by e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3
SQLFiddle
You can also use another variation of having clause for the query above:
having count(case when department not in('Y','X', 'Z') then department end) = 0
and count(distinct case when department in('Y','X','Z') then department end) = 3
SQLFiddle
In Postgres this can be simplified using arrays:
select empid
from employee
group by empid
having array_agg(department order by department)::text[] = array['Y','Z'];
It's important to sort the elements in the array_agg() and compare them to a sorted list of departments in the same order. Otherwise this won't return correct answers.
E.g. array_agg(department) = array['Z', 'Y'] might potentially return wrong results.
This can be done in a more flexible manner using a CTE to supply the departments:
with depts_to_check (dept) as (
values ('Z'), ('Y')
)
select empid
from employee
group by empid
having array_agg(department order by department) = array(select dept from depts_to_check order by dept);
That way the sorting of the elements is always done by the database and will be consistent between the values in the aggregated array and the one to which it is compared.
An option with standard SQL is to check if at least one row has a different department together with counting all rows
select empid
from employee
group by empid
having min(case when department in ('Y','Z') then 1 else 0 end) = 1
and count(case when department in ('Y','Z') then 1 end) = 2;
The above solution won't work if it's possible that a single employee is assigned twice to the same department!
The having min (...) can be simplified in Postgres using the aggregate bool_and().
When applying the standard filter() condition to do conditional aggregation this can also be made to work with situation where an employee can be assigned to the same department twice
select empid
from employee
group by empid
having bool_and(department in ('Y','Z'))
and count(distinct department) filter (where department in ('Y','Z')) = 2;
bool_and(department in ('Y','Z')) only returns true if the condition is true for all rows in the group.
Another solution with standard SQL is to use the intersection between those employees that have at least those two departments and those that are assigned to exactly two departments:
-- employees with at least those two departments
select empid
from employee
where department in name in ('Y','Z')
group by empid
having count(distinct department) = 2
intersect
-- employees with exactly two departments
select empid
from employee
group by empid
having count(distinct department) = 2;

How to get all departments with Employee number

I have an EmployeeDepartmetn juction table like this. I have all the departments in Depeartment table and employees in Employee table..
I want to get departments for an particular employee along with the all the departments available in depeartment table.
It should be like Select DepartmentId, DepartmentName, EmployeeID from Query.
Main criteria here is, Need to display NULL if the employee dont have that department. I am confused here...please help.
Please give Linq Query
Thanks in Advance
Put criteria in your left join:
Select distinct a.DeptID, b.DepartmentName, b.EmployeeID
From Department a
left join EmployeeDepartment b
on a.DeptID = b.DeptID and b.EmployeeID = 1 --insert employee ID here
It will show all departments (even those with no employees), then show the employee ID you chose in the third column only if that employee is assigned there.
You can do this with conditional aggregation:
select DeptId,
max(case when EmployeeId = 1 then EmployeeId end) as EmployeeId
from EmployeeDepartment ed
group by DeptId;
EDIT:
If you have a departments table as well:
select d.deptid, d.name, ed.employeeid
from Departments d left join
EmployeeDepartment ed
on d.deptid = ed.deptid and
ed.employeeid = 1;

Query to fetch the manager name by managerID

I have a table named Employee and it has the fields as ID, Name, EmpID, rankID, DeptID and managerID. I have set the managerID as a foreign key to the Employee table with reference to ID in employee table. Now I want a query to fetch all the employee and their manager information. The "manager information should be manager name not the managerID."
select e1.ID, e1.Name, e1.EmpID, e1.rankID, e1.DeptID, e2.name as managername
from employee e1
left outer join employee e2 on e1.managerID = e2.id