Select the manager name with the most employees - sql

We have a table emp with columns empno, ename, job, mgr, hiredate, sal, comm, deptno
I have tried
SELECT m.ename, COUNT(e.empno) FROM emp e
INNER JOIN emp m ON e.empno = m.empno
GROUP BY m.ename HAVING COUNT(e.empno) = GREATEST(COUNT(e.empno));
My output is the names of the managers each with the value 1
How do we output the name of the manager with the most employees?

The following works:
SELECT COUNT(e.empno), m.ename FROM emp e
INNER JOIN emp m ON e.mgr = m.empno
GROUP BY m.ename HAVING GREATEST(COUNT(e.ename)) = COUNT(e.ename)
LIMIT 1;

First, fix the ON clause. Second, Use ORDER BY and LIMIT if you want one row:
SELECT m.ename, COUNT(e.empno)
FROM emp e INNER JOIN
emp m
ON m.empno = e.mgr
GROUP BY m.ename
ORDER BY COUNT(e.empno) DESC
LIMIT 1;
Your HAVING clause does not filter anything because any (non-NULL) value is equal to itself.

Related

Perform SELECT statement inside of SELECT Statement?

I'm using Emp, Dept... databases. I would like to get the Name, Salary, Deptno and Average salary in the department of those employees who earn more than the average of their department. So here is what I'm trying to do:
SELECT e.Ename, e.Sal, e.Deptno
, (
SELECT AVG(Sal)
FROM Emp b
WHERE b.Deptno = e.Deptno
GROUP BY Deptno
) AS 'Average Salary'
FROM Emp e
WHERE e.Sal > (
SELECT AVG(b.Sal)
FROM Emp b
WHERE b.Deptno = e.Deptno
GROUP BY Deptno
);
And I can't use AVG(Sal), because it will give the average salary of the employee, and not the department where he works.
You would use a correlated subquery:
SELECT e.Ename, e.Sal, e.Deptno,
(SELECT AVG(e2.Sal)
FROM Emp e2
WHERE e2.Deptno = e.Deptno
) AS [Average Salary]
FROM Emp e
WHERE e.Sal > (SELECT AVG(e2.Sal)
FROM Emp e2
WHERE e2.Deptno = e.Deptno
);
But in actuality, you would just use a window function:
select e.*
from (select e.*, avg(sal) over (partition by deptno) as avg_sal
from emp e
) e
where sal > avg_sal;
Just get rid of the group by.
SELECT e.Ename, e.Sal, e.Deptno, (SELECT AVG(Sal)
FROM Emp b
WHERE b.Deptno = e.Deptno
) AS 'Average Salary'
FROM Emp e
WHERE e.Sal > (SELECT AVG(b.Sal)
FROM Emp b
WHERE b.Deptno = e.Deptno
);
If you join to the subquery, you will not need to repeat it.
SELECT e.Ename, e.Sal, e.Deptno, dAvgs.avgSal AS 'Average Salary'
FROM Emp AS e
INNER JOIN (
SELECT Deptno, AVG(b.Sal) AS avgSal
FROM Emp b
GROUP BY Deptno
) AS dAvgs
ON e.Deptno = dAvgs.Deptno AND e.Sal > dAvgs.avgSal
;

i get that error ORA-01427: single-row subquery returns more than one row

i wrote this query to get the employee who is manager to select minimum salary for the employees who is managed by that manager
select min(e.sal) from emp e
where e.mgr = (select empno from emp where job = upper('manager'))
group by e.sal, e.mgr
Your subselect select empno from emp where job = upper('manager') returns more than one row.
You could change the WHERE-clause from = to IN - the result will be a list of all managers and the minimum salary of their employees:
select min(e.sal), e.mgr
from emp e
where e.mgr IN (select empno from emp where job = upper('manager'))
group by e.sal, e.mgr
There is more than one manager in company, therefor this query you wrote returns more than one row:
select empno from emp where job = upper('manager')
When you wrote the equal sign = you said to the SQL compiler that you're expecting only one row as a result, from the above query. Instead of writing the equal sign, you shoul use the IN function:
e.mgr IN (select empno from emp where job = upper('manager'))
The full code, as it should have been:
select min(e.sal) from emp e
where e.mgr in (select empno from emp where job = upper('manager'))
group by e.sal, e.mgr

small tricky query on self join

