Confusion with the behavior of HAVING clause - sql

I am not able to understand the behavior of the following query:
select max(avg(salary)) from employees
group by first_name
having avg(salary) >= max(salary);
It prints out the result as 17000. But if max(salary) is 24000 shouldn't there be empty result. If I replace "">="" with ">" it prints the result as NULL.
And if I replace ">=" with "<", then 13100 is printed in the result.
Below is the employees table:
TJ 2100
Steven 2200
Hazel 2200
James 2400
Ki 2400
Karen 2500
James 2500
Joshua 2500
Peter 2500
Martha 2500
Randall 2500
Guy 2600
Randall 2600
Donald 2600
Douglas 2600
Irene 2700
John 2700
Sigal 2800
Mozhe 2800
Girard 2800
Vance 2800
Shelli 2900
Michael 2900
Timothy 2900
Anthony 3000
Kevin 3000
Alex 3100
Curtis 3100
Jean 3100
Alana 3100
Julia 3200
Stephen 3200
Winston 3200
Samuel 3200
Laura 3300
Jason 3300
Julia 3400
Trenna 3500
Renske 3600
Jennife 3600
Kelly 3800
Britney 3900
Sarah 4000
Alexis 4100
Diana 4200
Nandita 4200
Jennife 4400
David 4800
Valli 4800
Kevin 5800
Bruce 6000
Pat 6000
Sundita 6100
Amit 6200
Charles 6200
Sundar 6400
Shanta 6500
Susan 6500
David 6800
Luis 6900
Oliver 7000
Sarath 7000
Kimbe 7000
Mattea 7200
Eliza 7300
William 7400
Nanette 7500
Louise 7500
Ismael 7700
Jose 7800
Payam 7900
Matthew 8000
Christ 8000
Lindsey 8000
John 8200
Adam 8200
William 8300
Jack 8400
Jonath 8600
Alyssa 8800
Alex 9000
Daniel 9000
Peter 9000
Allan 9000
Patrick 9500
Danie 9500
David 9500
Tayler 9600
Hermann 10000
Harris 10000
Janette 10000
Peter 10000
Clara 10500
Eleni 10500
Gerald 11000
Den 11000
Ellen 11000
Lisa 11500
Alberto 12000
Shelley 12008
Nancy 12008
Michael 13000
Karen 13500
John 14000
Lex 17000
Neena 17000
Steven 24000

You are doing Group By First_name, in your table,
For Neena, max(salary) = 17000 and avg(salary)=17000,
So, >= matches the condition in the query and 17000 is returned.
Where as replacing >= with > evaulated to NULL.
For Steven, Max(salary)=24000, AVG(salary)=(24000+2200)/2=13100
So replacing >= with < returns 13100
Note : Grouping by ColumnName here first_name plays the key role
here. All the aggregate functions in SELECT as well as having clause,
are applied per employee and not on the whole table.

If you want to compare an employee's mean salary (does this really make sense? Surely an employee only has one salary at a point in time?) then this might help
WITH
employees (first_name,salary)
AS
(SELECT 'TJ',2100 FROM dual UNION ALL
SELECT 'Steven',2200 FROM dual UNION ALL
SELECT 'Hazel',2200 FROM dual UNION ALL
SELECT 'James',2400 FROM dual UNION ALL
SELECT 'Ki',2400 FROM dual UNION ALL
SELECT 'Karen',2500 FROM dual UNION ALL
SELECT 'James',2500 FROM dual UNION ALL
SELECT 'Joshua',2500 FROM dual UNION ALL
SELECT 'Peter',2500 FROM dual UNION ALL
SELECT 'Martha',2500 FROM dual
)
SELECT
first_name
,mean_salary_all_emps
,AVG(salary)
FROM
(SELECT
first_name
,salary
,AVG(salary) OVER () mean_salary_all_emps
FROM
employees
)
WHERE 1=1
GROUP BY
first_name
,mean_salary_all_emps
HAVING AVG(salary) > mean_salary_all_emps
;

Related

Find top 2 employees with longest work experience for each salary range

