Rewriting uncorrelated subquery to correlated subquery - sql

I am working with a default oracle scott database with additional table PROJECT, where there are two columns: projectno and empno.
I want to select names of employees with the highest salaries for each project.
I know how to do it with uncorrelated subquery:
SELECT p.projno,
e.sal,
e.ename
FROM emp e
INNER
JOIN proj_emp p
ON e.empno = p.empno
WHERE (e.sal, p.projno)
IN (SELECT MAX(e.sal),
p.projno
FROM emp e INNER JOIN proj_emp p
ON e.empno = p.empno
GROUP BY p.projno)
However, i was asked to do it with a correlated subquery written in a WHERE clause, but i am wondering if it is possible?

I would do :
SELECT t.*
FROM (SELECT p.projno, e.sal, e.ename,
DENSE_RANK() OVER (PARTITION BY p.projno ORDER BY e.sal DESC) AS Seq
FROM emp e INNER JOIN
proj_emp p
ON e.empno = p.empno
) t
WHERE Seq = 1;
EDIT : If you want to do it with correlated subquery then i would rewrite your query to make correlated :
SELECT p.projno, e.sal, e.ename
FROM emp e INNER JOIN
proj_emp p
ON e.empno = p.empno
WHERE e.sal = (SELECT MAX(e1.sal)
FROM emp e1 INNER JOIN
proj_emp p1
ON e1.empno = p1.empno
WHERE p1.projno = p.projno
);

Use window functions:
SELECT projno, sal, ename
FROM (SELECT p.projno, e.sal, e.ename,
MAX(e.sal) OVER (PARTITION BY p.projno) as max_sal
FROM emp e INNER JOIN
proj_emp p
ON e.empno = p.empno
) ps
WHERE sal = max_sal;

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.

How to get result if exactly one match inner join

How can I write a query to join two tables and return result if exactly one match in there. I have to discard results if zero match and more than one match.
All I am looking for is to extend the INNER JOIN. Let me just get to the point. I have two tables Dept & Emp. One Dept can have multiple Emp's & not the other way around.
Table Dept
Table Emp
I need to JOIN it on Dept_id
Expected Results
You can join with a not exists condition:
select d.*, e.emp_id, e.emp_name
from dept d
inner join emp e
on d.dept_id = e.dept_id
and not exists (
select 1
from emp e1
where e1.dept_id = d.dept_id and e1.emp_id != e.emp_id
)
One alternative to existing solutions can be one using analytics (window functions),
instead of joining twice:
select dept_id, dept_name, emp_id, emp_name
from
(
SELECT
d.Dept_id, d.Dept_name, e.Emp_id, e.Emp_Name,
count(*) over (partition by d.dept_id) cnt1
FROM d
INNER JOIN e
ON d.Dept_id = e.Dept_id
) where cnt = 1;
You could use a subquery for group by dept_id haing count = 1
select t.dept_id, dept.dept_name, emp.Emp_name
from (
select dept_id
from emp
group by dept_id
having count(*) = 1
) t
INNER JOIN dept on t.dept_id = dept.dept_id
INNER JOIN emp ON t.dept_id = emp.dept_id
You can phrase this as an aggregation query in Oracle:
select d.dept_id, d.dept_name,
max(e.emp_id) as emp_id,
max(e.emp_name) as emp_name
from dept d inner join
emp e
using (dept_id)
group by d.dept_id, d.dept_name
having count(*) = 1;
This works because if there is only one match, then max() returns the value from the one row.
Also, try below query;
SELECT a.depid dept_id,dept_name,emp_id,emp_name
FROM
(SELECT case WHEN count(*)=1 THEN dept_id END depid FROM emp GROUP BY dept_id) a INNER JOIN emp ON depid=dept_id
INNER JOIN dept b ON a.depid = b.dept_id
WHERE depid IS NOT NULL
Another way would be
select d.dept_id, d.dept_name, e.emp_name
from emp e
join dept d on d.dept_id = e.dept_id
where e.dept_id in
( select dept_id from emp group by dept_id having count(*) = 1 )

Need to retrieve columns from 3 tables

There are three tables: dept, emp, sal . You can find their structure and data in the images.
I need to extract the list of employees who have location as pune and have max salary in their department. Since there are five departments, the final output will contain five rows and columns of emp_id, dept, dept_id, salary.
I've tried...
select e.emp_id, dept,e.dept_id, max(sal) as 'highest salary'
from sal s,emp e,dept d
where e.emp_id = s.emp_id and d.dept_id = e.dept_id and loc ='Pune'
group by e.emp_id,e.dept_id,dept
order by e.dept_id
I would use apply :
select t.emp_id, d.dept, d.dept_id, t.sal
from dept d
cross apply ( select top 1 e.emp_id, s.sal as sal
from emp e
inner join sal s on s.emp_id = e.emp_id
where d.dept_id = e.dept_id and e.loc = 'Pune'
order by s.sal desc
) t;
Unless you have window functions (that depends on whether you're using MySQL, Oracle, SQLite, etc) you will need to do it in two steps.
Find the highest salary per department (for employees in 'Pune'), then another set of joins to find out who those people are.
SELECT
dep.dept,
dep.dept_id,
emp.emp_id,
sal.sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
INNER JOIN
(
SELECT
emp.dept_id,
MAX(sal.sal) AS max_sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
WHERE
emp.loc = 'Pune'
)
dep_sal
ON dep_sal.dept_id = emp.dept_id
AND dep_sal.max_sal = sal.sal
INNER JOIN
dep
ON dep.dept_id = emp.dept_id
WHERE
emp.loc = 'Pune'
ORDER BY
dep.dept,
emp.emp_id
EDIT: With SQL Server 2008 on-wards it's a bit easier...
WITH
emp_sal_ranked AS
(
SELECT
emp.dept_id,
emp.emp_id,
sal.sal,
RANK(sal.sal) OVER (PARTITION BY emp.dept_id
ORDER BY sal.sal
)
AS rank_sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
WHERE
emp.loc = 'Pune'
)
SELECT
dep.dept,
dep.dept_id,
emp_sal_ranked.emp_id,
emp_sal_ranked.sal
FROM
emp_sal_ranked
INNER JOIN
dept
ON dept.dept_id = emp_sal_ranked.dept_id
WHERE
emp_sal_ranked.rank_sal = 1
ORDER BY
dep.dept,
emp_sal_ranked.emp_id

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;

sql programming joining of four tables

select e.empno, e.ename, e.sal, d.deptno, d.dname, l.loc_id, l.loc_name, a.add1, a.add2, a.pin from emp e, address a, location l, dept d on inner join emp,deptno and on inner join emp,address and inner join dept,location having a.add2=rp and l.loc_name=kolkata;
select e.empno, e.ename, e.sal, d.deptno, d.dname, l.loc_id, l.loc_name, a.add1, a.add2, a.pin
from emp e
inner join dept d on d.deptno=e.deptno
inner joint location l on l.loc_id=d.loc_id
inner join address a on a.adress_id=e.address_id
where a.add2 = 'rp' and l.loc_name='kolkata';
Did you meant somethig like this?