How to show unused foreign key value when joining tables - sql

I have 2 tables, emp, and dept(employee and department).
Say I want to show the sum of all salaries per department, I could use something like:
select sum(sal), deptno
from emp
group by deptno
Now that works, but say there is a department in the list that has no employees, and I want it to show up also, how would I do that? I've tried joins, the nvl function, but no luck this far. For example, this works, but it won't show the empty departments:
select sum(emp.sal), dept.deptno
from emp, dept
where emp.deptno=dept.deptno
group by dept.deptno
Thanks in advance for any help!

LEFT JOIN will do the trick:
select coalesce(sum(emp.sal),0), dept.deptno
from dept
left join emp on emp.deptno=dept.deptno
group by dept.deptno
You should always explicitly declare your joins, so that you can change them when you need to. Implicit joins are always INNER JOIN.
Additionally, I change the query to show 0 instead of NULL when department has no employees, by using COALESCE.

You need to use an outer join
SELECT
dept.deptno,
SUM(emp.sal)
FROM
dept
LEFT OUTER JOIN emp ON dept.deptno = emp.deptno
GROUP BY
dept.deptno

You want to use a LEFT JOIN so that you return all departments, regardless of whether or not they have employees.
SELECT dept.deptno, SUM(emp.sal)
FROM dept
LEFT JOIN emp
ON dept.deptno = emp.deptno
GROUP BY dept.deptno

Try this:
SELECT ISNULL(SUM(e.sal),0) AS SalSum,
d.DeptNo
FROM Dept AS d
LEFT JOIN emp AS e ON e.DeptNo = d.DeptNo
GROUP BY d.DeptNo

Use an outer join:
select
sum(emp.sal), dept.deptno
from dept LEFT OUTER JOIN emp
ON dept.deptno=emp.deptno
group by dept.deptno

Another alternative approach, justification being that it uses relational operators (union and difference) making it simple to read and understand, IMO:
select sum(sal), deptno
from emp
group by deptno
union
select 0, d.deptno
from dept d
minus
select 0, e.deptno
from emp e;

select sum(sal), deptno
from emp
group by deptno
union all
select 0, deptno
from dept
where deptno not in (select deptno from emp)
or
select sum(sal), deptno
from emp
group by deptno
union all
select 0, d.deptno
from dept d
where not exists
(
select *
from emp e
where e.deptno = d.deptno
)

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 count the number of members of different departments after executing a JOIN between two tables?

I have a table Emp and another table Dept
This is table Emp
This is table Dept
The query I have to execute is to display the average salary (i.e., sal) for all departments (i.e. DeptName) with more than 5 working people. So we'll have to do a JOIN or something and match the DeptID here with the DeptID there etc.
This is really confusing and I don't understand how to go about it.
you can use group by with having clause query for that.
group by Dept_id and check count using having clause.
SELECT
SUM(E.Sal) / COUNT(E.Dept_id) AS Avg_Sal,
E.Dept_Id
FROM EMP E
JOIN Dept D
ON D.DeptId = E.Dept_Id
GROUP BY E.DeptId
HAVING COUNT(E.Dept_Id) > 5
Select
DeptName,
AVG(sal) as averageSalary,
Count(EmpId) as NumberofEE
FROM Emp e
/*Left Join Because there are Nulls in the Dept_Id each Emp should belong to
1 dept*/
LEFT JOIN Dept d on e.Dept_Id = d.DeptId
GROUP BY
DeptName
Having Count(EmpId)> 5
This will give you the average salary (i.e., sal) for all departments (i.e. DeptName) with more than 5 working people
If i'm right with your requirement.

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

the grouping column for an aggregate function is as a join condition?

I was reading some Oracle SQL resources and I found this SQL code:
SELECT e.ename AS "NAME",
e.sal AS "Salary",
e.deptno,
AVG(a.sal) dept_avg
FROM emp e, emp a
WHERE e.deptno = a.deptno
AND e.sal > ( SELECT AVG(sal)
FROM emp
WHERE deptno = e.deptno )
GROUP BY e.ename, e.sal, e.deptno;
This SQL code is supposed to return every employee that gets more than the average salary of his department and display his name, his salary his department's ID and then the average salary in his department.
In order to return the dept_avg, we have to group by deptno, but the grouping columns are weird. What I guess, is that the grouping column is the column that is used as a join condition, the a.deptno. Is that true ? if not can someone please clarify it?
Maybe re-writing with more modern conventions makes it clearer?
WITH avgbydept as
(
SELECT deptno, avg(sal) as avgsal
FROM emp
GROUP BY deptno
)
SELECT e.ename AS "NAME",
e.sal AS "Salary",
e.deptno,
AVG(a.sal) dept_avg
FROM emp e
JOIN emp a ON e.deptno = a.deptno
JOIN avgbydept abd ON e.deptno = abd.deptno
WHERE e.sal > abd.avgsal
GROUP BY e.ename, e.sal, e.deptno;
One thing this makes clear is that it has a "bug" of an extra join and group by -- To do as you say:
This SQL code is supposed to return every employee that gets more than
the average salary of his department and display his name, his salary
his department's ID and then the average salary in his department.
I believe you want this
WITH avgbydept as
(
SELECT deptno, avg(sal) as avgsal
FROM emp
GROUP BY deptno
)
SELECT e.ename AS "NAME",
e.sal AS "Salary",
e.deptno,
abd.avgsal as dept_avg
FROM emp e
JOIN avgbydept abd ON e.deptno = abd.deptno
WHERE e.sal > abd.avgsal
If you remove GROUP BY and use SELECT *, you'll see what's happening.
emp is joined on itself, every employee with salary higher than average is joined with every other employee in his department, making an awful lot of rows. Then, from that data, average salary (from every other worker in dept) is counted again, using GROUP BY. It's impressively inefficient, look at other answers to see how it should have been done.
GROUP BY can throw us for a loop. Here's an easy way to think about grouping:
select field1, field1, sum(field3)
from ..
group by <all fields that do not participate in aggregate>
The query you noticed could be re-written somewhat like this:
select e.*, t.avgsal
from emp e
inner join (select deptno, avg(sal) avgsal from emp group by deptno) t
on e.deptno = t.deptno
where e.sal > t.avgsal
Now you can see that the subquery aliased with t will get average salary by department. We then use departments to join employee and our derived avg salary by department and eliminate the need for grouping.