small tricky query on self join - sql

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;

Related

Select the manager name with the most employees

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.

I need to display all employees which were hired before their managers. Can somebody help me?

I need to run an SQL query which will display all the employees who were hired before their managers and they have the minimum salary for his function.
SELECT ENAME,JOB, SALAR FROM EMP
WHERE JOB = 'SALESMAN'
OR JOB = 'CLERK'
OR JOB = 'ANALYST'
Use window functions:
select . . .
from (select e.*, m.hiredate as mgr_hiredate,
min(salar) over (partition by job) as min_salar
from emp e left join
emp em
on e.mgr = m.empno
) em
where hiredate < mgr_hiredate and salar = min_salar;
You can join EMP to itself to add details about each employee's manager (we're interested in his HIREDATE). Later in WHERE section you can check that manager's HIREDATE is later than corresponding employee's.
You can also pre-calculate all minimal salaries for each job, then join this information to each employee and later you can check that employee's salary is equal to the minimal.
SELECT EMPNO, ENAME
FROM EMP e
INNER JOIN EMP m ON e.MGR = m.EMPNO
INNER JOIN (
SELECT JOB, MIN(SALAR) as MINSALAR
FROM EMP
GROUP BY JOB
) s ON s.JOB = e.JOB
WHERE m.HIREDATE > e.HIREDATE AND e.SALAR = s.MINSALAR

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;

how to navigate in self loop tables?

Consider the following table
create table EMPLOYEE
(
empno NUMBER not null,
ename VARCHAR2(100),
salary NUMBER,
hiredate DATE,
manager NUMBER
);
alter table EMPLOYEE add constraint PK_EMP primary key (EMPNO);
alter table EMPLOYEE
add constraint FK_MGR foreign key (MANAGER)
references EMPLOYEE (EMPNO);
which is a self looped table i.e. every employee has a manager, except for the root.
I want to run the following query on this table:
find all the employees having more salary than their managers?
P.S.
There is only one root in the structure
consider the following query
SELECT LPAD(emp.ename, (LEVEL-1)*5 + LENGTH(emp.ename), ' ') AS "Hierarchy"
FROM employee emp
START WITH emp.manager IS NULL
CONNECT BY manager = PRIOR empno;
the result would be something like this:
Alice
Alex
Abbey
Sarah
Jack
Bill
Jacob
Valencia
Bob
Babak
...
I made the following query
SELECT LPAD(emp.ename, (LEVEL-1)*5 + LENGTH(emp.ename), ' ') AS "Hierarchy"
FROM employee emp
START WITH empno IN (SELECT empno FROM employee)
CONNECT BY PRIOR manager = empno;
which makes a subtree for every employee in the employee table from bottom to top, but I don't know how to navigate through to get to the desired result!
Here is one way to do it
with fullemployee (empno, ename, salary, key)
as
(
select A.empno, A.ename, A.salary, A.empno || '.' from
employee A
where A.manager is null
union all
select C.empno, C.ename, C.salary, D.key || '.' || C.empno from
employee C
inner join fullemployee D on C.manager = D.empno
)
select E.ename, F.ename as manager from fullemployee E
inner join fullemployee F on E.key like F.key || '%' and E.key <> F.key
where E.salary > F.salary
or equivalently
with fullemployee (empno, ename, salary, key)
as
(
SELECT empno, ename, salary, SYS_CONNECT_BY_PATH(empno, '.') || '.'
FROM employee
START WITH manager is null
CONNECT BY PRIOR empno = manager
)
select E.ename, F.ename as manager from fullemployee E
inner join fullemployee F on E.key like F.key || '%' and E.key <> F.key
where E.salary > F.salary
SQL Fiddle - http://sqlfiddle.com/#!4/37f4ae/35
This should do the work. Remove that or condition if you don't want the 'root' in your list.
select e.empno, e.ename, e.salary from employee e
inner join employee mgr on mgr.empno = e.manager
where e.salary > mgr.salary
or (e.manager = mgr.empno)