I have two tables.
Salary_Grade
GRADE
Min_Salary
Max_Salary
12
2100
3600
13
3601
4200
14
4201
6000
15
6001
9000
16
9001
30000
Employees
EMPLOYEE_NO
NAME
HIRE_DATE
SALARY
1007
SMITH
2016-02-20 00:00:00.000
15000
2340
JOHNSON
2018-02-07 00:00:00.000
3300
2341
WILLIAMS
2019-10-11 00:00:00.000
3750
2345
BROWN
2018-01-01 00:00:00.000
8925
2355
JONES
2015-07-13 00:00:00.000
8550
3434
GARCIA
2011-08-11 00:00:00.000
7350
4356
MILLER
2013-10-12 00:00:00.000
3750
4455
DAVIS
2000-04-30 00:00:00.000
2850
4456
WILSON
1980-03-03 00:00:00.000
9000
4467
ANDERSON
2001-07-28 00:00:00.000
3900
5643
THOMAS
2011-03-10 00:00:00.000
4800
6538
TAYLOR
2011-08-11 00:00:00.000
9000
6578
MOORE
2020-11-27 00:00:00.000
2400
8900
LEE
2015-03-03 00:00:00.000
4500
My task is to display the two employees with the longest work experience, for each GRADE (the grade is results from the salary range in the SALARY_GRADE and the corresponding SALARY from the EMPLOYEE table)
Expected result:
GRADE
NAME
EXPERIENCE(DAYS)
12
JOHNSON
1359
12
DAVIS
7851
13
MILLER
2938
13
ANDERSON
7397
14
THOMAS
3885
14
LEE
2431
15
WILSON
15214
15
TAYLOR
3731
16
SMITH
2077
I created table EMPLOYEE_SALGRADE with employee id and salary grades connected to them
CREATE TABLE [EMPLOYEE_SALGRADE](
[GRADE_NO] [int] not null,
[EMPLOYEE_NO] [int] not null,
FOREIGN KEY (Grade_NO) REFERENCES Salary_Grade(grade),
FOREIGN KEY (Employee_NO) REFERENCES Employee(Employee_NO))
insert into EMPLOYEE_SALGRADE(GRADE_NO, EMPLOYEE_NO)
SELECT s.grade, e.EMPLOYEE_NO FROM employee as e,salary_grade as s
WHERE e.salary BETWEEN s.min_salary AND s.max_salary
order by e.salary'
and added column Experience to Employee table
Alter table Employee
add Experience as DATEDIFF(dd,Hire_date,getdate())
Now I'm trying with subquery
select s.GRADE, e.NAME, e.Experience
from SALARY_GRADE as S
join EMPLOYEE_SALGRADE AS ES
ON S.GRADE=es.GRADE_NO
join EMPLOYEE as e
on es.Employee_no=e.EMPLOYEE_NO
where Experience in (select top 2(experience) from EMPLOYEE group by Experience)
But this not correct result
I did some research, and the correct answer is:
select * from ( select s.GRADE, e.NAME, e.Experience, row_number() over (partition by s.grade order by e.experience desc) as employee_rank
from SALARY_GRADE as S
join EMPLOYEE_SALGRADE AS ES
ON S.GRADE=es.GRADE_NO
join EMPLOYEE as e
on es.Employee_no=e.EMPLOYEE_NO) ranks
where employee_rank <= 2;

SQL Grouping of Salaries

