About sql subquery - sql

What to do when we want to select salary of a employee greater than many (lets say 12) employees's salary from a table. I know that we'll have to use a subquery but writing it as :-
Select ename,salary
from emp
where salary>( select salary
from emp
where ename='A'||ename='B'.....)
it could be written like that but its not a good approach. Please suggest some useful query for it.

If you know the 12 employees, I think you want to write the query as:
Select ename,salary
from emp
where salary> (select max(salary)
from emp
where ename in ('A', 'B', . . . )
)
IN is much more convenient than a bunch of or statements. And, the subquery needs to return one value, the maximum salary.

Select ename,salary
from emp
where salary > (
select salary
from
(
select
salary,
rownum as rn
from emp
order by salary
)
where rn = 12
)

This is not exact code that you may use, but it should help you.
You can use RANK() function.
Example from article at oracle-base.com:
SELECT empno,
deptno,
sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal) "rank"
FROM emp;
EMPNO DEPTNO SAL rank
---------- ---------- ---------- ----------
7934 10 1300 1
7782 10 2450 2
7839 10 5000 3
7369 20 800 1
7876 20 1100 2
7566 20 2975 3
7788 20 3000 4
7902 20 3000 4
7900 30 950 1
7654 30 1250 2
7521 30 1250 2
7844 30 1500 4
7499 30 1600 5
7698 30 2850 6

I can see two different interpretations of your requirement.
1. What employees earn more than 12 other (random) employees
and
2. What employees earn more than 12 specific employees
This query solves the first requirement, although it will become slow as hell on larger datasets.
select *
from emp a
where 12 = (select count(*)
from emp b
where b.salary < a.salary);
This query solves the second requirement
select *
from emp
where salary > all(select salary
from emp
where emp_id in(1,2,3,4,5)
)

Related

What is the best way(in case of performance) to find out the user details with maximum salary?

We have user details with a column of salary also, how can we print the user details with the maximum salary, I don't want to use the Subquery, and yeah how subquery will reduce the performance.
I know this query is wrong but I want something like this:
select User_name, user_id
from dual where salary=Max(salary);
Analytic functions help.
Using a CTE (which is kind of a subquery; don't be afraid of it, it doesn't bite and won't affect performance), query might look like this (based on sample Scott's schema):
SQL> select ename, sal from emp order by sal desc;
ENAME SAL
---------- ----------
KING 5000 --> this is the highest salary
FORD 3000 --> FORD and SCOTT share the 2nd place
SCOTT 3000
JONES 2975
BLAKE 2850
CLARK 2450
ALLEN 1600
TURNER 1500
MILLER 1300
WARD 1250 --> WARD and MARTIN are then 9th
MARTIN 1250
ADAMS 1100
JAMES 950
SMITH 800
14 rows selected.
Query is then
SQL> with temp as
2 (select ename,
3 dense_rank() over (order by sal desc) rnk
4 from emp
5 )
6 select ename
7 from temp
8 where rnk = 1;
ENAME
----------
KING
SQL>
Why dense_rank? Because two (or more) employees can have the same salary so they "rank" the same. For example, if you want to know whose salary is ranked as 9th, you'd
SQL> l8
8* where rnk = 1
SQL> c/1/9
8* where rnk = 9
SQL> /
ENAME
----------
WARD
MARTIN
SQL>
Query you suggested (although wrong, but - I got the idea) looks like this:
SQL> select ename
2 from emp
3 where sal = (select max(sal) from emp);
ENAME
----------
KING
SQL>
And yes, it affects performance because you're fetching data from the same emp table twice: once to find the max salary (in a subquery), and then in the main query to find who it belongs to.

About "group function is not allowed here"

