How do I return both values if the maximum if two rows are equal? - sql

I need to find the persons with the maximum salaries in each department. I've got the code and found out the persons with the maximum salaries for each department. But then, when I looked at my data, there is another person that has the equal max value in the same department. Is there a way to return both persons' name?
example table:
Department Salary Name
Admin $1000 Amy
Admin $900 Ben
HR $1500 Cassy
HR $1500 Dan
I have tried this code:
SELECT department, Max(salary), name
FROM table
GROUP BY department
ORDER BY salary desc;
I've been getting Admin's person's details OK. But HR I can only get Cassy's name. Is there a way to get Dan's name in my output as well? Can anyone give me an example? Thank you

Hope this can help
SELECT department, salary, name
FROM table t
where salary= (select max(salary) from table where t.department = department)

You didn't mention the DBMS you are using.
With standard SQL, you can use window functions for this (which are supported by all modern DBMS):
select department, salary, name
from (
select department, salary, name,
dense_rank() over (partition by department order by salary desc) as rnk
from department
) t
where rnk = 1;

With NOT EXISTS:
SELECT department, salary, name
FROM tablename t
WHERE NOT EXISTS (
SELECT 1 FROM tablename
WHERE department = t.department and salary > t.salary
)
ORDER BY salary desc, name;
See the demo.
Results:
| Department | Salary | Name |
| ---------- | ------ | ----- |
| HR | 1500 | Cassy |
| HR | 1500 | Dan |
| Admin | 1000 | Amy |

You can use two levels of aggregation if you want one row per department with the names lists on the row:
select dept, salary, names
from (select dept, salary, group_concat(name) as names,
row_number() over (partition by dept order by salary desc) as seqnum
from example
group by dept, salary
) t
where seqnum = 1;

Related

Find the average value from the previous row and the current row for a same department id

I have 3 employees with the same departments. i wanted to find the average of their salaries based on the previous rows.
For example
Employee_id department_id salary avg(salary)
101 1 5000 5000
102 1 10000 7500
103 1 15000 10000
This is like for department id as 1 with first salary we find the average as 5000 for employee id 101 and for the same department id as 1 for the employee id as 102 , we find the average for the 2 values grouped by department id.
Hence
average is (10000+5000) /2 = 7500
But for the employee id as 103, department id is 1 and is grouped with all the above three values of amount.
Hence,
average of salary is (10000+5000+15000)/3 = 10000
The requirement is i have been asked to use query_partition_clause and order_by_clause.
Hence i tried as follows,
select avg(salary) OVER (partition by department_id ORDER BY department_id ) salary, department_id, salary from employee
But i am always getting the values by considering the department of 3 data values.
Henceforth can somebody help on this resolution?
Many Thanks for the help.
Use ORDER BY salary (or ORDER BY employee_id) rather than ORDER BY department_id:
Oracle Setup:
CREATE TABLE employees ( Employee_id, department_id, salary ) AS
SELECT 101, 1, 5000 FROM DUAL UNION ALL
SELECT 102, 1, 10000 FROM DUAL UNION ALL
SELECT 103, 1, 15000 FROM DUAL;
Query:
SELECT e.*,
AVG( salary ) OVER ( PARTITION BY department_id ORDER BY salary ) AS avg_salary
FROM employees e
Output:
EMPLOYEE_ID | DEPARTMENT_ID | SALARY | AVG_SALARY
----------: | ------------: | -----: | ---------:
101 | 1 | 5000 | 5000
102 | 1 | 10000 | 7500
103 | 1 | 15000 | 10000
db<>fiddle here
SELECT EMPLOYEE_ID,
DEPARTMENT_ID,
SALARY,
(SELECT AVG(SALARY)
FROM EMPLOYEES B
WHERE B.EMPLOYEE_ID <= A.EMPLOYEE_ID) AVG_SALARY
FROM EMPLOYEES A
GROUP BY EMPLOYEE_ID,
DEPARTMENT_ID,
SALARY
A sub query can be done in the query itself by filtering the employee ID. I hope I have helped with something.

SQL query, using group functions