I have a question to ask and I am supposed to create a query which shows:
MIN(lastname) MAX(firstname) SUM(salary) AVG(salary)
---------------------------------------------------------------
DAVIES TRINA 17500 3500
This was the query/queries I created:
SQL> SELECT MIN(LASTNAME), MAX(FIRSTNAME), SUM(SALARY), AVG(SALARY)
2 FROM EMPLOYEES
3 GROUP BY JOB_ID;
SQL> SELECT MIN(LASTNAME), MAX(FIRSTNAME), SUM(SALARY), AVG(SALARY)
2 FROM EMPLOYEES
3 GROUP BY JOB_ID, MANAGER_ID;
But I get multiple rows shown to me and in the part where DAVIES TRINA was shown the SUM and AVG salary is different and I am unsure how they were grouped.
MIN(LASTNAME) MAX(FIRSTNAME) SUM(SALARY) AVG(SALARY)
---------- ---------- ----------- -----------
ERNST DIANA 10200 5100
HIGGINS SHELLEY 12000 12000
GIETZ WILLIAM 8300 8300
MOURGOS KEVIN 5800 5800
WHALEN JENNIFER 4400 4400
DE HAAN NENA 34000 17000
ZLOTKEY ELENI 10500 10500
HARTSTEIN MICHAEL 13000 13000
KING STEVEN 24000 24000
ABEL KIMBERLEY 26600 8866.66667
FAY PAT 6000 6000
**DAVIES TRINA 11700 2925**
What am I doing wrong?
MORE INFO BELOW:
EMPLOYEE_ID FIRSTNAME LASTNAME JOB_ID SALARY MANAGER_ID DEPARTMENT_ID
100 STEVEN KING AD_PRES 24000 90
101 NENA KOCHAR AD_VP 17000 100 90
102 LEX DE HAAN AD_VP 17000 100 90
103 ALEXANDER HUNOLD IT_PROG 101 60
104 BRUCE ERNST IT_PROG 6000 102 60
107 DIANA LORENTZ IT_PROG 4200 103 60
124 KEVIN MOURGOS ST_MAN 5800 100 50
141 TRINA RAJS ST_CLERK 3500 124 50
142 CURTIS DAVIES ST_CLERK 3100 124 50
143 RANDALL MATOS ST_CLERK 2600 124 50
144 PETER VARGAS ST_CLERK 2500 124 50
EMPLOYEE_ID FIRSTNAME LASTNAME JOB_ID SALARY MANAGER_ID DEPARTMENT_ID
149 ELENI ZLOTKEY SA_MAN 10500 100 80
174 ELLEN ABEL SA_REP 11000 149 50
176 JONATHAN TAYLOR SA_REP 8600 149 80
178 KIMBERLEY GRANT SA_REP 7000 149
200 JENNIFER WHALEN AD_ASST 4400 101 10
201 MICHAEL HARTSTEIN MK_MAN 13000 100 20
202 PAT FAY MK_REP 6000 201 20
205 SHELLEY HIGGINS AC_MGR 12000 101 110
206 WILLIAM GIETZ AC_ACCOUNT 8300 205 110
If you provide the proper data that is, the data before running the query/queries and also tell why it should be 17500(sum) and 3500(avg) then you have more chances to get answers.
In the 1st query you are grouping based on JOB_ID. That means for all similar JOB_IDs (in case of SUM(SALARY)) it will select the the values of SALARY column and SUM the values for each different JOB_ID.
Example:
JOB_ID SALARY
1 200
2 300
1 150
2 100
3 270
If you run the following query:
Select JOB_ID,SUM(SALARY) FROM Table GROUP BY JOB_ID
Output:
JOB_ID SALARY
1 350
2 400
3 270

How to display department (once) and list of all employees working in that department?

For the schema epartments(department_id, department_name) employees(last_name, department_id, salary)
I want to display the department_id, department_name, count(employees),avg(salary),last_name,salary
I have tried using the following query
SELECT d1.department_id,d1.department_name,d1."count",d1."avg",e.last_name,e.salary
FROM employees e
INNER JOIN (SELECT d.department_id,d.department_name,count(e.last_name) AS "count",round(avg(e.salary),2) AS "avg"
FROM employees e,departments d
WHERE e.department_id=d.department_id
GROUP BY d.department_id,d.department_name) d1
ON e.department_id=d1.department_id;
While it displays the correct output it is not in the format I want.
The above query gives output as
90 Executive 3 19333.33 King 24000
90 Executive 3 19333.33 Kochhar 17000
90 Executive 3 19333.33 De Haan 17000
60 IT 3 6400 Hunold 9000
60 IT 3 6400 Ernst 6000
60 IT 3 6400 Lorentz 4200
50 Shipping 5 3500 Mourgos 5800
While it should be like
90 Executive 3 19333.33 King 24000
Kochhar 17000
De Haan 17000
60 IT 3 6400 Hunold 9000
Ernst 6000
Lorentz 4200
50 Shipping 5 3500 Mourgos 5800
If you really want to show something like '' instead of those data in your query, I think you can use ROW_NUMBER() for that like this:
CASE
WHEN (ROW_NUMBER() OVER (PARTITION BY d1.department_id
ORDER BY d1.department_id)) = 1 THEN
d1.department_id
ELSE
Null
END
for columns: d1.department_id,d1.department_name,d1."count",d1."avg".

query on hr schema