I have a table with job, salary and date columns. I am writing the following query in PL/SQL, but I am getting an error
group function is not allowed here
delete employees where date = '06-05-2020 'and avg (salary)> 5500;
How can I solve this problem?
Your query makes no sense (to me, at least). What does that average salary represent? Whose average salary?
Here's an example based on Scott's EMP table; I'm going to delete employees who were hired on 3th of December 1981 and work in department whose employees' average salary is higher than 2000.
Sample data:
SQL> select deptno, ename, sal, hiredate from emp order by deptno, ename;
DEPTNO ENAME SAL HIREDATE
---------- ---------- ---------- ----------
20 ADAMS 1100 12.01.1983
20 FORD 3000 03.12.1981 --> this
20 JONES 2975 02.04.1981
20 SCOTT 3000 09.12.1982
20 SMITH 800 17.12.1980
30 ALLEN 1600 20.02.1981
30 BLAKE 2850 01.05.1981
30 JAMES 950 03.12.1981 --> this
30 MARTIN 1250 28.09.1981
30 TURNER 1500 08.09.1981
30 WARD 1250 22.02.1981
11 rows selected.
Averege salaries per department:
SQL> select deptno, avg(sal) avg_salary
2 from emp
3 group by deptno
4 order by avg_salary desc;
DEPTNO AVG_SALARY
---------- ----------
20 2175 --> higher than 2000
30 1566,66667
So: I'm looking for employees who work in department 20 (as only that department has average salaries higher than 2000) and who were hired on 03.12.1981 (James and Ford, but only Ford works in department 20):
SQL> delete from emp
2 where hiredate = date '1981-12-03'
3 and deptno in (select deptno
4 from emp
5 group by deptno
6 having avg(sal) > 2000
7 );
1 row deleted.
Is Ford still in there?
SQL> select * From emp where ename = 'FORD';
no rows selected
SQL>
Nope, deleted.
Now, your turn.

How to use rank to get the latest bonus record

currently I am using the below query to get the previous year’s bonus amount for the employees. But I am facing some issues, so I am trying to get the latest element entry value(screen entry value) for the element ‘xyz bonus’ using the RANK() function. Please help. Thanks.
Select
Pam.assignment_number,
Peev.screen_entry_value as bonus_amount
From
Per_all_assignments_m Pam,
Pay_element_entries_f peef,
Pay_element_types_tl petl,
Pay_element_entry_values_f peev
Where
Pam.Person_id=peef.person_id
and peef.element_type_id = petl. element_type_id
And peef.element_entry_id = peev. element_entry_id
And petl.language=‘US’
And to_char(peef.effective_start_date,’yyyy’)=(to_char(sysdate,’yyyy’)-1)
And to_char(peev.effective_start_date,’yyyy’)=(to_char(sysdate,’yyyy’)-1)
And petl.element_name = ‘xyz bonus’
As I don't have your tables, I'm using Scott's sample EMP table.
In there, rows sorted by salaries per department look like this:
SQL> select deptno,
2 ename,
3 sal,
4 rank() over (partition by deptno order by sal desc) rn
5 from emp
6 order by deptno,
7 sal desc;
DEPTNO ENAME SAL RN
---------- ---------- ---------- ----------
10 KING 10000 1
10 CLARK 2450 2
10 MILLER 1300 3
20 SCOTT 3000 1
20 FORD 3000 1
20 JONES 2975 3
20 ADAMS 1100 4
20 SMITH 920 5
30 BLAKE 2850 1
30 ALLEN 1600 2
30 TURNER 1500 3
30 MARTIN 1250 4
30 WARD 1250 4
30 JAMES 950 6
14 rows selected.
SQL>
If you want to fetch the highest salary per department, you'd then
SQL> select deptno, ename, sal
2 from (select deptno,
3 ename,
4 sal,
5 rank() over (partition by deptno order by sal desc) rn
6 from emp
7 )
8 where rn = 1;
DEPTNO ENAME SAL
---------- ---------- ----------
10 KING 10000
20 SCOTT 3000
20 FORD 3000
30 BLAKE 2850
SQL>
I guess that's what you are looking for.
Your query might then look like this:
Select
Pam.assignment_number,
Peev.screen_entry_value as bonus_amount,
rank() over (partition by pam.assignment_number order by peev.screen_entry_value desc) rn
From
...
Now, use it as an inline view (or a CTE) and fetch desired values.
If that's not what you are looking for, please, post sample data and desired result.

SQL how to order on highest rank in group, with total group

