Reputation: 351
So, I admit it was one of my exam tasks yesterday and I failed to deal with it... I had simple database of people with their name, salary and function (A, B, C, D, E and F) and I had to select functions that have the biggest and the lowest avg salary. I had also to ignore function C. Example of database:
name salary function
Mike 100 A
John 200 F
Jenny 500 B
Fred 400 B
... 250 C
... 800 D
... 100 E
... 350 E
... 450 F
... 250 A
... 500 B
Example of result:
function avg salary
A 300
C 600
I know how to do it using UNION as in oracle I can group by function order by salary, union that with order by salary desc and for example fetch 1 rows only in both selects. I could have used WHERE but it's impossible to use WHERE with aggregation (like AVG(salary)), but how to do it in single query without UNION, MINUS or INTERSECT?
Upvotes: 0
Views: 105
Reputation: 60482
If you want to return exactly one min/max row you probably get a better plan when you avoid two different ORDER BY:
select function, avg_salary
from (
select function, avg(salary) as avg_salary,
ROW_NUMBER() over (order by avg(salary)) as rnk_asc,
COUNT(*) over () as cnt
from tablename
group by function
)
where rnk_asc = 1 or rnk_asc = cnt;
Caution, RANK will fail if there are two rows with the same MAX value (no row where rnk_asc = cnt
).
Upvotes: 1
Reputation: 146557
Can you use subquery in Oracle ? try this
Select z.Function from
(Select Function, Avg(Salary) avgSal
from tableName
group By function) z
Where z.avgSal = (Select Max(z0.avgSal)
from (Select Avg(Salary) avgSal
from tableName
group By function) z0)
Or z.avgSal = (Select Min(z1.avgSal)
from (Select Avg(Salary) avgSal
from tableName
group By function) z1)
Upvotes: 0
Reputation: 49270
You could use rank
function (use dense_rank
if there can be ties in averages) to order rows by their average salary. Then select the highest and lowest ranked rows.
select t1.function, avg(t1.salary)
from (select
rank() over(order by avg(salary) desc) rnk_high
,rank() over(order by avg(salary)) rnk_low
,function
from tablename
group by function) t
join tablename t1 on t.function = t1.function
where rnk_high = 1 or rnk_low = 1
group by t1.function
Upvotes: 2
Reputation: 191520
Similar to @vkp's approach, but without the join back to the base table:
select function, avg_salary
from (
select function, avg(salary) as avg_salary,
rank() over (order by avg(salary)) as rnk_asc,
rank() over (order by avg(salary) desc) as rnk_desc
from tablename
group by function
)
where rnk_asc = 1 or rnk_desc = 1;
F AVG_SALARY
- ----------
D 800
A 175
The two rank()
calls put each function/average in order:
select function, avg(salary) as avg_salary,
rank() over (order by avg(salary)) as rnk_asc,
rank() over (order by avg(salary) desc) as rnk_desc
from tablename
group by function;
F AVG_SALARY RNK_ASC RNK_DESC
- ---------- ---------- ----------
D 800 6 1
B 4.7E+02 5 2
F 325 4 3
C 250 3 4
E 225 2 5
A 175 1 6
That forms the inline view; the outer query then just selects the rows ranked 1 in either of the generated columns, which is D and A here.
If you had two functions with the same average salary then they would get the same rank, and both ranked as 1 then you'd see both; so you can get more than two results. That may be what you want. If not you can avoid it by defining how to break ties, either with rank()
or dense_rank()
.
Upvotes: 2