5

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:

enter image description here

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.

skinkelynet
  • 857
  • 2
  • 9
  • 14

11 Answers11

6

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.

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
3

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)
Rachcha
  • 8,486
  • 8
  • 48
  • 70
  • Doesn't return anything for the IT department though. – skinkelynet May 28 '13 at 19:20
  • 1
    That's because there's no employee record in the IT department! Put an employee record in IT dept and you will see it working. – Rachcha May 28 '13 at 19:23
  • Yeah I know. :-) I'd be curious to know (as specified in the question) how you'd return a row that states that for the IT department, i.e. employee is `null` and so is salary. – skinkelynet May 28 '13 at 19:24
  • Now that's a good question. I will answer to that. Please put this clause as an edit to your question so that everyone is able to see it. – Rachcha May 28 '13 at 19:25
  • Thanks! I've edited the original question to make it more explicit, I hope it's better. – skinkelynet May 28 '13 at 19:29
  • Thank you, I'll await Scotch's extended answer before accepting an answer. – skinkelynet May 28 '13 at 19:33
3

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
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
Scotch
  • 3,186
  • 11
  • 35
  • 50
  • You probably have it set to MySQL where there is no Rank() over function. He said Postgres. – Scotch May 28 '13 at 19:26
  • Oh! My fault. Can you explain how rank() over and the partitioning works? Possibly why it's more effective than what Rachcha proposed. – skinkelynet May 28 '13 at 19:27
  • No probs. At a glance, it doesn't seem that the other suggestion has department names in the query. Also, my suggestion likely has better performance because the other one does an aggregation of the table, and a full scan again and then compares with an `IN` clause. Whereas the one I provided is just doing subselects and a couple small joins. – Scotch May 28 '13 at 19:44
  • This is much more complicated than it needs to be in Postgres. – Erwin Brandstetter May 28 '13 at 22:37
  • It *does* offer an alternative solution to pull multiple peers sharing the highest salary per apartment. It's unclear what the OP wants exactly. But I am not quite satisfied with it. I added a simpler version to my answer. – Erwin Brandstetter May 29 '13 at 00:10
  • @ErwinBrandstetter Gotcha, I've never used postgresql and didn't know about `Distinct On`. It definitely looks prettier than mine so I threw you a bone :) – Scotch May 29 '13 at 00:13
0

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
   )
Sam
  • 1,176
  • 6
  • 19
cyrus
  • 1
0

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; 
Infinite Recursion
  • 6,511
  • 28
  • 39
  • 51
0
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);
SysDragon
  • 9,692
  • 15
  • 60
  • 89
Zakuta
  • 1
0
`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.

Srini
  • 91
  • 1
  • 2
0

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
0

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
Ali Bayat
  • 3,561
  • 2
  • 42
  • 43
0

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);
Delphi Coder
  • 1,723
  • 1
  • 14
  • 25
John
  • 43
  • 3
-1

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

  • the e.name will return the first name in the department rather than the name associated with the max salary. This is due to the nature of the aggregate functions im afraid – Green Demon May 28 '13 at 19:47