Find departments with no employees - sql

We were given an assignment in class to provide at the minimum two different solutions to find departments, which don't have any employees. As you can see from below I completed the task successfully.
An additional solution is extra credit, which I like to get. Unfortunately, I can't think of a third solution and was hoping someone can point me in the right direction. Would something with a MINUS function work?
Below is my setup and 2 working test cases. Any suggestions and help would be greatly appreciated.
CREATE TABLE departments
(
department_id,
department_name
) AS
SELECT 1, 'IT' FROM DUAL UNION ALL
SELECT 3, 'Sales' FROM DUAL UNION ALL
SELECT 2, 'DBA' FROM DUAL;
CREATE TABLE employees
(
employee_id,
first_name, last_name,
hire_date,
salary,
department_id
) AS
SELECT 1, 'Lisa', 'Saladino', DATE '2001-04-03', 100000, 1 FROM DUAL UNION ALL
SELECT 2, 'Abby', 'Abbott', DATE '2001-04-04', 50000, 1 FROM DUAL UNION ALL
SELECT 3, 'Beth', 'Cooper', DATE '2001-04-05', 60000, 1 FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Orr', DATE '2001-04-06', 70000,1 FROM DUAL UNION ALL
SELECT 5, 'Vicky', 'Palazzo', DATE '2001-04-07', 88000,2 FROM DUAL UNION ALL
SELECT 6, 'Cheryl', 'Ford', DATE '2001-04-08', 110000,1 FROM DUAL UNION ALL
SELECT 7, 'Leslee', 'Altman', DATE '2001-04-10', 66666, 1 FROM DUAL UNION ALL
SELECT 8, 'Jill', 'Coralnick', DATE '2001-04-11', 190000, 2 FROM DUAL UNION ALL
SELECT 9, 'Faith', 'Aaron', DATE '2001-04-17', 122000,2 FROM DUAL;
/* departments with no employees */
select
d.department_id,
d.department_name
from
employees e
right join
departments d on e.department_id = d.department_id
group by
d.department_id, d.department_name
having
count(e.employee_id) = 0;
Output:
DEPARTMENT_ID DEPARTMENT_NAME
--------------------------------
3 Sales
SELECT
d.department_id,
d.department_name
FROM
departments d
WHERE
NOT EXISTS (SELECT * FROM employees e
WHERE d.department_id = e.department_id)
Output:
DEPARTMENT_ID DEPARTMENT_NAME
--------------------------------
3 Sales

You may also use left join as the following:
select
d.department_id,
d.department_name
from departments d left join employees e
on e.department_id=d.department_id
where e.employee_id is null
Also, you may use a sub query as the following:
select department_id, department_name
from departments
where department_id not in (select department_id from employees)
And with minus you may try:
select department_id, department_name
from departments
minus
select
d.department_id,
d.department_name
from departments d join employees e
on e.department_id=d.department_id
See a demo.

You can try below query-
SELECT *
FROM departments
WHERE department_id in (SELECT department_id
FROM departments
minus
SELECT DISTINCT department_id
FROM employees
);
Demo.

Related

Analytic functions and plain SQL equivalent