I am new to SQL and am practicing on the HR schema available on Oracle 10g XE.
This is the question:
Write a query to select the name, job, and salary and department number of all employees except Sales Rep from department number 80.
My query is this:
select first_name||' '||last_name "Employee Name"
, job_id, salary, department_id
from employees
where not( job_id='SA_REP' and department_id=80 )
order by department_id;
Output:
Employee Name JOB_ID SALARY DEPARTMENT_ID
---------------------------------------------- ---------- ---------- -------------
Jennifer Whalen AD_ASST 4400 10
Michael Hartstein MK_MAN 13000 20
Pat Fay MK_REP 6000 20
Den Raphaely PU_MAN 11000 30
Alexander Khoo PU_CLERK 3100 30
Shelli Baida PU_CLERK 2900 30
Sigal Tobias PU_CLERK 2800 30
Guy Himuro PU_CLERK 2600 30
Karen Colmenares PU_CLERK 2500 30
Susan Mavris HR_REP 6500 40
Matthew Weiss ST_MAN 8000 50
Adam Fripp ST_MAN 8200 50
Payam Kaufling ST_MAN 7900 50
Shanta Vollman ST_MAN 6500 50
Kevin Mourgos ST_MAN 5800 50
Julia Nayer ST_CLERK 3200 50
Irene Mikkilineni ST_CLERK 2700 50
James Landry ST_CLERK 2400 50
Steven Markle ST_CLERK 2200 50
Laura Bissot ST_CLERK 3300 50
Mozhe Atkinson ST_CLERK 2800 50
James Marlow ST_CLERK 2500 50
TJ Olson ST_CLERK 2100 50
Jason Mallin ST_CLERK 3300 50
Michael Rogers ST_CLERK 2900 50
Ki Gee ST_CLERK 2400 50
Hazel Philtanker ST_CLERK 2200 50
Renske Ladwig ST_CLERK 3600 50
Stephen Stiles ST_CLERK 3200 50
John Seo ST_CLERK 2700 50
Joshua Patel ST_CLERK 2500 50
Trenna Rajs ST_CLERK 3500 50
Curtis Davies ST_CLERK 3100 50
Randall Matos ST_CLERK 2600 50
Peter Vargas ST_CLERK 2500 50
Winston Taylor SH_CLERK 3200 50
Jean Fleaur SH_CLERK 3100 50
Martha Sullivan SH_CLERK 2500 50
Girard Geoni SH_CLERK 2800 50
Nandita Sarchand SH_CLERK 4200 50
Alexis Bull SH_CLERK 4100 50
Julia Dellinger SH_CLERK 3400 50
Anthony Cabrio SH_CLERK 3000 50
Kelly Chung SH_CLERK 3800 50
Jennifer Dilly SH_CLERK 3600 50
Timothy Gates SH_CLERK 2900 50
Randall Perkins SH_CLERK 2500 50
Sarah Bell SH_CLERK 4000 50
Britney Everett SH_CLERK 3900 50
Samuel McCain SH_CLERK 3200 50
Vance Jones SH_CLERK 2800 50
Alana Walsh SH_CLERK 3100 50
Kevin Feeney SH_CLERK 3000 50
Donald OConnell SH_CLERK 2600 50
Douglas Grant SH_CLERK 2600 50
Alexander Hunold IT_PROG 9000 60
Bruce Ernst IT_PROG 6000 60
David Austin IT_PROG 4800 60
Valli Pataballa IT_PROG 4800 60
Diana Lorentz IT_PROG 4200 60
Hermann Baer PR_REP 10000 70
John Russell SA_MAN 14000 80
Karen Partners SA_MAN 13500 80
Alberto Errazuriz SA_MAN 12000 80
Gerald Cambrault SA_MAN 11000 80
Eleni Zlotkey SA_MAN 10500 80
Steven King AD_PRES 24000 90
Neena Kochhar AD_VP 17000 90
Lex De Haan AD_VP 17000 90
Nancy Greenberg FI_MGR 12000 100
Daniel Faviet FI_ACCOUNT 9000 100
John Chen FI_ACCOUNT 8200 100
Ismael Sciarra FI_ACCOUNT 7700 100
Luis Popp FI_ACCOUNT 6900 100
Jose Manuel Urman FI_ACCOUNT 7800 100
Shelley Higgins AC_MGR 12000 110
William Gietz AC_ACCOUNT 8300 110
77 rows selected.
I am not however getting the desired output,as the output is not displaying this row:
Employee Name JOB_ID SALARY DEPARTMENT_ID
---------------------------------------------- ---------- ---------- -------------
Kimberely Grant SA_REP 7000
This record does not have a department number, but it should also be displayed in the output along with the other 77 rows. Can anyone please point out where i am going wrong with the query?
So the goal is to get a list of all employees except those who are sales reps and in department 80? That is, you still want employees in department 80 (as well as all other departments) unless they are sales reps, and sales reps unless they are in department 80?
In Oracle, the comparison of a NULL value with anything else returns NULL rather than TRUE or FALSE. So you'll want to either: (a) add an explicit IS NULL condition (or IS NOT NULL, depending on your query parameters) or (b) use the COALESCE() (or NVL()) function. Something like this:
SELECT first_name || ' ' || last_name "Employee Name"
, job_id, salary, department_id
FROM employees
WHERE job_id != 'SA_REP'
OR department_id IS NULL
OR department_id != 80
ORDER BY department_id;
or:
SELECT first_name || ' ' || last_name "Employee Name"
, job_id, salary, department_id
FROM employees
WHERE job_id != 'SA_REP'
OR COALESCE(department_id, 0) != 80
ORDER BY department_id;
Thanks to #DavidFaber for confirming my original thought. The not that you have in your where clause isn't playing nice with the department_id = 80 portion, because of the empty value for Kimbereley Grant in department_id. Basically, the "not (job_id='SA_REP' and department_id=80)" becomes "not job_id='SA_REP' or not department_id=80", hence "job_id != 'SA_REP' or department_id != 80".
Kimberely Grant fails the first check (her job_id is in fact SA_REP) and the second check should result in essentially, "Unknown" (her department_id is not 80, but it's not not 80 either, it's NULL). As a result, you have false or "Unknown" which becomes false.
The fix is what #Ponder suggested. Add a null check on department_id:
not (job_id='SA_REP' and department_id=80 and department_id is not null)
which is equivalent to:
not (job_id='SA_REP' and department_id=80) or department_id is null
Thanks every one. I am able to get the required output now with this query:
SELECT first_name || ' ' || last_name "Employee Name"
, job_id, salary, department_id
FROM employees
WHERE job_id != 'SA_REP'
OR department_id IS NULL
OR department_id != 80
ORDER BY department_id;
Many Thanks again.Cheers.

