About "group function is not allowed here" - sql

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.

Related

What's the meaning of this Oracle SQL code?

I've been learning Oracle SQL recently and I came across the following code:
SELECT *
FROM employees outer_emps
WHERE (2-1) = (
SELECT COUNT(*)
FROM employees inner_emps
WHERE inner_emps.salary > outer_emps.salary
);
Could someone please help me understand the syntax?
It returns employees whose salary is the 2nd highest.
For example:
SQL> select ename, sal from emp order by sal;
ENAME SAL
---------- ----------
SMITH 840
JAMES 950
ADAMS 1100
WARD 1250
MARTIN 1250
MILLER 1300
TURNER 1500
ALLEN 1600
CLARK 2450
BLAKE 2850
JONES 2975
SCOTT 3000 --> SCOTT and FORD have the 2nd highest salary
FORD 3000 -->
KING 5000 --> KING has the highest salary
14 rows selected.
SQL> select ename, sal
2 from emp outer_emps
3 where 1 = (select count(*) from emp inner_emps
4 where inner_emps.sal > outer_emps.sal);
ENAME SAL
---------- ----------
SCOTT 3000
FORD 3000
SQL>
If you used <, you'd get the 2nd lowest salary (that's James because SMITH's salary is 840 - the lowest - and JAMES follows with 950):
SQL> select ename, sal
2 from emp outer_emps
3 where 1 = (select count(*) from emp inner_emps
4 where inner_emps.sal < outer_emps.sal);
ENAME SAL
---------- ----------
JAMES 950
SQL>
I'd probably do it as follows; that's easier to understand:
SQL> with temp as
2 (select ename, sal,
3 rank() over (order by sal desc) rnk
4 from emp
5 )
6 select ename,sal from temp
7 where rnk = 2;
ENAME SAL
---------- ----------
SCOTT 3000
FORD 3000
SQL>

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.

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.

Deleting duplicate records with ROWNUM?

I know how to delete duplicate records with ROWID.
Please guide me to delete duplicate records with ROWNUM in Oracle.
That just won't work. From documentation:
For each row returned by a query, the ROWNUM pseudocolumn returns a number indicating the order in which Oracle selects the row from a table or set of joined rows. The first row selected has a ROWNUM of 1, the second has 2, and so on.
Its value is set at the moment you run the query and can be changed, depending on how you fetch data (different ORDER BY will produce different ROWNUM value for the same row). As it is sequential, you can't set "groups" of ROWNUM values (for example, so that it goes from 1, 2, 3 for one set, then 1, 2, 3, 4, 5 for another - you'll always get 1, 2, 3, 4, 5, 6, 7, 8).
If you planned to do something like this:
SQL> create table test as
2 select e.empno, e.deptno, d.dname, e.ename
3 from emp e join dept d on e.deptno = d.deptno;
Table created.
SQL> select * from test order by deptno;
EMPNO DEPTNO DNAME ENAME
---------- ---------- -------------- ----------
7782 10 ACCOUNTING CLARK
7839 10 ACCOUNTING KING
7934 10 ACCOUNTING MILLER
7369 20 RESEARCH SMITH
7902 20 RESEARCH FORD
7566 20 RESEARCH JONES
7900 30 SALES JAMES
7844 30 SALES TURNER
7654 30 SALES MARTIN
7521 30 SALES WARD
7499 30 SALES ALLEN
7698 30 SALES BLAKE
12 rows selected.
SQL> delete from test t
2 where t.empno in (select a.empno
3 from (select t1.empno, t1.deptno, t1.dname, rownum rn
4 from test t1
5 ) a
6 where a.rn > 1
7 );
11 rows deleted.
As you can see, all rows (but one) are duplicates. Here's why:
SQL> rollback;
Rollback complete.
SQL> select a.deptno, a.empno, a.rn, a.rn1
2 from (select t1.empno, t1.deptno, t1.dname, rownum rn,
3 row_number() over (partition by t1.deptno order by null) rn1
4 from test t1
5 ) a;
DEPTNO EMPNO RN RN1
---------- ---------- ---------- ----------
10 7782 2 1
10 7839 1 2
10 7934 3 3
20 7369 5 1
20 7902 4 2
20 7566 6 3
30 7900 7 1
30 7844 8 2
30 7654 9 3
30 7521 10 4
30 7499 11 5
30 7698 12 6
12 rows selected.
See? RN (ROWNUM) has all values from 1, 2, ..., 12. RN1 (ROW_NUMBER, which allows us to set partitions) does the job correctly. So, if you used RN1 instead of RN, it would work:
SQL> delete from test t
2 where t.empno in (select a.empno
3 from (select t1.empno, t1.deptno, t1.dname, rownum rn,
4 row_number() over (partition by t1.deptno order by null) rn1
5 from test t1
6 ) a
7 where a.rn1 > 1
8 );
9 rows deleted.
SQL> select * From test;
EMPNO DEPTNO DNAME ENAME
---------- ---------- -------------- ----------
7782 10 ACCOUNTING CLARK
7369 20 RESEARCH SMITH
7900 30 SALES JAMES
SQL>
[EDIT: deleting duplicates #2]
Here's another example which shows how/what to do if you want to delete duplicates. It is based on the "ROWID technique" (although there are another ones too).
Back to the table we've been working with. Suppose that we want to keep only one distinct job per department:
SQL> select deptno, job, dname, empno, ename
2 from test
3 order by deptno, job;
DEPTNO JOB DNAME EMPNO ENAME
---------- --------- -------------- ---------- ----------
10 CLERK ACCOUNTING 7934 MILLER
10 MANAGER ACCOUNTING 7782 CLARK
10 PRESIDENT ACCOUNTING 7839 KING
20 ANALYST RESEARCH 7902 FORD
20 CLERK RESEARCH 7369 SMITH
20 MANAGER RESEARCH 7566 JONES
30 CLERK SALES 7900 JAMES
30 MANAGER SALES 7698 BLAKE
30 SALESMAN SALES 7844 TURNER -- leave
30 SALESMAN SALES 7654 MARTIN -- only
30 SALESMAN SALES 7521 WARD -- one
30 SALESMAN SALES 7499 ALLEN -- salesman
12 rows selected.
in department 10, there are no duplicates - 3 employees, each of them doing their own job
the same goes for department 20
however, in department 30, there are 4 SALESMEN and we want to keep only one - another ones are duplicates
It means that you have to take both columns - DEPTNO and JOB - into account when deleting rows. Let's do that:
SQL> delete from test a
2 where rowid > (select min(rowid)
3 from test b
4 where a.deptno = b.deptno --> take both DEPTNO ...
5 and a.job = b.job --> and JOB into account
6 );
3 rows deleted.
The result: departments 10 and 20 didn't change, but in department 30 now we have only one salesman, just as we wanted:
SQL> select deptno, job, dname, empno, ename
2 from test
3 order by deptno, job;
DEPTNO JOB DNAME EMPNO ENAME
---------- --------- -------------- ---------- ----------
10 CLERK ACCOUNTING 7934 MILLER
10 MANAGER ACCOUNTING 7782 CLARK
10 PRESIDENT ACCOUNTING 7839 KING
20 ANALYST RESEARCH 7902 FORD
20 CLERK RESEARCH 7369 SMITH
20 MANAGER RESEARCH 7566 JONES
30 CLERK SALES 7900 JAMES
30 MANAGER SALES 7698 BLAKE
30 SALESMAN SALES 7844 TURNER
9 rows selected.
SQL>

About sql subquery

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)
)