SQL - Getting MAX AVG - sql

So I have been trying to solve this for a while, and even though I have found many interesting things here I simply could not solve it the way it has been requested.
I have two tables:
PROFESSOR (ID, NAME, DEPARTMENT_ID and SALARY) and
DEPARTMENT (ID, NAME).
I have to write a query that shows the DEPARTMENT NAME that has the HIGHEST average SALARY. Also if more than one department have the highest average SALARY, should list all of then in any order.
I have tried so many things and in the end I created a monster, I think. i tried using HAVING but it did not work the way I did. I'm lost. The problem is that I need to use to aggregate functions.
SELECT b.nam, b.average
FROM ( SELECT DEPARTMENT.NAME AS nam, AVG(PROFESSOR.SALARY) AS average
FROM PROFESSOR JOIN DEPARTMENT ON (PROFESSOR.DEPARTMENT_ID =
DEPARTMENT.ID)
GROUP BY DEPARTMENT.NAME) AS b
GROUP BY b.nam, b.average
ORDER BY b.average DESC
But this query is bringing me all the departments with the average, not the maximum.
If someone could please assist me and explain in a easy way I would really appreciate it.
Thanks!

You can use this. If more than one row has same max avg value, with using WITH TIES you can bring all of them.
SELECT TOP 1 WITH TIES DEPARTMENT.NAME AS nam, AVG(PROFESSOR.SALARY) AS average
FROM PROFESSOR
JOIN DEPARTMENT ON (PROFESSOR.DEPARTMENT_ID = DEPARTMENT.ID)
GROUP BY DEPARTMENT.NAME
ORDER BY AVG(PROFESSOR.SALARY) DESC

;WITH x AS (
SELECT t.dept,
T.avg_sal,
rank() OVER(ORDER BY t.avg_sal DESC) AS rnk
FROM
(
SELECT d.name AS 'dept',
avg(p.salary) AS avg_sal
FROM department AS d
INNER JOIN
professor AS p ON p.department_id=d.id
GROUP BY d.name
) AS t
)
-- all depts with highest avg sal
SELECT dept, avg_sal
FROM x
WHERE rnk = 1

You can subquery for the MAX(avgSalary). The way I've done it here was to use a CTE.
WITH cte AS
(
SELECT DEPARTMENT_ID
,AVG(SALARY) [avgSalary]
FROM PROFESSOR
GROUP BY DEPARTMENT_ID
)
SELECT D.[NAME]
,cte.avgSalary
FROM cte INNER JOIN DEPARTMENT D
ON D.ID = cte.DEPARTMENT_ID
WHERE cte.avgSalary = (SELECT MAX(avgSalary)
FROM cte)

I think what you want is:
select
NAME,
max(avg_salary) as max_avg_salary
from
DEPARTMENT d inner join
(select
DEPARTMENT_ID ,
avg(SALARY) as avg_salary
from
PROFESSOR
group by
DEPARTMENT_ID) a on
d.DEPARTMENT_ID = a.DEPARTMENT_ID
there are other ways to do it as you see in the other answers, but I think you want the simplest solution possible using group by to determine both each avg and the max of all avgs. Only other thing you need is a subquery, which you're probably familiar with.
HTH

Related

Find avg of Salary , Departmnent wise , SQL

I would like to find the avg salary from the below table, department wise without a join. The result should have Name, Age, Department, and avg_salary per department
Using a self join as below, is one way I can get the desired output. WOuld like to know option without using a self join.
SELECT name,age, department,avg.avg_salary
FROM Table1
LEFT JOIN
(SELECT department, avg(salary) as avg_salary
FROM Table1 GROUP by department) avg
ON avg.department = table1.department
No platform tagged, but I think most platforms have window functions that you can use like so:
Average Salary Using AVG() Window Function
SELECT name
,age
,department
,AVG(salary) OVER (PARTITION BY department) AS avg_salary
FROM Table1

Why doesn't DISTINCT work in this case? (SQL)

SELECT DISTINCT
employees.departmentname,
employees.firstname,
employees.salary,
employees.departmentid
FROM employees
JOIN (
SELECT MAX(salary) AS Highest, departmentID
FROM employees
GROUP BY departmentID
) departments ON employees.departmentid = departments.departmentid
AND employees.salary = departments.highest;
Why doesn't the DISTINCT work here?
I'm trying to have each department to show only once because the question is asking the highest salary in each department.
Use the ROW_NUMBER() function, as in:
select departmentname, firstname, salary, departmentid
from (
select e.*,
row_number() over(partition by departmentid, order by salary desc) as rn
from employees e
) x
where rn = 1
I'm trying to have each department to show only once because the question is asking the highest salary in each department.
Use window functions:
SELECT e.*
FROM (SELECT e.*,
ROW_NUMBER() OVER (PARTITION BY departmentID ORDER BY salary DESC) as seqnum
FROM employees e
) e
WHERE seqnum = 1;
This is guaranteed to return one row per department, even when there are ties. If you want all rows when there are ties, use RANK() instead.
Why doesn't the DISTINCT work here?
DISTINCT is not a function; it is a keyword that will eliminate duplicate rows when ALL the column values are duplicates. It does NOT apply to a single column.
The DISTINCT keyword has "worked" (i.e. done what it is intended to do) because there are no rows where all the column values are a duplicate of another row's values.
However, it hasn't solved your problem because DISTINCT is not the correct solution to your problem. For that, you want to "fetch the row which has the max value for a column [within each group]" (as per this question).
Gwen, Elena and Paula all have the same salary
and they are in the same department

