SQL with nested group function - sql

I'm using Oracle db 11g
I have a table 'EMPLOYEES' like this...
ID JOB_ID SALARY
100 AD_PRES 24000
101 AD_VP 17000
102 AD_VP 17000
103 IT_PROG 9000
104 IT_PROG 6000
107 IT_PROG 4200
124 ST_MAN 5800
141 ST_CLERK 3500
142 ST_CLERK 3100
143 ST_CLERK 2600
144 ST_CLERK 2500
149 SA_MAN 10500
174 SA_REP 11000
176 SA_REP 8600
178 SA_REP 7000
200 AD_ASST 4400
201 MK_MAN 13000
202 MK_REP 6000
205 AC_MGR 12000
206 AC_ACCOUNT 8300
And I want to get the maximum of average salary of each job(job_id) and the job of it.
I firstly tried this and It resulted in error
SELECT MAX(AVG(salary)) AS max_avg_salary, job_id
FROM employees
GROUP BY job_id;
ORA-00937: not a single-group group function
00937. 00000 - "not a single-group group function"
*Cause:
*Action:
I finally made it with this code
WITH emp AS (SELECT AVG(salary) AS avg_salary, job_id FROM employees GROUP BY job_id)
SELECT e1.avg_salary AS max_avg_salary, e1.job_id
FROM emp e1 JOIN (SELECT MAX(avg_salary) AS max_avg_salary FROM emp) e2
ON e1.avg_salary = e2.max_avg_salary;
MAX_AVG_SALARY JOB_ID
24000 AD_PRES
What I want to know is..
Why my first code makes error?
Is there any better (more simple or easier) way than my code?

How about this?
select job_id, salary
from ( select job_id,
avg (salary) salary,
rank () over (order by avg (salary) desc) rnk
from employees
group by job_id)
where rnk = 1;

you can modify your query like this,
SELECT MAX(avg_salary)
FROM (SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id);
or if you want to display the job id also,
SELECT avg_salary, job_id
FROM (SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
ORDER BY avg_salary DESC)
WHERE ROWNUM = 1;

[Edited] ... Unless you meant to get just a single answer, in which case the other respondent's solution should work and I'll offer a variation:
Non-Oracle:
SELECT TOP 1 *
FROM
(SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
) a
ORDER BY a.avg_salary DESC
Littlefoot pointed out, quite correctly, that this won't work on Oracle because of the TOP 1. You should pick his solution. I'm going to leave this here for any non-Oracle folks:
However, the asker himself, C Park, suggested an Oracle-compatible variation on this using ROWNUM. He's tested it and it worked for him so this will work in..
Oracle:
SELECT *
FROM
(SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
) a
ORDER BY a.avg_salary DESC
WHERE ROWNUM = 1
I hope this helps.

Related

Regarding the working mechanism of NOT IN clause in Oracle SQL

