PL/SQL- Find second highest salary per department - sql

I am stuck on a school project. I need the second-highest salary per department, but without having the third, fourth etc. I am using Oracle SQL. and I need to do it with subquery.
This is the only code I can come up with so far. I can filter all salaries after the highest, but I cant filter only the second highest. I have two statements, that produce the same result at the end.
select e.department_id, d.department_name, e.first_name, e.last_name, e.salary
from employees e inner join departments d
on (e.department_id = d.department_id)
where e.salary < (select max(salary)
from employees e
where e.department_id = d.department_id
order by d.department_id
offset 1 row fetch next 2 row only)
order by e.department_id;
Or
select department_id, first_name, last_name, salary
from employees
where salary < any (select max(salary)
from employees
group by department_id)
order by department_id;

Related

Selecting employees from departments with average salary smaller than

I have to write query that returns all employees from departments with average salary smaller than 12000.
I wrote down this query
SELECT DEPARTMENT_ID, FIRST_NAME, LAST_NAME
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID,FIRST_NAME,LAST_NAME
HAVING AVG(SALARY) < 12000
ORDER BY LAST_NAME ASC;
but it doesn't seem to work. Instead of taking all employees from those departments it takes only ones with salary smaller than 12000.
How to write this query properly?
Thanks in advance.
You need the average salary for the department. I would use a window function:
SELECT DEPARTMENT_ID, FIRST_NAME, LAST_NAME
FROM (SELECT e.*, AVG(SALARY) OVER (PARTITION BY DEPARTMENT_ID) as avg_salary_dept
FROM EMPLOYEES e
) e
WHERE avg_salary_dept < 12000
ORDER BY LAST_NAME ASC;
You need subquery :
SELECT e.DEPARTMENT_ID, e.FIRST_NAME, e.LAST_NAME
FROM EMPLOYEES e
WHERE e.DEPARTMENT_ID IN (SELECT e1.DEPARTMENT_ID
FROM EMPLOYEES e1
GROUP BY e1.DEPARTMENT_ID
HAVING AVG(e1.SALARY) < 12000
)
ORDER BY e.LAST_NAME ASC;
You can use correlated subquery
SELECT e.DEPARTMENT_ID, e.FIRST_NAME, e.LAST_NAME
FROM EMPLOYEES a
WHERE exists (SELECT 1 FROM EMPLOYEES b
where a.department_id=b.department_id
HAVING AVG(b.SALARY) < 12000
);
Try it..
SELECT e.DEPARTMENT_ID, e.FIRST_NAME, e.LAST_NAME
from employee e where e.DEPARTMENT_ID in
(select e.DEPARTMENT_ID from employee e
group by e.DEPARTMENT_ID
having avg(salary)<3000)

Why one query returns more than other

