Select EVERY department and number of employees with salary higher than 10000 - sql

I need to select every department name and number of employees earning 10000 or more. That's my code.
SELECT d.department_name, COUNT(e.employee_id)
FROM hr.employees e
JOIN hr.departments d ON d.department_id=e.department_id WHERE e.salary > 10000
GROUP BY d.department_name;
It shows only departments which contains employees earning 10000 but I also need rest of departments with number 0 or - in column COUNT(e.employee_id). Can you help me?

You can do conditional aggregation. The idea is to not put the condition on employee salary in the join, but instead it in a conditional sum:
select
d.department_name,
sum(case when e.salary > 10000 then 1 else 0 end) cnt_high_salaries
from hr.department d
inner join hr.employee e on d.department_id = e.department_id
group by d.department_id, d.department_name

I think you just need outer join in your original query.
SELECT d.department_name,
COUNT(e.employee_id)
FROM hr.departments d
LEFT JOIN hr.employees e
ON d.department_id=e.department_id
and e.salary > 10000
GROUP BY d.department_name;

Is this what you are looking for?
SELECT d.department_name, COUNT(e.employee_id) FROM hr.employees e
JOIN hr.departments d ON d.department_id=e.department_id WHERE d.department_id in
(SELECT distinct e.department_id from employees e WHERE e.salary > 10000)
GROUP BY d.department_name;

Related

join operation in SQL