I'm using Oracle and SQL Developer. I have downloaded HR schema and need to do some queries with it. Now I'm working with table Employees. As an user I need to see the list of employees with lowest salary in each department. I need to provide different solutions by means of plain SQL and one of analytic functions. About analytic functions, I have used RANK():
SELECT *
FROM
(SELECT
employee_id,
first_name,
department_id,
salary,
RANK() OVER (PARTITION BY department_id
ORDER BY salary) result
FROM
employees)
WHERE
result = 1
AND department_id IS NOT NULL;
The result seems correct:
but when I try to use plain SQL I actually get all employees with their salaries.
Here is my attempt with GROUP BY:
SELECT
department_id, MIN(salary) AS "Lowest salary"
FROM
employees
GROUP BY
department_id;
This code seems good, but I need to also get columns first_name and employee_id.
I tried to do something like this:
SELECT
employee_id,
first_name,
department_id,
MIN(salary) result
FROM
employees
GROUP BY
employee_id,
first_name,
department_id;
and this:
SELECT
employee_id,
first_name,
salary,
departments.department_id
FROM
employees
LEFT OUTER JOIN
departments ON (employees.department_id = departments.department_id)
WHERE
employees.salary = (SELECT MIN(salary)
FROM departments
WHERE department_id = employees.department_id)
These seem wrong. How can I change or modify my queries to get the same result as when I'm using RANK() by means of plain SQL (two solutions at least)?
One of the options could be like here (with old EMP table)...
SELECT EMPNO, ENAME, DEPTNO, SAL
FROM EMP e
WHERE SAL = (Select MIN_SAL From (SELECT DEPTNO, Min(SAL) "MIN_SAL"
FROM EMP
GROUP BY DEPTNO)
Where DEPTNO = e.DEPTNO)
ORDER BY DEPTNO, SAL;
Second option could be...
SELECT EMPNO, ENAME, DEPTNO, SAL
FROM (SELECT e.EMPNO, e.ENAME, e.DEPTNO, e. SAL, (Select Min(SAL) "MIN_SAL" From EMP Where DEPTNO = e.DEPTNO) "MIN_SAL" From EMP e)
WHERE SAL = MIN_SAL
ORDER BY DEPTNO, SAL;
Regards...
You can use a subquery to find the lowest salary per employee and use the main query to only show the information of those employees that are selected by this subquery:
SELECT
employee_id,
first_name,
department_id,
salary
FROM employees e1
WHERE salary =
(SELECT MIN(e2.salary)
FROM employees e2
WHERE e1.employee_id = e2.employee_id);
This will produce exactly the same outcome as your query with RANK.
I think it would make sense to apply some sorting which is missing in your query. I don't know how you want to sort, but here an example to sort by the employee's name:
SELECT
employee_id,
first_name,
department_id,
salary
FROM employees e1
WHERE salary =
(SELECT MIN(e2.salary)
FROM employees e2
WHERE e1.employee_id = e2.employee_id)
ORDER BY first_name;
Since you asked for at least two solutions, let's have a look on another option:
SELECT
e1.employee_id,
e1.first_name,
e1.department_id,
e1.salary
FROM employees e1
JOIN (
SELECT employee_id, MIN(salary) salary
FROM employees
GROUP BY employee_id ) e2
ON e1.employee_id = e2.employee_id AND e1.salary = e2.salary
ORDER BY first_name;
As you can see, this differs since the sub query will apply a GROUP BY clause and it can be successfully executed as own query which is not possible for the sub query used in the previous query.
The JOIN to the main query will then make sure to get again the desired result.
Here are some options to get the employees with the minimum salary in their department:
With MIN (salary) OVER (...)
select employee_id, first_name, department_id, salary
from
(
select e.*, min(salary) over (partition by department_id) as min_sal
from employees e
)
where sal = min_sal;
With RANK and FETCH FIRST
select *
from employees
order by rank() over (partition by department_id order by salary)
fetch first row with ties;
With IN
select *
from employees
where (department_id, salary) in
(
select department_id, min(salary)
from employees
group by department_id
);
With NOT EXISTS
select *
from employees e
where not exists
(
select null
from employees other
where other.department_id = e.department_id
and other.salary < e.salary
);
If you will only ever have one person with the minimum salary per department then you can use KEEP:
SELECT department_id,
MIN(employee_id) KEEP (DENSE_RANK FIRST ORDER BY salary) AS employee_id,
MIN(first_name) KEEP (DENSE_RANK FIRST ORDER BY salary, employee_id) AS first_name,
MIN(salary) AS min_salary
FROM employees
GROUP BY department_id
Which, for the sample data:
CREATE TABLE employees (employee_id, department_id, first_name, salary) AS
SELECT 1, 1, 'Alice', 1000 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', 2000 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', 3000 FROM DUAL UNION ALL
SELECT 4, 2, 'Debra', 3000 FROM DUAL UNION ALL
SELECT 5, 2, 'Emily', 4000 FROM DUAL;
Outputs:
DEPARTMENT_ID
EMPLOYEE_ID
FIRST_NAME
MIN_SALARY
1
1
Alice
1000
2
3
Carol
3000
Note: this will not match Debra, even though she also has the lowest salary in department 2, as it will only find a single employee with the minimum salary and the minimum employee id.
If you can have multiple employees with the same minimum-per-department then you can use a correlated sub-query:
SELECT department_id,
employee_id,
first_name,
salary
FROM employees e
WHERE EXISTS(
SELECT 1
FROM employees x
WHERE e.department_id = x.department_id
HAVING MIN(x.salary) = e.salary
);
Which, for the sample data, outputs:
DEPARTMENT_ID
EMPLOYEE_ID
FIRST_NAME
SALARY
1
1
Alice
1000
2
3
Carol
3000
2
4
Debra
3000
Which does return Debra.
fiddle