Using the following question could you please explain to me what's the difference between the two SQLs and why they do not have the same result?
Display the last name, department name, and salary of any employee whose salary and commission match the salary and commission of any employee located in location ID1700.
SELECT E.LAST_NAME, D.DEPARTMENT_NAME, E.SALARY
FROM EMPLOYEES E
JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID =D.DEPARTMENT_ID)
WHERE E.SALARY IN (SELECT SALARY
FROM EMPLOYEES
WHERE D.LOCATION_ID = 1700) AND
E.COMMISSION_PCT IN (SELECT COMMISSION_PCT
FROM EMPLOYEES
WHERE D.LOCATION_ID = 1700);
(0 outputs)
and
SELECT e.last_name, d.department_name, e.salary
FROM employees e,
departments d
WHERE e.department_id = d.department_id AND
(salary, NVL(commission_pct,0)) IN (SELECT salary,
NVL(commission_pct,0)
FROM employees e,
departments d
WHERE e.department_id = d.department_id AND
d.location_id = 1700);
(36 outputs)
Note that this query:
SELECT e.last_name, d.department_name, e.salary
FROM employees e,
departments d
WHERE e.department_id = d.department_id AND
(salary, NVL(commission_pct,0)) IN (SELECT salary,
NVL(commission_pct,0)
FROM employees e,
departments d
WHERE e.department_id = d.department_id AND
d.location_id = 1700);
... will include any employee that has the same salary and commission as someone in location 1700. By consequence all employees in location 1700 will be included in the result set. But other employees, not in that location, could be found as well, if only there is someone in location 1700 that has exactly the same salary and commission. So this query answers correctly to the given assignment.
However, the other query has an odd condition in both sub queries, in that it applies a condition on the outer query (the only place where D is defined) -- not the sub query -- and it does so twice (which does not help):
SELECT E.LAST_NAME, D.DEPARTMENT_NAME, E.SALARY
FROM EMPLOYEES E
JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID =D.DEPARTMENT_ID)
WHERE E.SALARY IN (SELECT SALARY
FROM EMPLOYEES
WHERE D.LOCATION_ID = 1700) AND
E.COMMISSION_PCT IN (SELECT COMMISSION_PCT
FROM EMPLOYEES
WHERE D.LOCATION_ID = 1700);
You would not change anything substantial if you would move that condition on location_id in the outer where clause. And now it becomes clear that this query excludes any employees that are not in location 1700 -- this is not always true for the other query.
Furthermore, this one does not have the NVL function applied to commission_pct, which means that employees will be excluded also if they have null as commission_pct. This is because NULL IN (SELECT ...) always evaluates to false.
So there are two reasons why this query can return fewer records, and also why it is the wrong answer to the assignment you got.

How to display department_id, job_id and the average salary for each job classification in each department?

I have tried this but obviously it will only give mt the average salary for all the departments not each job classification
SELECT e.department_id, e.job_id, (SELECT avg(salary)
FROM employees)
FROM employees e
GROUP BY department_id, job_id;
Do you want the department's avg salary? Do a correlated sub-query:
SELECT e.department_id, e.job_id, (SELECT avg(salary)
FROM employees e2
where e2.department_id = e.department_id)
FROM employees e
I suppose you want this (if your aim is get average salary for couple department_id / job_id):
SELECT e.department_id, e.job_id, avg(e.salary)
FROM employees e
GROUP BY e.department_id, e.job_id;
If you want average only for department_id you can write:
SELECT e.department_id, e.job_id,
(SELECT avg(e1.salary)
FROM employees e1
where e1.department_id = e.department_id)
FROM employees e
GROUP BY e.department_id, e.job_id;

Employees with largest salary in department