There is such a task: By joining the tables HR.DEPARTMENTS and HR.EMPLOYEES, display complete data on departments in which the minimum salary is below 5000.
I tried to do this, but it gives an error
select distinct d.department_id,department_name,
d.manager_id, location_id
from hr.departments d
left join hr.employees e on e.department_id = d.department_id
where min(e.salary) < 5000
order by 1
Error: group function is not allowed here
This is what hr.employees looks like:
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
100 Steven King SKING 515.123.4567 17-JUN-03 AD_PRES 24000 - - 90
hr.departments:
DEPARTMENT_ID DEPARTMENT_NAME MANAGER_ID LOCATION_ID
10 Administration 200 1700
You cannot use MIN in the WHERE clause, because MIN is an aggregation result over many rows, but in a WHERE clause you look at single rows (before any aggregation takes place).
The task to get the departments in question by joining the tables is a bit weird, because this is not how this should be done in SQL. If you must do it this way, then you only need a slight change to your query: Change the join into an inner join and check the rows' salary.
select distinct
d.department_id, department_name, d.manager_id, location_id
from hr.departments d
join hr.employees e on e.department_id = d.department_id
where e.salary < 5000
order by d.department_id;
The proper solution would use EXISTS or IN instead, so as not to create an unnecessarily large intermediate result that you must get rid of with DISTINCT:
select *
from hr.departments
where department_id in (select department_id from employees where salary < 5000)
order by department_id;
or
select *
from hr.departments d
where exists
(
select null
from employees e
where e.salary < 5000
and e.department_id = d.department_id
)
order by department_id;
This works for your solution, where is use for row filtering like gender = 'Male' while having is for aggregating filtering functions like min(salary) < 5000 but for having you need to group by with something like department.
SELECT
*
FROM
DimEmployee
WHERE
EmployeeID IN (
SELECT
EmployeeID
FROM
DimEmployee
GROUP BY
EmployeeID
HAVING
MIN(Salary) < 5000
)
First of all, don't use distinct, unless you have to. Secondly, you can't use group functions like that.
In order to solve this, you need to break the task into steps, breaking down your sentences.
"...the tables"
So we have this:
SELECT * FROM hr.departments;
... and ...
SELECT * FROM hr.employees;
"HR.DEPARTMENTS and HR.EMPLOYEES"
As you pointed our, the FK is the department.
(we first test the join, then add what we need)
(the 1 is just a placeholder; you can use EMPLOYEE_ID or COUNT(1), it's irrelevant)
SELECT 1
FROM hr.employees e
LEFT JOIN hr.departments d on e.department_id = d.department_id;
"display complete data on departments"
Well, this is simple, you just enumerate the columns you need or use d.*. We'll do this later.
"which the minimum salary is below 5000"
Now we get to the blocking issue. Let's list the records.
SELECT d.*
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id
AND EXISTS (SELECT 1 FROM hr.employees m WHERE m.department_id = d.department_id GROUP BY m.department_id HAVING min(m.salary) < 5000);
But what's this? We get a line for every employee of that department. Well, we can either use DISTINCT, but that is bad practice or we can fix the query.
We'll just remove the employees from the join.
SELECT d.*
FROM hr.departments d
WHERE EXISTS (SELECT 1 FROM hr.employees e WHERE e.department_id = d.department_id GROUP BY e.department_id HAVING min(e.salary) < 5000);
UPDATE:
To respect the task "By joining the tables"
So we have this:
SELECT d.*
FROM hr.departments d,
(
SELECT e.department_id
FROM hr.employees e
GROUP BY e.department_id
HAVING min(salary) < 5000
) e
WHERE e.department_id = d.department_id;

Oracle: range of standard deviation of salary in correlated subquery

I am using an Oracle Developer Database and I have the two tables, Employees and Departments :
I am supposed to find (using a correlated subquery) for each department all the employees, which have a salary within the range of the salary-standard deviation of every single department.
This is what I have tried so far:
SELECT d.department_name, e.employee_id, e.last_name, AVG(e.salary), e.salary
FROM hr.employees e
JOIN hr.departments d
on e.department_id = d.department_id
WHERE e.salary IN
(SELECT salary
FROM hr.employees
WHERE salary >
(SELECT ROUND(AVG(e.salary)-STDDEV(e.salary),2)
FROM hr.employees) AND salary < (SELECT ROUND(AVG(e.salary) + STDDEV(e.salary),2) FROM hr.employees)
GROUP BY d.department_name,
e.employee_id, e.last_name, e.salary;
Even though this query has the correct syntax, it does not show any result.
This other (partial) approach works but how can I query at the same time the employee table?
SELECT d.department_name, e.department_id, ROUND(AVG(e.salary) + STDDEV(e.salary), 2)
AS standard_deviation_max,
ROUND(AVG(e.salary) - STDDEV(e.salary), 2)
AS standard_deviation_min
FROM hr.employees e
JOIN hr.departments d
on e.department_id = d.department_id
GROUP BY department_name, e.department_id;
Since I am new to SQL, I would really appreciate any hint. Thank you in advance
You can use your query to find the employee details and then correlate the salary to the average ± the standard deviation:
SELECT d.department_name,
e.employee_id,
e.last_name,
e.salary
FROM hr.employees e
JOIN hr.departments d
on e.department_id = d.department_id
WHERE EXISTS (
SELECT 1
FROM hr.employees x
HAVING e.salary BETWEEN AVG(e.salary) + STDDEV(e.salary)
AND AVG(e.salary) + STDDEV(e.salary)
);
or, you can use analytic functions:
SELECT department_name,
employee_id,
last_name,
salary
FROM (
SELECT d.department_name,
e.employee_id,
e.last_name,
e.salary,
AVG(e.salary) OVER () AS avg_salary,
STDDEV(e.salary) OVER () AS stddev_salary
FROM hr.employees e
JOIN hr.departments d
on e.department_id = d.department_id
)
WHERE salary BETWEEN avg_salary - stddev_salary
AND avg_salary + stddev_salary;

Find the number of employees in each department using rollup

so i use code like this:
select count(*) as count, d.department_name
from employees e inner join departments d on e.department_id = d.department_id
group by d.department_name;
but I want to add a rollup function, how do I do it?
Like this:
SELECT COUNT (*) AS COUNT, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
GROUP BY ROLLUP (d.department_name);

i am not getting error missing keyword in my code

select e.department_id,e.salary,d.department_name
from employees e
join departments d
on d.department_id=e.department_id
inner join
(
select department_id,max(salary) as max_sal from employees
group by department_id
) as t
on e.department_id=t.department_id
where e.salary =t.max_sal;
Oracle does not support as for table aliases, so you can try:
select e.department_id, e.salary, d.department_name
from employees e join
departments d
on d.department_id = e.department_id inner join
(select department_id, max(salary) as max_sal
from employees
group by department_id
) t
on e.department_id = t.department_id
where e.salary = t.max_sal;
Of course, this would be better written using window functions, but this answers the question that you asked.

Two aggregation functions group by

I'm trying to print the department names that have the sum of all salaries bigger than the average sum on departments.
SELECT d.department_name, SUM(e.salary)
FROM departments d, employees e
WHERE d.department_id = e.department_id
GROUP BY d.department_name
HAVING SUM(e.salary) > (SELECT AVG(SUM(salary)) from employees);
In the second select, after what do I have to group by AVG(SUM(salary))?
You need to repeat the first query in the condition. This can be done with the WITH clause.
WITH dept_sums AS (SELECT d.department_name, SUM(e.salary) sum_salary
FROM departments d, employees e
WHERE d.department_id = e.department_id
GROUP BY d.department_name)
SELECT * FROM dept_sums d_s_1 WHERE d_s_1.sum_salary > (SELECT AVG(sum_salary) FROM dept_sums d_s_2);
This is where window (analytic) functions come in handy. Below I am using AVG() as an analytic function to calculate the average total salary across all departments.
SELECT department_name, dept_salary FROM (
SELECT d.department_name, SUM(e.salary) AS dept_salary
, AVG(SUM(e.salary)) OVER ( ) AS avg_dept_salary
FROM departments d INNER JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_name
) WHERE dept_salary > avg_dept_salary;