How to handle ties in SQL

SELECT
employee_id,
department_id,
first_name,
last_name,
hire_date,
job_id
FROM employees e
WHERE hire_date IN
(
SELECT max(hire_date)
FROM employees
WHERE e.department_id = department_id
)
ORDER BY hire_date ASC
Result of the query
So this is my query and the result of it. There are two tied results for SA_REP department and I should have only one result - for instance the one with the lower employee_id. I've googled the problem but can't seem to find any related results...
Thanks for any help!
You can use a not exists query which gives you more flexibility:
SELECT *
FROM employees e
WHERE NOT EXISTS ( -- no x exists that...
SELECT *
FROM employees x
WHERE x.department_id = e.department_id -- belongs to same department
AND (
x.hire_date > e.hire_date OR -- but hired later than e
x.hire_date = e.hire_date AND x.employee_id < e.employee_id -- hired on same date but has lesser employee id than e
)
)
You may use the RANK analytic function here:
WITH cte AS (
SELECT e.*, RANK() OVER (PARTITION BY department_id
ORDER BY hire_date DESC, employee_id) rnk
FROM employees e
)
SELECT employee_id, department_id, first_name, last_name, hire_date, job_id
FROM cte
WHERE rnk = 1;

SQL: Trying to find total of one column and who has the highest value in that column

Link of tables:
https://docs.google.com/document/d/1df4zUTI6e5Rw8mJxZKkOWLBuRYzjAOPkkf8zsC_2mRo/edit?usp=sharing
I'm trying to write a query that displays the total (sum) of all the salaries in the salary column and the name of the individual who has the highest salary.
I used:
select name as highest_paid,sum(salary) as total_salary
from uscis,employer
where uscis.alienno=employer.alienno and salary=(select max(salary) from employer) group by name
I did get the result of name of the highest salary paid but i did not get the sum of the salary columns. I instead got 280,000 which is the highest salary.
My apologies in advance if I worded this question poorly.
So you do not want just the one persons salary? You want to sum the whole salary column from the Employer table? Assuming a 1:1 relationship between USCIS table and Employer, try this.
SELECT name as highest_paid, sum(salary) OVER() as everybodys_total_salary
FROM uscis
INNER JOIN employer
on uscis.alienno=employer.alienno
ORDER BY salary DESC
FETCH FIRST ROW ONLY
Description of oracle sum as an analytic function found here https://oracle-base.com/articles/misc/sum-analytic-function
Want to get the sum(salary) and max(salary) together?
with total as (
select sum(salary) as sum_salary from employer
), max as (
select a.name, b.salary from uscis a, employer b where a.alienno = b.alienno and b.salary = (select max(salary) from employer)
)
select * from max, total;
look at the dbfiddle
Use SUM as an analytic function and the ORDER BY the salary in DESCending order and get only the first row:
SELECT name as highest_paid,
SUM(salary) OVER () as total_salary
FROM uscis
INNER JOIN employer
ON ( uscis.alienno=employer.alienno )
ORDER BY salary DESC
FETCH FIRST ROW WITH TIES;
-- or FETCH FIRST ROW ONLY if you only ever want one row.
So, for your test data:
CREATE TABLE uscis ( AlienNo, Name, Nationality, City ) AS
SELECT 'A1023', 'Jeff', 'India', 'Atlanta' FROM DUAL UNION ALL
SELECT 'A1024', 'David', 'China', 'NY' FROM DUAL UNION ALL
SELECT 'A1025', 'Mark', 'UK', 'Charlotte' FROM DUAL UNION ALL
SELECT 'A2050', 'Shown', 'Germany', 'Astoria' FROM DUAL;
CREATE TABLE employer ( AlienNo, SSN, Dept, Salary ) AS
SELECT 'A1023', '111-22-4567', 'EE', 280000 FROM DUAL UNION ALL
SELECT 'A1024', '333-32-8767', 'CS', 180000 FROM DUAL UNION ALL
SELECT 'A1025', '444-45-3454', 'CE', 140000 FROM DUAL UNION ALL
SELECT 'A2050', '234-34-2234', 'ME', 180.000 FROM DUAL;
This outputs:
HIGHEST_PAID | TOTAL_SALARY
:----------- | -----------:
Jeff | 600180
db<>fiddle here