I have a table EMP with columns as below:
create table emp(
empno number(4,0),
ename varchar2(10),
job varchar2(9),
mgr_id number(4,0),
sal number(7,2),
deptno number(2,0));
I want to list all employees' names along with their manager names, including those who do not have a manager. For those employees, their manager's name should be displayed as 'BOSS'.
The following query should work:
select e.ename, (case when m.ename is null then 'BOSS' else m.ename end) as mgrName
from emp e
left join emp m on m.empno = e.mgr_id
To my mind, the better solution is proposed by Charanjith.
In Oracle, we could even use NVL function instead of "case when" in order to replace null value by something. The result should be the same.
select e.ename empName, NVL(m.ename, 'BOSS') mgrName from emp e
left join emp m on m.empno = e.mgr_id
Moreover, we could see another solution : using inner join to filter on emp when a manager exists. Then union for all employees who don't have any manager.
select e.ename empName, m.ename mgrName from emp e inner join emp m on e.mgr_id = m.empno
union
select e.ename empName, 'BOSS' mgrName from emp e where not exists (select 1 from emp m where e.mgr_id = m.empno)
This work fine in oracle:
SELECT e.ename,
nvl(m.ename, 'BOSS')mgr
FROM emp a
LEFT JOIN emp b
ON m.empno = e.mgr_id;

Select departments with or without employees. Also select employees if assigned

I want to select DNAME, DEPTNO from all departments with or without employees assigned. And if a department does have any employees I want to get their ENAME, EMPNO.
I tried this: select e.empno,e.name,d.deptno,d.dname from emp e full join dept d
on e.deptno=d.deptno
But it didn't work.
Table structure:
DEPT
DEPTNO DNAME
EMP
EMPNO ENAME DEPTNO
Use OUTER JOIN(LEFT/RIGHT)
SELECT D.DEPTNO,
D.DNAME,
E.EMPNO,
E.ENAME
FROM DEPT D
LEFT OUTER JOIN EMP E
ON D.DEPTNO = E.DEPTNO
When the Department does not have any employee then NULL will be displayed in E.EMPNO and E.ENAME column
SELECT
DEPT.DEPTNO,DEPT.DNAME,EMP.EMPNO,EMP.NAME
FROM
DEPT LEFT OUTER JOIN
EMP ON DEPT.DEPTNO = EMP.DEPTNO

Using WHERE clause on a joined table

I am trying to find the best way to filter out rows by using conditions based on a joined table. As an example I am joining the employees and salary grade table based on the salary grade for each employee. Then I want to show only the employees that have the same grade as a certain employee (Blake). I used the following code:
SELECT e.ename, e.sal, sg.grade
FROM emp e JOIN salgrade sg
ON(e.sal BETWEEN sg.losal AND sg.hisal)
WHERE sg.grade = (SELECT sg.grade FROM emp e JOIN salgrade sg ON(e.sal BETWEEN sg.losal AND sg.hisal) WHERE e.ename = 'BLAKE')
ORDER BY e.sal DESC
Is there a more optimal way to write the query?
Here is one method that uses window functions:
SELECT es.*
FROM (SELECT e.ename, e.sal, sg.grade,
MAX(CASE WHEN e.ename = 'BLAKE' THEN sg.grade END) OVER () as blake_grade
FROM emp e JOIN
salgrade sg
ON e.sal BETWEEN sg.losal AND sg.hisal
) es
WHERE grade = blake_grade
ORDER BY e.sal DESC;
I wouldn't use a join in the select in the WHERE clause; rather, I would use an inner scalar subquery to pick up BLAKE's salary and then an outer scalar subquery to pick up his salgrade. Otherwise very similar to your query:
select e.ename, e.sal, s.grade
from emp e inner join salgrade s on e.sal between s.losal and s.hisal
where s.grade = ( select grade
from salgrade
where (select sal from emp where ename = 'BLAKE')
between losal and hisal
)
order by sal desc
;
Using the same idea, you could do away with the first join as well (by returning the losal and hisal for BLAKE as well as his salgrade), but perhaps that is taking it too far.
If this is just about not having to write the same code twice, you can use a WITH clause:
WITH emps_and_sals AS
(
SELECT e.ename, e.sal, sg.grade
FROM emp e
JOIN salgrade sg ON e.sal BETWEEN sg.losal AND sg.hisal
)
SELECT *
FROM emps_and_sals
WHERE grade = (SELECT grade FROM emps_and_sals WHERE ename = 'BLAKE')
ORDER BY sal DESC;