dataset taken from: Tim Hall's Oracle-Base rank/partition documentation Original assignment was to rank the salaries within a department. The MYRANK column is syntactic and introduced with
RANK() OVER (PARTITION BY deptno ORDER BY sal) AS myrank
But now I want to sort on the highest salary followed with all records within the same department. Then the second highest salary again followed with all the records in the same department. The order of dept is a kind of coincidental, it happens to be aligned with the order of highest salary per departnement.
I think I can solve this when the rank() is substituted for a max() like:
MAX() OVER (PARTITION BY DEPTNO ORDER BY SAL) AS MAX
and than a order by MAX, DEPTNO, but this fails with: invalid number of arguments
EMPNO DEPTNO SAL MYRANK
---------- ---------- ---------- ----------
7839 10 5000 3
7782 10 2450 2
7934 10 1300 1
7788 20 3000 4
7902 20 3000 4
7566 20 2975 3
7876 20 1100 2
7369 20 800 1
7698 30 2850 6
7499 30 1600 5
7844 30 1500 4
7654 30 1250 2
7521 30 1250 2
7900 30 950 1
You can put analytic functions in the order by, so you can do:
order by max(sal) over (partition by deptno) desc,
deptno,
sal desc
Notice that this has three keys in the order by. The deptno is needed in case two departments have the same highest salary.

select rows between two values in Oracle 11g

This is a common question I saw in many places, but don't know yet it possible or not. I'm trying to select rows between 2 and 5 in following way using oracle sql developer tool.
As of result this query, this should select 3rd and 4th query according to below query
SELECT * FROM MyTable
WHERE ROWNUM > 2 AND ROWNUM < 5
but it's not selecting the 3rd and 4th rows,
Then I tried the following query
SELECT * FROM MyTable
WHERE RN BETWEEN 2 AND 5
This also syntactically and progrmatically correct, but not selecting the exact columns.
Use a subquery:
SELECT t.*
FROM (SELECT t.*, ROWNUM as rn
FROM MyTable t
) t
WHERE rn > 2 AND rn < 5;
Note that tables represent unordered sets. There is no such thing as a first or second row. You should have an ORDER BY clause to specify the ordering.
The reason that your version doesn't work is that rownum starts at 1 when the first row is put into the result set. If no row is put in, the value never increments. So, it never hits 2 or 3.
I should also note that between in SQL is inclusive. So >= and <= are more appropriate.
EDIT:
I should note that Oracle 12+ supports FETCH/OFFSET:
select t.*
from mytable t
offset 2 -- start on the third row
fetch first 2 rows only -- fetch two rows in total
An order by is still recommended in this case.
A little bit of analytics.
Salaries in the EMP table, sorted by $$$, look like this:
SQL> select ename, sal
2 from emp
3 order by sal;
ENAME SAL
---------- ----------
SMITH 800
JAMES 950 2 you want to return James ...
WARD 1250 3
MARTIN 1250 4
MILLER 1300 5 ... to Miller
TURNER 1500
ALLEN 1600
CLARK 2450
BLAKE 2850
JONES 2975
FORD 3000
KING 5000
12 rows selected.
SQL>
If you do it as follows, you'd get what you wanted:
SQL> select ename, sal, rn
2 from (select ename, sal, row_number() over (order by sal) rn
3 from emp
4 )
5 where rn between 2 and 5;
ENAME SAL RN
---------- ---------- ----------
JAMES 950 2
WARD 1250 3
MARTIN 1250 4
MILLER 1300 5
SQL>
However, as you can see, Ward and Martin earn the same $1250. So, should we count them as having the same salary and include Turner into the list, or not? Yet two another analytic functions might help you decide: RANK and DENSE_RANK:
SQL> select ename, sal,
2 row_number() over (order by sal) rn,
3 rank() over (order by sal) rnk,
4 dense_rank() over (order by sal) drnk
5 from emp
6 order by sal;
ENAME SAL RN RNK DRNK
---------- ---------- ---------- ---------- ----------
SMITH 800 1 1 1
JAMES 950 2 2 2 2nd isn't questionable, but ...
WARD 1250 3 3 3
MARTIN 1250 4 3 3
MILLER 1300 5 5 4 ... which one is 5th? Miller (RN and RNK), ...
TURNER 1500 6 6 5 ... or Turner (DRNK column)?
ALLEN 1600 7 7 6
CLARK 2450 8 8 7
BLAKE 2850 9 9 8
JONES 2975 10 10 9
FORD 3000 11 11 10
KING 5000 12 12 11
12 rows selected.
SQL>
To be fair, DENSE_RANK is probably the best option in such cases:
SQL> select ename, sal, drnk
2 from (select ename, sal, dense_rank() over (order by sal) drnk
3 from emp
4 )
5 where drnk between 2 and 5;
ENAME SAL DRNK
---------- ---------- ----------
JAMES 950 2
WARD 1250 3
MARTIN 1250 3
MILLER 1300 4
TURNER 1500 5
SQL>
Now you have several options; pick the one that suits you best.