Finding user names with particular characters in their last name

In Oracle SQL, I wish to find all user names,whose Last name contains some specific Characters like ('Z','X','D','F') Or contains Some Range of Characters from 'A-F'.
I tried this:
SELECT user_last_name
FROM userbase
WHERE user_last_name LIKE '%A%' OR user_last_name LIKE '%F%' .
So How can I go about it.
And I written facebook in question although its not directly related to it,because,its the example given by my sir.
UPDATE: I tried the above code,it works with so many OR's, but How can we define some range to search for like IN('Z','X','D','F') or IN ('A-F')
If you are using a recent version of Oracle, you should be able to use regular expressions
SQL> ed
Wrote file afiedt.buf
1 select first_name, last_name
2 from employees
3* where regexp_like( last_name, 'Z|X|D|[A-F]' )
SQL> /
FIRST_NAME LAST_NAME
-------------------- -------------------------
Ellen Abel
Sundar Ande
Mozhe Atkinson
David Austin
Hermann Baer
Shelli Baida
Amit Banda
Elizabeth Bates
Sarah Bell
David Bernstein
Laura Bissot
Harrison Bloom
Alexis Bull
Anthony Cabrio
Gerald Cambrault
Nanette Cambrault
John Chen
Kelly Chung
Karen Colmenares
Curtis Davies
Lex De Haan
Julia Dellinger
Jennifer Dilly
Louise Doran
Bruce Ernst
Alberto Errazuriz
Britney Everett
Daniel Faviet
Pat Fay
Kevin Feeney
Jean Fleaur
Tayler Fox
Adam Fripp
Samuel McCain
Allan McEwen
Donald OConnell
Eleni Zlotkey
37 rows selected.
If you want the search to be case-insensitive
SQL> ed
Wrote file afiedt.buf
1 select first_name, last_name
2 from employees
3* where regexp_like( last_name, 'Z|X|D|[A-F]', 'i' )
SQL> /
FIRST_NAME LAST_NAME
-------------------- -------------------------
Ellen Abel
Sundar Ande
Mozhe Atkinson
David Austin
Hermann Baer
Shelli Baida
Amit Banda
Elizabeth Bates
Sarah Bell
David Bernstein
Laura Bissot
Harrison Bloom
Alexis Bull
Anthony Cabrio
Gerald Cambrault
Nanette Cambrault
John Chen
Kelly Chung
Karen Colmenares
Curtis Davies
Lex De Haan
<<snip>>
Matthew Weiss
Jennifer Whalen
Eleni Zlotkey
93 rows selected.
You can use INSTR to test whether characters exist in your field, like so:
SELECT
user_last_name
FROM
userbase
WHERE 0 < INSTR(user_last_name,'A')
or 0 < INSTR(user_last_name,'B')
...repeat for each character you want to test for...