Select department_id, max(avg(salary))
From employees
Group by department_id
The above query gives the error
ORA-00937: not a single-group function
I want to display the average salary for each department and also the maximum average salary from the departments. There are multiple departments. I'm using oracle 11g.
If you want the department with the maximum average salary -- and want only one row in the case of ties, you can simply do:
select e.*
from (select department_id, avg(salary) as avg_salary
from employees
group by department_id
order by avg(salary) desc
) e
where rownum = 1;
In Oracle 12C, you don't need a subquery. You can use fetch first 1 row only
The problem with your logic is that you are asking Oracle to do two levels of aggregation in a single GROUP BY, and that isn't possible. As a workaround, we can try using ROW_NUMBER here:
WITH cte AS (
SELECT department_id, AVG(salary) AS avg_salary,
ROW_NUMBER() OVER (ORDER BY AVG(salary) DESC) rn
FROM employees
GROUP BY department_id
)
SELECT department_id, avg_salary
FROM cte
WHERE rn = 1;
If two departments might be tied for the highest average salary, then you may replace ROW_NUMBER above with either RANK or DENSE_RANK.
you can simply rewrite your query as follows. There are many ways to achieve this.
Select department_id, max(avg_Salary) from (
select department_id, avg(salary) as avg_Salary
From employees
Group by department_id)
Group by department_id
If you want to select BOTH average salary per department AND maximum average salary aming departments then you should try the following code:
with qr as (
select department_id, avg(salary) as dept_avg_salary
from employees
group by department_id
)
select qr.department_id
, qr.dept_avg_salary
, (select max(dept_avg_salary) from qr) as max_avg_salary
from qr
I hope I helped!
To get average salary for each department and also the maximum average salary from all departments use this query:
with sal as (
Select department_id, avg(salary) avg_salary
From employees
Group by department_id)
select
DEPARTMENT_ID, AVG_SALARY,
max(AVG_SALARY) over () MAX_AVG_SALARY
from sal
The first subquery is a simple aggregation of the AVG_SALARY.
In the second step you use analytic function to get the global maximum of the averages. You get the global maximum by ommiting the PARTITION BY clause
using only OVER()
The next query displays the average salary for each department and also the maximum average salary from the departments:
SELECT
department_id,
CAST(AVG(salary) AS NUMBER(8, 4)) AS dept_avg_salary,
CAST(MAX(AVG(salary)) OVER () AS NUMBER(8, 4)) AS max_avg_salary
FROM employees
GROUP BY department_id
Sample output:
+---------------+-----------------+----------------+
| DEPARTMENT_ID | DEPT_AVG_SALARY | MAX_AVG_SALARY |
+---------------+-----------------+----------------+
| 1 | 1241.7465 | 1241.7465 |
| 2 | 1191.2267 | 1241.7465 |
| 5 | 1189.8193 | 1241.7465 |
| 4 | 1201.4198 | 1241.7465 |
| 3 | 1212.7079 | 1241.7465 |
+---------------+-----------------+----------------+
Test it here https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=a3c4057eab034ee1707f92f69125a338.

SQL - Retrieving data within groups before and after some condition