using join and group by...having giving error

SELECT d.DEPARTMENT_NAME
FROM Department d,
Student s
WHERE d.DEPARTMENT_ID = s.DEPARTMENT_ID
GROUP BY d.DEPARTMENT_NAME
HAVING COUNT(s.STUDENT_ID) < MAX(COUNT(s.STUDENT_ID));
The code is for joining student and department table. The department_id is key from department to student. Need to find departments with not the max number of students. Error is group function nested too deeply. Isn't nesting allowed up to 3?
Here is the exact error
ORA-00935: group function is nested too deeply
upd. Oh, shi~! I just mentioned the question's related to Oracle. I don't know its syntax for rank() function, but I think it should be really close to Sql Server.
Here it is:
;with Department(DEPARTMENT_ID, DEPARTMENT_NAME) as (
select 1, 'first' union all
select 2, 'second' union all
select 3, 'third'
)
, Student(STUDENT_ID, DEPARTMENT_ID) as (
select 1, 1 union all
select 2, 2 union all
select 3, 2 union all
select 4, 2 union all
select 5, 3 union all
select 6, 3 union all
select 7, 3
)
, DepOrdered as (
select
d.DEPARTMENT_ID,
d.DEPARTMENT_NAME,
s.StudentCnt,
-- rank departments by the number of students
rank() over (order by s.StudentCnt desc) as Rnk
from Department d
cross apply (
-- for every department count its students
select
count(s.STUDENT_ID) StudentCnt
from Student s
where
d.DEPARTMENT_ID = s.DEPARTMENT_ID
) s
)
select
DEPARTMENT_ID,
DEPARTMENT_NAME,
StudentCnt
from DepOrdered
where
-- Rnk = 1 would have all departments with max number of students
Rnk > 1

How to Join Tables with UNION without displaying null when no matched UNION

how do i join two tables to show the department_id, department_name and last_name. It has to be with UNION.
I only found this solution (below) but that will show the department_name with the last_name as NULL and the last_name with the department_name as null. Is there any method to show everything without Null?
SELECT department_id, department_name, TO_CHAR(NULL)
FROM departments
WHERE department_id IN (10,20)
UNION ALL
SELECT department_id, TO_CHAR(NULL), last_name
FROM employees
WHERE department_id IN (10,20)
Below is the code which displays what i want to display with UNION:
SELECT d.department_id, d.department_name, l.last_name
FROM departments d JOIN employees l
ON d.department_id = l.department_id
WHERE d.department_id IN (10,20)
Thank you in advance :)
It doesn't make much sense, to do it this way, but so does the whole task.
select *
from(
select department_id, max(department_name) over (partition by deptno), last_name
from(
SELECT department_id, department_name, TO_CHAR(NULL) last_name
FROM DEPT
WHERE department_id IN (10,20)
UNION ALL
SELECT department_id, TO_CHAR(NULL), last_name
FROM emp
WHERE department_id IN (10,20)))
where last_name is not null
Just add a where clause with COALESCE to get NOT NULL in an outer query.
SELECT * FROM (<your union query>) WHERE COALESCE(column1, column2, column3) IS NOT NULL
You could use CTE for your inner query, i.e. your union query. And then use my suggestion to select NOT NULL rows.