Puzzle of the day.
This is a fairly simple problem but from time to time I am being approached by developers who need help with very similar problems.
Find all employees who has a unique job title in their respective department.
Solution #1: Using NOT EXISTS
SELECT ename, deptno, job, sal
FROM emp a
WHERE NOT EXISTS(SELECT 1
FROM emp b
WHERE a.deptno=b.deptno
AND a.job=b.job
AND a.empno!=b.empno)
ORDER BY deptno, job
Result:
ENAME DEPTNO JOB SAL
---------- ---------- --------- ----------
MILLER 10 CLERK 1300
CLARK 10 MANAGER 2450
KING 10 PRESIDENT 5000
JONES 20 MANAGER 2975
JAMES 30 CLERK 950
BLAKE 30 MANAGER 2850
Solution #1.1 – Generic substitution for NOT EXISTS
SELECT ename, deptno, job, sal
FROM emp a
WHERE 0=(SELECT COUNT(b.empno)
FROM emp b
WHERE a.deptno=b.deptno
AND a.job=b.job
AND a.empno!=b.empno)
ORDER BY deptno, job
Solution #2: Using NOT IN
SELECT ename, deptno, job, sal
FROM emp a
WHERE job NOT IN(SELECT job
FROM emp b
WHERE a.deptno=b.deptno
AND a.empno!=b.empno)
ORDER BY deptno, job
Result:
ENAME DEPTNO JOB SAL
---------- ---------- --------- ----------
MILLER 10 CLERK 1300
CLARK 10 MANAGER 2450
KING 10 PRESIDENT 5000
JONES 20 MANAGER 2975
JAMES 30 CLERK 950
BLAKE 30 MANAGER 2850
Solution #2.1: Using NOT IN with Multi-column subquery – it is pretty much the same as Solution #2.
SELECT ename, deptno, job, sal
FROM emp a
WHERE (job, deptno) NOT IN(SELECT job, deptno
FROM emp b
WHERE a.empno!=b.empno)
ORDER BY deptno, job
Solution #3.1: Using COUNT in subquery (very similar to Solution #1.1 but has different execution plan)
SELECT ename, deptno, job, sal
FROM emp a
WHERE 1=(SELECT COUNT(b.empno)
FROM emp b
WHERE a.deptno=b.deptno
AND a.job=b.job)
ORDER BY deptno, job
Result:
ENAME DEPTNO JOB SAL
---------- ---------- --------- ----------
MILLER 10 CLERK 1300
CLARK 10 MANAGER 2450
KING 10 PRESIDENT 5000
JONES 20 MANAGER 2975
JAMES 30 CLERK 950
BLAKE 30 MANAGER 2850
Solution #3.2: A mixed version of Solutions #1.1 and #3.1:
SELECT ename, deptno, job, sal
FROM emp a
WHERE 0=(SELECT SUM(CASE WHEN a.empno=b.empno THEN 0 ELSE 1 END)
FROM emp b
WHERE a.deptno=b.deptno
AND a.job=b.job)
ORDER BY deptno, job
Solution #4: Using Analytical function COUNT
WITH x AS (
SELECT ename, deptno, job, sal, COUNT(*) OVER(PARTITION BY deptno, job) cnt
FROM emp a
)
SELECT ename, deptno, job, sal
FROM x
WHERE cnt=1
ORDER BY deptno, job
Result:
ENAME DEPTNO JOB SAL
---------- ---------- --------- ----------
MILLER 10 CLERK 1300
CLARK 10 MANAGER 2450
KING 10 PRESIDENT 5000
JONES 20 MANAGER 2975
JAMES 30 CLERK 950
BLAKE 30 MANAGER 2850
Solution #4.1 – using MIN/MAX analytical functions – essentially, it is the same as solution #4
WITH x AS (
SELECT ename, deptno, job, sal,
MAX(empno) OVER(PARTITION BY deptno, job) max_no,
MIN(empno) OVER(PARTITION BY deptno, job) min_no
FROM emp a
)
SELECT ename, deptno, job, sal
FROM x
WHERE max_no=min_no
ORDER BY deptno, job
Solution #5: Using In-Line view (WITH)
WITH x AS (
SELECT deptno, job
FROM emp
GROUP BY deptno, job
HAVING COUNT(*)=1
)
SELECT ename, deptno, job, sal
FROM emp JOIN x USING (deptno, job)
ORDER BY deptno, job
Result:
ENAME DEPTNO JOB SAL
---------- ---------- --------- ----------
MILLER 10 CLERK 1300
CLARK 10 MANAGER 2450
KING 10 PRESIDENT 5000
JONES 20 MANAGER 2975
JAMES 30 CLERK 950
BLAKE 30 MANAGER 2850
For more tricks and cool techniques check my book “Oracle SQL Tricks and Workarounds” for instructions.