With the two following tables:
EMPLOYEE (Fname, Lname, SSN, DNO)
DEPARTMENT (Dname, Dnumber)
For each department that has more than five employees, retrieve the
department name and the number of its employees who are making more
than $40,000
Here is an incorrect solution to this:
SELECT
dname,
COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY
dname
HAVING
COUNT(*) > 5;
It is clear that it would not list any department that have five or more employees unless they all have more than $40,000 salary, because where is applied before group by clause. which is not what we want.
Here is the correct solution:
SELECT
dname, COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5)
GROUP BY
dname
I cant see why is this correct?
Isn't it going to restrict the rows first with employees who have more than $40,000, then do the grouping just like the first query? what is different here?
Sub-Query, the basic:
First, let make this query a bit easier to read :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
As you can see, there is what we call a "sub-query": a query inside the query.
This is the part in dno IN (/*HERE is the Sub-query*/).
As in mathematics parenthesis are run first, so SQL will go find DNO that have more than 5 employees, producing the following query :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
'dno10emp', 'dno24emp', 'dno45emp'
)
GROUP BY dname
Now, you find yourself with a simple query that will produce the result:
of department that have a least one employee with >40k$ salary
and are part of the department with more the 5 employee
What's wrong ?!
Well, I'll said your "good query" isn't that good, and that's why you're struggling: It'll not bring department if they don't have at least one employee with > 40k$.
Here is the query that'll do this :
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
This will bring you all department that have at least 6 employee, then count the number of employee with at least 40K$ (a department could have 0).
Could you show me ?
As an image worth a thousand word :
SQL Fiddle
MySQL 5.6 Schema Setup:
| dname | nb | salary |
|-------------------|----|--------|
| accounting | 2 | 30000 |
| accounting | 4 | 50000 |
| boss | 6 | 150000 |
| garbage-collector | 6 | 15000 |
Query 1:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY dname
HAVING COUNT(*) > 5
Results:
| dname | COUNT(*) |
|-------|----------|
| boss | 6 |
Query 2:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND
dno IN (
SELECT dno FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
Results:
| dname | COUNT(*) |
|------------|----------|
| accounting | 4 |
| boss | 6 |
Query 3:
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
Results:
| dname | COUNT(Employee.salary) |
|-------------------|------------------------|
| accounting | 4 |
| boss | 6 |
| garbage-collector | 0 |
See sample data below.
http://sqlfiddle.com/#!9/357d29/2
The first query will only get departments with 6 or more highy paid employees WHILE the 2nd query will get highly paid employees of those departments with 6 or more employees. Below sample will not show in the 1st query but will show in the 2nd query.
Department Employee Salary
accounting john doe 50k
jan smith 55k
dan brown 60k
eric murphy 60k
al daniels 70k
ellen boyle 30k
1st query: nothing because only five emp have > 40k salary
2nd query: All except ellen boyle. Department has > 5 employees and all except 1 has > 40k salary
For the record, you already got correct answers. I'll just try to explain it in a different way.
Your first query has 1 select statement. It only returns employees with salary > 40k and from departments > 5 employees. Every record will only contain information about an employee with salary > 40k and from departments > 5 employees.
Your second query has 2 select statements:
This is the first one:
Select dname, count(*)
from Department, Employee
where dnumber = dno
and salary > 40000
it returns the count of all employees, by department name who earn > 40000. There are no conditions on the count(*) here. And the condition on the salary has no power over the second select statement:
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
This one returns ALL employees in all departments. This is where we have the condition on the count(*) - but it is only applied locally, to limit the number of employees per department.
And then two statements are joined together - so, first we limit the departments to the ones we are interested in, and then from those only select high-salary employees.
First, never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
I think the best and simplest solution uses conditional aggregation:
SELECT d.dname, SUM(CASE WHEN e.salary > 40000 THEN 1 ELSE 0 END) as num_40kplus
FROM Department d JOIN
Employee e
ON d.dno = e.dnumber
GROUP BY dname
HAVING COUNT(*) > 5;
I see no reason why a subquery would be necessary or desirable.

How to create a oracle view of the max sum of a sum of values of a column based on the values of another

I need to create a view in Oracle 11g that would take these tables:
employees
FirstName | LastName | EmployeeID
-----------------------------------
joe | shmo | 1
bob | moll | 2
salesData
Employee ID | commission on sale
----------------------------------
1 | $20
1 | $30
2 | $50
2 | $60
and then sum up the total commission each employee earned and return the employee who earned the most commission.
So using the sample data the view will contain the employee id :: 2 or bob moll.
This should get you what you need
Create someviewname as view
Select EmployeeID, sum (commision)
from employees
left outer join salesData on salesData.EmployeeID = employees.EmployeeID
Group by EmployeeID, commision
order by commission desc
SELECT employeeID
FROM
(SELECT employeeID,
SUM(commission)
FROM sales
GROUP BY employeeID
ORDER BY SUM(commission)
)
WHERE rownum = 1
Not sure why you want a view of that, but hopefully, you can figure that out.
In Oracle 12g+, you can use fetch:
select employeeid
from sales
group by employeeid
order by sum(commission) desc
fetch first 1 row only;
In earlier versions, one method is to use rownum:
select s.*
from (select employeeid
from sales
group by employeeid
order by sum(commission) desc
) s
where rownum = 1;

Get top results for each group (in Oracle)

How would I be able to get N results for several groups in
an oracle query.
For example, given the following table:
|--------+------------+------------|
| emp_id | name | occupation |
|--------+------------+------------|
| 1 | John Smith | Accountant |
| 2 | Jane Doe | Engineer |
| 3 | Jack Black | Funnyman |
|--------+------------+------------|
There are many more rows with more occupations. I would like to get
three employees (lets say) from each occupation.
Is there a way to do this without using a subquery?
I don't have an oracle instance handy right now so I have not tested this:
select *
from (select emp_id, name, occupation,
rank() over ( partition by occupation order by emp_id) rank
from employee)
where rank <= 3
Here is a link on how rank works: http://www.psoug.org/reference/rank.html
This produces what you want, and it uses no vendor-specific SQL features like TOP N or RANK().
SELECT MAX(e.name) AS name, MAX(e.occupation) AS occupation
FROM emp e
LEFT OUTER JOIN emp e2
ON (e.occupation = e2.occupation AND e.emp_id <= e2.emp_id)
GROUP BY e.emp_id
HAVING COUNT(*) <= 3
ORDER BY occupation;
In this example it gives the three employees with the lowest emp_id values per occupation. You can change the attribute used in the inequality comparison, to make it give the top employees by name, or whatever.
Add RowNum to rank :
select * from
(select emp_id, name, occupation,rank() over ( partition by occupation order by emp_id,RowNum) rank
from employee)
where rank <= 3
tested this in SQL Server (and it uses subquery)
select emp_id, name, occupation
from employees t1
where emp_id IN (select top 3 emp_id from employees t2 where t2.occupation = t1.occupation)
just do an ORDER by in the subquery to suit your needs
I'm not sure this is very efficient, but maybe a starting place?
select *
from people p1
join people p2
on p1.occupation = p2.occupation
join people p3
on p1.occupation = p3.occupation
and p2.occupation = p3.occupation
where p1.emp_id != p2.emp_id
and p1.emp_id != p3.emp_id
This should give you rows that contain 3 distinct employees all in the same occupation. Unfortunately, it will give you ALL combinations of those.
Can anyone pare this down please?