min(count()) function not working

I want to display the name of the departments that has the least student count. This is my query:
select department_name, count(student_id)
from department d, student s
where d.department_id = s.department_id
group by department_name;
This query works fine and shows student count, but when I add the min function as min(count(student_id)), it fails with:
ERROR at line 1: ORA-00937: not a single-group group function
I don't know what I am doing wrong. Any help/suggestion?
As others have noted, when you wrapped the count in MIN that would have been getting the min PER DEPARTMENT. Because there is already only one row per department, this is a non-operation
What you say you want is a min over ALL DEPARTMENTS. Something like this:
Select department_name from(
select department_name, count(student_id), rank() over(order by count(student_id)) as x
from department d, student s
where d.department_id = s.department_id
group by department_name
)
Where x = 1
How it works? The rank() function assigns a position to rows in order, like winning a race. In this case the order is ascending count() of students, hence the minimum count is in position 1. What is special with rank is that rows with equal counts have an equal rank. We thus know that all rows that are ranked 1 are all equally the minimum count. To show only rows that are =1 we have to use a subquery, alas (I know you didn't want to see it) because oracle doesn't let us say something like where rank() over(...) =1. The subquery isn't really a performance penalty though, because the ranking is prepared at the same time as the rest of the data is single scanned, and then this passes through the filter of the outer where clause. This is different to the other posted answer that uses HAVING.. in that case the query is run, all the counts are found, the min of them all is found, then the query is run again, the counts are found again, and then the HAVING filters them down to the min that was found before
In this case, MIN() function works before GROUP BY function. So error came. You can try this.
SELECT TOP 1 department_name,count(student_id) as A
FROM department d,student s
WHERE d.department_id=s.department_id
GROUP BY department_name
ORDER BY A DESC
If I understand correctly, you need to find the department that has least number of students. Below query can give you the answer.
select department_name
from department d, student s
where d.department_id = s.department_id
group by department_name
having count(student_id) = (select min(a.cnt_std_id) from (select
department_name, count(student_id) cnt_std_id
from department d, student s
where d.department_id = s.department_id
group by department_name)a);
Here's another way:
select department_name, students
from ( select department_name, count(*) as students
, rank() over (order by count(*)) as seq
from department join student using(department_id)
group by department_name )
where seq = 1;
It is an easy way. Goodluck
SELECT department_name, MIN(A)
FROM (select department_name, count(student_id) as A
from department d, student s
where d.department_id = s.department_id
group by department_name)

To display the name of the department that has the least student count in SQL

Tables:
Department (dept_id,dept_name)
Students(student_id,student_name,dept_id)
I am using Oracle. I have to print the name of that department that has the minimum no. of students. Since I am new to SQL, I am stuck on this problem. So far, I have done this:
select d.department_id,d.department_name,
from Department d
join Student s on s.department_id=d.department_id
where rownum between 1 and 3
group by d.department_id,d.department_name
order by count(s.student_id) asc;
The output is incorrect. It is coming as IT,SE,CSE whereas the output should be IT,CSE,SE! Is my query right? Or is there something missing in my query?
What am I doing wrong?
One of the possibilities:
select dept_id, dept_name
from (
select dept_id, dept_name,
rank() over (order by cnt nulls first) rn
from department
left join (select dept_id, count(1) cnt
from students
group by dept_id) using (dept_id) )
where rn = 1
Group data from table students at first, join table department, rank numbers, take first row(s).
left join are used is used to guarantee that we will check departments without students.
rank() is used in case that there are two or more departments with minimal number of students.
To find the department(s) with the minimum number of students, you'll have to count per department ID and then take the ID(s) with the minimum count.
As of Oracle 12c this is simply:
select department_id
from student
group by department_id
order by count(*)
fetch first row with ties
You then select the departments with an ID in the found set.
select * from department where id in (<above query>);
In older versions you could use RANK instead to rank the departments by count:
select department_id, rank() over (order by count(*)) as rnk
from student
group by department_id
The rows with rnk = 1 would be the department IDs with the lowest count. So you could select the departments with:
select * from department where (id, 1) in (<above query>);

Select records with maximum value

I have a table that is called: customers.
I'm trying to get the name and the salary of the people who have the maximum salary.
So I have tried this:
SELECT name, salary AS MaxSalary
FROM CUSTOMERS
GROUP BY salary
HAVING salary = max(salary)
Unfortunately, I got this error:
Column 'CUSTOMERS.name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I know I should add the name column to the group by clause, but I get all the records of the table.
I know that I can do it by:
SELECT name, salary
FROM CUSTOMERS
WHERE salary = (SELECT MAX(salary) FROM CUSTOMERS)
But I want to achieve it by group by and having clauses.
This requirement isn't really suited for a group by and having solution. The easiest way to do so, assuming you're using a modern-insh version of MS SQL Server, is to use the rank window function:
SELECT name, salary
FROM (SELECT name, salary, RANK() OVER (ORDER BY salary DESC) rk
FROM customers) c
WHERE rk = 1
Mureinik's answer is good with rank, but if you didn't want a windowed function for whatever reason, you can just use a CTE or a subquery.
with mxs as (
select
max(salary) max_salary
from
customers
)
select
name
,salary
from
customers cst
join mxs on mxs.max_salary = cst.salary
There was no need to use group by and having clause there, you know. But if you want to use them then query should be
SELECT name, salary
FROM CUSTOMERS
GROUP BY salary
having salary = (select max(salary) from CUSTOMERS)