Following are the results of my sql queries:
SELECT DISTINCT(department_id)
from employees
ORDER BY department_id;
Result:
DEPARTMENT_ID
10
20
30
40
50
60
70
80
90
100
110
Then:
SELECT department_id
FROM departments
ORDER BY department_id;
DEPARTMENT_ID
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
When I execute the Following Query, I get the result:
SELECT department_id
from departments
where department_id IN (select distinct(department_id) from employees)
ORDER BY department_id;
DEPARTMENT_ID
10
20
30
40
50
60
70
80
90
100
110
However, the following query returns "NO Rows Selected" when I execute the following query:
SELECT department_id
from departments
WHERE department_id NOT IN (select distinct(department_id) from employees)
ORDER BY department_id;
What I was expected was the Id of departments that are not present in Employees table.
I feel its a beginner's mistake however i am not able to resolve this issue. Any help would be largely appreciated.
This is because at least one department_id in employees isNULL. When any value in theNOT INlist isNULL`, no rows are returned at all.
To fix this, I simply recommend always using NOT EXISTS with a subquery:
SELECT d.department_id
FROM departments d
WHERE NOT EXISTS (SELECT 1 FROM employees e WHERE d.department_id = e.department_id)
ORDER BY d.department_id;
(Or by using a LEFT JOIN/WHERE.)
You could fix this using a WHERE clause in the subquery. I think it is better to use a construct that does what you intend.

Why cannot exclude 'IT_PROG' in SQL query?

The database data is in
http://www.w3resource.com/mysql-exercises/aggregate-function-exercises/write-a-query-to-get-the-average-salary-for-each-job-id-excluding-programmer.php
The result:
sqlite> SELECT job_id, AVG(salary)
...> FROM employees
...> WHERE job_id <> 'IT_PROG'
...> GROUP BY job_id;
JOB_ID AVG(salary)
------------ -----------
AC_ACCOUNT 8300.0
AC_MGR 12000.0
AD_ASST 4400.0
AD_PRES 24000.0
AD_VP 17000.0
FI_ACCOUNT 7920.0
FI_MGR 12000.0
HR_REP 6500.0
**IT_PROG 5760.0**
MK_MAN 13000.0
MK_REP 6000.0
PR_REP 10000.0
PU_CLERK 2780.0
PU_MAN 11000.0
SA_MAN 12200.0
SA_REP 8350.0
SH_CLERK 3215.0
ST_CLERK 2785.0
ST_MAN 7280.0
sqlite>
Above result still include IT_PROG, why?
It looks a correct query. You should look at database settings for the "string" type.
Try also to apply trim function that removes the blank spaces:
SELECT job_id, AVG(salary) FROM employees WHERE TRIM(job_id) <> 'IT_PROG' GROUP BY job_id;

Oracle, row_number, deleting rows

I have a little problem. Here's a code:
select * from
(select department_id, manager_id,last_name, salary, row_number() over
(partition by department_id,manager_id order by salary asc) p_number
from employees )
where p_number <=3;
This query shows 3 the least salaries in every department for every manager in department
ex: Dep no.30 has 2 managers (100 and 114), manager no.100 has 1 employee beneath him, manager no.114 - 3 employees.
part of result:
20 100 Hartstein 13000 1
20 201 Fay 6000 1
30 100 Raphaely 11000 1
30 114 Colmenares 2500 1
30 114 Himuro 2600 2
30 114 Tobias 2800 3
Now, i want to delete all rows, where there is only 1 employee beneath manager. In this example there should be, Harstein, Fay, Raphaely. Colmenares also has number 1, but there is more employees under manager 114.
Any ideas?
PS Having Count is out, because there is no group by, and modifying
where p_number <=3;
into
where p_number <=3 and p_number >1;
is also out, because it will delete all my employees with no.1 and i want to 'safe' few as they have more 'colleagues' :)
Thanks!
This will give the result and only require a single table scan:
SELECT *
FROM (
SELECT department_id,
manager_id,
last_name,
salary,
ROW_NUMBER()
OVER ( PARTITION BY department_id, manager_id
ORDER BY salary ASC ) AS p_number,
COUNT(*)
OVER ( PARTITION BY department_id, manager_id ) AS p_count
FROM employees
ORDER BY department_id, manager_id, salary
)
WHERE p_count >= 2
AND p_number <= 3;
Output:
DEPARTMENT_ID MANAGER_ID LAST_NAME SALARY P_NUMBER P_COUNT
------------- ---------- ---------- ---------- ---------- ----------
30 114 Colmenares 2500 1 3
30 114 Himuro 2600 2 3
30 114 Tobias 2800 3 3

How to retrieve max salary of employee from each department

Consider the table Employee:
Department number Employee_id Salary
1 123 2000
1 234 3266
1 657 3265
2 546 2050
2 657 3000
2 121 6000
3 131 6500
3 141 5000
3 151 1050
Now I want to retrieve the employee_id having highest salary from each department. How to write the query for this?
A good way to approach these queries is using row_number():
select department_number, employee_id, salary
from (select e.*, row_number() over (partition by department order by salary desc) as seqnum
from employee e
) e
where seqnum = 1;
If you want to get multiple rows for a department when there are ties, then use dense_rank() or rank() instead of row_number().
Try this
SELECT Dept_Num,EmpID,Salary
from Employee
WHERE Salary IN (Select MAX(Salary) from Employee Group By Dept_Num)
Can you use the max statement:
Select departament_number,employee_id,salary
from employee
where salary in (select MAX(salary) from employee group by departament_number)

getting error as too many values in my query

dpt_no salary period start_date end_date
------ ----- ------ ---------- --------
100 12580 15months 12-DEC-07 10-DEC-10
101 15500 19months 10-JAN-07 10-DEC-11
102 7777 18months 11-JUL-07 21-APR-11
103 9999 11months 07-JUL-07 31-JAN-11
104 8500 9months 12-MAR-07 27-MAR-11
105 10000 20months 17-SEP-07 01-AUG-11
106 25000 7months 17-NOV-07 26-JUL-11
107 100000 6months 05-MAY-07 21-JUN-11
108 35000 16months 28-FEB-08 21-JUN-11
109 5000 16months 02-DEC-08 19-AUG-11
i'm write query for giving rank to salary and getting that rank using ampersand. That query is
select salary from salary
where &RANK=(select salary, rank() over(order by salary desc)
as "rank" from salary salary).
BUT i'm getting error as "too many values". can any one help me please
It should be something like this:
select t1.salary from
(select salary.salary,
rank() over(order by salary desc)
as "rank" from salary) t1
where "rank"=&RANK
SQLFiddle demo