I found a couple of SQL tasks on Hacker News today, however I am stuck on solving the second task in Postgres, which I'll describe here:
You have the following, simple table structure:
List the employees who have the biggest salary in their respective departments.
I set up an SQL Fiddle here for you to play with. It should return Terry Robinson, Laura White. Along with their names it should have their salary and department name.
Furthermore, I'd be curious to know of a query which would return Terry Robinsons (maximum salary from the Sales department) and Laura White (maximum salary in the Marketing department) and an empty row for the IT department, with null as the employee; explicitly stating that there are no employees (thus nobody with the highest salary) in that department.
Return one employee with the highest salary per dept.
Use DISTINCT ON for a much simpler and faster query that does all you are asking for:
SELECT DISTINCT ON (d.id)
d.id AS department_id, d.name AS department
,e.id AS employee_id, e.name AS employee, e.salary
FROM departments d
LEFT JOIN employees e ON e.department_id = d.id
ORDER BY d.id, e.salary DESC;
->SQLfiddle (for Postgres).
Also note the LEFT [OUTER] JOIN that keeps departments with no employees in the result.
This picks only one employee per department. If there are multiple sharing the highest salary, you can add more ORDER BY items to pick one in particular. Else, an arbitrary one is picked from peers.
If there are no employees, the department is still listed, with NULL values for employee columns.
You can simply add any columns you need in the SELECT list.
Find a detailed explanation, links and a benchmark for the technique in this related answer:
Select first row in each GROUP BY group?
Aside: It is an anti-pattern to use non-descriptive column names like name or id. Should be employee_id, employee etc.
Return all employees with the highest salary per dept.
Use the window function rank() (like #Scotch already posted, just simpler and faster):
SELECT d.name AS department, e.employee, e.salary
FROM departments d
LEFT JOIN (
SELECT name AS employee, salary, department_id
,rank() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM employees e
) e ON e.department_id = d.department_id AND e.rnk = 1;
Same result as with the above query with your example (which has no ties), just a bit slower.
This is with reference to your fiddle:
SELECT * -- or whatever is your columns list.
FROM employees e JOIN departments d ON e.Department_ID = d.id
WHERE (e.Department_ID, e.Salary) IN (SELECT Department_ID, MAX(Salary)
FROM employees
GROUP BY Department_ID)
EDIT :
As mentioned in a comment below, if you want to see the IT department also, with all NULL for the employee records, you can use the RIGHT JOIN and put the filter condition in the joining clause itself as follows:
SELECT e.name, e.salary, d.name -- or whatever is your columns list.
FROM employees e RIGHT JOIN departments d ON e.Department_ID = d.id
AND (e.Department_ID, e.Salary) IN (SELECT Department_ID, MAX(Salary)
FROM employees
GROUP BY Department_ID)
This is basically what you want. Rank() Over
SELECT ename ,
departments.name
FROM ( SELECT ename ,
dname
FROM ( SELECT employees.name as ename ,
departments.name as dname ,
rank() over (
PARTITION BY employees.department_id
ORDER BY employees.salary DESC
)
FROM Employees
JOIN Departments on employees.department_id = departments.id
) t
WHERE rank = 1
) s
RIGHT JOIN departments on s.dname = departments.name
Good old classic sql:
select e1.name, e1.salary, e1.department_id
from employees e1
where e1.salary=
(select maxsalary=max(e.salary) --, e. department_id
from employees e
where e.department_id = e1.department_id
group by e.department_id
)
Table1 is emp - empno, ename, sal, deptno
Table2 is dept - deptno, dname.
Query could be (includes ties & runs on 11.2g):
select e1.empno, e1.ename, e1.sal, e1.deptno as department
from emp e1
where e1.sal in
(SELECT max(sal) from emp e, dept d where e.deptno = d.deptno group by d.dname)
order by e1.deptno asc;
SELECT
e.first_name, d.department_name, e.salary
FROM
employees e
JOIN
departments d
ON
(e.department_id = d.department_id)
WHERE
e.first_name
IN
(SELECT TOP 2
first_name
FROM
employees
WHERE
department_id = d.department_id);
`select d.Name, e.Name, e.Salary from Employees e, Departments d,
(select DepartmentId as DeptId, max(Salary) as Salary
from Employees e
group by DepartmentId) m
where m.Salary = e.Salary
and m.DeptId = e.DepartmentId
and e.DepartmentId = d.DepartmentId`
The max salary of each department is computed in inner query using GROUP BY. And then select employees who satisfy those constraints.
Assuming Postgres
Return highest salary with employee details, assuming table name emp having employees department with dept_id
select e1.* from emp e1 inner join (select max(sal) avg_sal,dept_id from emp group by dept_id) as e2 on e1.dept_id=e2.dept_id and e1.sal=e2.avg_sal
Returns one or more people for each department with the highest salary:
SELECT result.Name Department, Employee2.Name Employee, result.salary Salary
FROM ( SELECT dept.name, dept.department_id, max(Employee1.salary) salary
FROM Departments dept
JOIN Employees Employee1 ON Employee1.department_id = dept.department_id
GROUP BY dept.name, dept.department_id ) result
JOIN Employees Employee2 ON Employee2.department_id = result.department_id
WHERE Employee2.salary = result.salary
SQL query:
select d.name,e.name,e.salary
from employees e, depts d
where e.dept_id = d.id
and (d.id,e.salary) in
(select dept_id,max(salary) from employees group by dept_id);
Take look at this solution
SELECT
MAX(E.SALARY),
E.NAME,
D.NAME as Department
FROM employees E
INNER JOIN DEPARTMENTS D ON D.ID = E.DEPARTMENT_ID
GROUP BY D.NAME

Single-row subquery returns more than one row

I need some help with oracle sql. The problem: I have 2 tables employee and department. I got the average department salary from one query and i want to use it to see how many employees make more money than the average of their department. I have this so far.
This query returns the avg of the department:
select ROUND(AVG(Salary), 2) Dept_avg_sal
from employee, department
where department.department_id = employee.department_id
group by department_name
What i am trying to do is:
select employee_name,
salary,
d.department_name
from employee e,
department d
where salary > (select ROUND(AVG(Salary), 2) Dept_avg_sal
from employee,
department
where department.department_id = employee.department_id
group by department_name)
The error that im getting is :01427. 00000 - "single-row subquery returns more than one row"
I know that 2 employees in the same department make more money than the average and i think this is what is causing the issue.
EMPLOYEE_NAME - SALARY - -DEPARTMENT_NAME- DEPT_AVG_SAL
-------------------- ---------------------- -------------------- ------------
FISHER - 3000.00 - SALES - 2500.00
JONES - 3000.00 - ACCOUNTING - 2750.00
KING - 5000.00 - EXECUTIVE - 4500.00
**SCOTT - 2500.00 - IT - 2100.00
SMITH - 2900.00 - IT - 2100.00**
WILSON - 3000.00 - RESEARCH - 2633.33
Any help would be really appreciated.
Your initial query is missing any join condition on the outer query and any correlation condition in the inner query that would limit that to just the row for the department of interest. Also generally you do not want to group by name as presumably id is the primary key.
Resolving these issues to fix your correlated subquery gives
SELECT e.employee_name,
e.salary,
d.department_name
FROM employee e
JOIN department d
ON d.department_id = e.department_id
WHERE e.salary > (SELECT ROUND(AVG(Salary), 2) Dept_avg_sal
FROM employee e2
WHERE e2.department_id = e.department_id)
But you may find ditching the scalar correlated sub-query and replacing with a derived table works better.
SELECT e.employee_name,
e.salary,
d.department_name
FROM employee e
JOIN department d
ON d.department_id = e.department_id
JOIN (SELECT ROUND(AVG(Salary), 2) Dept_avg_sal,
department_id
FROM employee
GROUP BY department_id) e2
ON e2.department_id = e.department_id
AND e.salary > e2.Dept_avg_sal
For Oracle the following should also work I believe
SELECT employee_name,
salary,
d.department_name
FROM (SELECT employee_name,
salary,
d.department_name,
AVG(Salary) OVER (PARTITION BY e.department_id) AS AvgSalary
FROM employee e
JOIN department d
ON d.department_id = e.department_id)
WHERE salary > AvgSalary
The > operator accepts only one value, thus your inner SELECT has to return exactly 1 row. My guess is that you get multiple rows. Look at what your inner SELECT returns and try LIMIT 1.
I think you should put an extra d.department_id = department.department_id condition to the subquery (not tested):
select employee_name,
salary,
d.department_name
from employee e,
department d
where salary > (select ROUND(AVG(Salary), 2) Dept_avg_sal
from employee,
department
where department.department_id = employee.department_id
AND d.department_id = department.department_id
group by department_name)
Or just write:
select e.employee_name,
e.salary,
d.department_name
from employee e,
department d
where e.department_id = d.department_id
AND salary > (select ROUND(AVG(Salary), 2) Dept_avg_sal
from employee
where e.department_id = employee.department_id)