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;
Related
Table: EMPLOYEES
Columns:
EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID
My current output from the Employees table looks like this:
EMPLOYEE_ID LAST_NAME JOB_ID SALARY
----------- ------------------------- ---------- ----------
205 Higgins AC_MGR 12000
149 Zlotkey SA_MAN 12500
201 Hartstein MK_MAN 13000
102 De Haan AD_VP 17000
101 Kochhar AD_VP 18000
My SQL:
SELECT employee_id,
last_name,
job_id,
salary
from employees
where (job_id like '%VP' OR
Job_id like '%MAN' OR
Job_id like '%MGR')
AND (salary < 6000 OR salary >11000)
order by 4
I need a way to display the salaries before and after a raise, but I also need to be able to give different raises for different job_ids. For example, giving a raise only to the VPs and not to the managers, or giving the VPs a bigger raise than the managers.
One option is to create a CTE which says who gets what; then (outer) join it to the original table. Something like this:
SQL> with raise (job, raise_pct) as
2 (select 'CLERK' , 10 from dual union all
3 select 'ANALYST', 20 from dual
4 )
5 select e.empno, e.ename, e.job, e.sal old_salary,
6 e.sal * (1 + r.raise_pct/100) new_salary
7 from emp e left join raise r on r.job = e.job
8 order by e.job, e.ename;
EMPNO ENAME JOB OLD_SALARY NEW_SALARY
---------- ---------- --------- ---------- ----------
7902 FORD ANALYST 3000 3600
7788 SCOTT ANALYST 3000 3600
7876 ADAMS CLERK 1100 1210
7900 JAMES CLERK 950 1045
7934 MILLER CLERK 1300 1430
7369 SMITH CLERK 800 880
7698 BLAKE MANAGER 2850
7782 CLARK MANAGER 2450
7566 JONES MANAGER 2975
7839 KING PRESIDENT 5000
7499 ALLEN SALESMAN 1600
7654 MARTIN SALESMAN 1250
7844 TURNER SALESMAN 1500
7521 WARD SALESMAN 1250
14 rows selected.
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.
These are my two tables:
employee
employee_id employee_name job manager_id hire_date salary commission department_id
----------------------------------------------------------------------- -------------------------------------
7839 KING PRESIDENT 20-NOV- 01 5000 50
7596 JOST VICE PRESIDENT 7839 04-MAY- 01 4500 50
7603 CLARK VICE PRESIDENT 7839 12-JUN- 01 4000 50
7566 JONES PUBLIC ACCOUNTANT 7596 05-APR- 01 3000 10
7886 STEEL PUBLIC ACCOUNTANT 7566 08-MAR- 03 2500 10
7610 WILSON ANALYST 7596 03-DEC- 01 3000 20
7999 WOLFE ANALYST 7610 15-FEB- 02 2500 20
7944 LEE ANALYST 7610 04-SEP- 06 2400 20
7900 FISHER SALESMAN 7603 06-DEC- 01 3000 500 30
7921 JACKSON SALESMAN 7900 25-FEB- 05 2500 400 30
7952 LANCASTER SALESMAN 7900 06-DEC- 06 2000 150 30
7910 SMITH DATABASE ADMINISTRATOR 7596 20-DEC- 01 2900 40
7788 SCOTT PROGRAMMER 7910 15-JAN- 03 2500 40
7876 ADAMS PROGRAMMER 7910 15-JAN- 03 2000 40
7934 MILLER PROGRAMMER 7876 25-JAN- 02 1000 40
8000 BREWSTER TBA 22-AUG- 13 2500
department
department_id department_name address
------------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 IT DALLAS
50 EXECUTIVE NEW YORK
60 MARKETING CHICAGO
I need to write a statement using subqueries (not joins) to display the name and address of all departments (excluding departments in Dallas) having the maximum number of employees. I cannot have any hard coding except the string 'DALLAS'
The result should look like this:
DEPARTMENT_NAME ADDRESS
-------------------- --------------------
EXECUTIVE NEW YORK
SALES CHICAGO
I'm at a loss on doing this without joins. Here are my three tries:
SELECT department_name, address
FROM department
WHERE department_id NOT IN
(SELECT department_id
FROM department
WHERE UPPER(address) + 'DALLAS')
ORDER BY department_name;
SELECT department_name, address
FROM department
WHERE department_id NOT IN
(SELECT department_id
FROM department
WHERE UPPER(address) = 'DALLAS')
AND department_id > ALL
(select COUNT(department_id) FROM employee GROUP BY department_id)
ORDER BY department_id;
select *
from department
where department_id in (select department_id
from employee
group by department_id
having count(*) = (select max(num)
from (select department_id,
count(*) as num
from employee_tbl
where address != 'DALLAS'
group by department_id)));
Can anyone PLEASE tell me if I'm even getting close and guide me in the right direction? Thanks
The departments with the maximum number of employees (using a single table scan):
SELECT department_id
FROM (
SELECT department_id,
RANK() OVER ( ORDER BY num_employees DESC ) AS rnk
FROM (
SELECT department_id,
COUNT(1) AS num_employees
FROM employees
GROUP BY department_id
)
)
WHERE rnk = 1;
Finding the departments not in DALLAS:
SELECT department_id
FROM departments
WHERE address <> 'DALLAS'
So combining the two:
SELECT department_id
FROM (
SELECT department_id,
RANK() OVER ( ORDER BY num_employees DESC ) AS rnk
FROM (
SELECT department_id,
COUNT(1) AS num_employees
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE address <> 'DALLAS'
)
GROUP BY department_id
)
)
WHERE rnk = 1;
maybe something like this?
select department_name, address
from department
where department_id in
(SELECT e.department_id
FROM employee e
group by e.department_id
having count(*) = (
select max(count(e2.employee_id))
FROM employee e2
WHERE department_id NOT IN (SELECT department_id
FROM department
WHERE UPPER(address) = 'DALLAS')
group by e2.department_id
)
)
and UPPER(address) != 'DALLAS'
but it is not the best query in plane :) very strange
In your query Ellen I did not understand where from the table
employee_tbl
Can you guys help me with this query? I want to modify it so that it only shows me those departments with at least 3 employees from this query (with the 78%), and not from (the original table of employees/department). Every time i try " having COUNT (department_ID) or # the WHERE clause it gives me an error. Do i need to do a 2nd join? Thank you
select *
from
(
SELECT b.employee_id, b.employee_name,b.salary, a.department_id,
NVL(a.department_name, 'N/A') as dept_name,
max(b.salary) over (partition by a.department_id) as max_sal
FROM department a, employee b
WHERE a.department_id(+) = b.department_id
) z
WHERE salary > (max_sal*.78 )
Results:
EMPLOYEE_ID EMPLOYEE_NAME SALARY DEPARTMENT_ID DEPT_NAME MAX_SAL
7566 JONES 3000 10 ACCOUNTING 3000
7886 STEEL 2500 10 ACCOUNTING 3000
7944 LEE 2400 20 RESEARCH 3000
7999 WOLFE 2500 20 RESEARCH 3000
7610 WILSON 3000 20 RESEARCH 3000
7921 JACKSON 2500 30 SALES 3000
7900 FISHER 3000 30 SALES 3000
7788 SCOTT 2500 40 IT 2900
7910 SMITH 2900 40 IT 2900
7603 CLARK 4000 50 EXECUTIVE 5000
7596 JOST 4500 50 EXECUTIVE 5000
7839 KING 5000 50 EXECUTIVE 5000
8000 BREWSTER 2500 N/A 2500
13 rows selected
This are RESULTS I need to get:
EMPLOYEE_ID EMPLOYEE_NAME SALARY DEPARTMENT_ID DEPT_NAME MAX_SAL
7944 LEE 2400 20 RESEARCH 3000
7999 WOLFE 2500 20 RESEARCH 3000
7610 WILSON 3000 20 RESEARCH 3000
7603 CLARK 4000 50 EXECUTIVE 5000
7596 JOST 4500 50 EXECUTIVE 5000
7839 KING 5000 50 EXECUTIVE 5000
6 rows selected
You can do the calculation using an analytic function in another subquery:
select de.*
from (select de.*, count(*) over (partition by department_id) as cnt
from (SELECT e.employee_id, e.employee_name, d.salary, d.department_id,
NVL(d.department_name, 'N/A') as dept_name,
max(e.salary) over (partition by d.department_id) as max_sal
FROM department d JOIN
employee e
ON d.department_id = e.department_id
) de
where salary > max_sal*.78
) de
where cnt >= 3;
The outer join doesn't seem necessary so I replaced it with an inner join and modern join syntax. I also changed the table aliases to be abbreviations for the tables. It makes the code easier to read.
Oracle ships with a very handy feature. You can create hierarchical queries (recursive behaviour) using the following clause:
CONNECT BY [NOCYCLE] {condition [AND condition...]} [START WITH condition]
As documented here:
http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/queries003.htm
I'm wondering, are there any other established RDBMS that support an equivalent or similar syntax? Or can recursive behaviour like this be generically simulated using regular SQL?
A good example that I'd like to be able to simulate is this (taken from the Oracle documentation):
SELECT LPAD(' ', 2 * (LEVEL-1)) || last_name org_chart,
employee_id, manager_id, job_id
FROM employees
START WITH job_id = 'AD_VP'
CONNECT BY PRIOR employee_id = manager_id;
Resulting in:
ORG_CHART EMPLOYEE_ID MANAGER_ID JOB_ID
------------------ ----------- ---------- ----------
Kochhar 101 100 AD_VP
Greenberg 108 101 FI_MGR
Faviet 109 108 FI_ACCOUNT
Chen 110 108 FI_ACCOUNT
Sciarra 111 108 FI_ACCOUNT
Urman 112 108 FI_ACCOUNT
Popp 113 108 FI_ACCOUNT
Whalen 200 101 AD_ASST
Mavris 203 101 HR_REP
Baer 204 101 PR_REP
Higgins 205 101 AC_MGR
Gietz 206 205 AC_ACCOUNT
De Haan 102 100 AD_VP
Hunold 103 102 IT_PROG
Ernst 104 103 IT_PROG
Austin 105 103 IT_PROG
Pataballa 106 103 IT_PROG
Lorentz 107 103 IT_PROG
The LEVEL pseudo column and the indentation achieved with it is not so important to me
SQL Server uses common table expressions (WITH statement) to achieve the same (see Recursive Queries Using Common Table Expressions).
This kind of query can also be used in Oracle (starting with 11g if I'm not mistaken).
The resulting query is more complex:
WITH emp(employee_id, manager_id, job_id, last_name, lvl)
AS (
SELECT e.employee_id, e.manager_id, e.job_id, e.last_name, 1 lvl
FROM employees e
WHERE job_id = 'AD_VP'
UNION ALL
SELECT e.employee_id, e.manager_id, e.job_id, e.last_name, r.lvl + 1 lvl
FROM employees e
JOIN emp r ON r.employee_id = e.manager_id
)
SELECT LPAD(' ', 2 * (lvl-1)) || last_name org_chart,
employee_id, manager_id, job_id
FROM emp;
There is an article on the developerworks site Port CONNECT BY to DB2 that does a nice conversion.
Also an interesting article on Explain Extended (Quassnoi's blog) that shows some difference between CONNECT BY and recursive CTE: Adjacency list vs. nested sets: Oracle, being row-based and set-based. He has also a nice article about "SQL Server: are the recursive CTE’s really set-based?". It seems that the "recursive CTE in Oracle is also not set based". I hope this helps with the conversion, recursion in JOOQ and understanding the difference of both implementations of recursion in SQL.
Regards,
JJ.
A trawl through SO showed the following questions and answers that deal with hierarchical queries over a variety of databases. The last of these refers to a MySql resource, that gives a generic SQL approach.
Building a Table Dependency Graph With A Recursive Query
Recursive select in SQL
SQL recursive query
Generating Depth based tree from Hierarchical Data in MySQL (no CTEs)