Average the sal then sort in descending order - sql

I want to list all the employees that make more than the average salary. I'm alittle lost on this one. I need to add up all the salary's then average them out and only display the ones that make more than the average. I need alot of help on this one.
My query that doesnt work
SQL> select empno, ename, job, hiredate, sal, deptno from emp where sal avg(sal);
select empno, ename, job, hiredate, sal, deptno from emp where sal avg(sal)
*
ERROR at line 1:
ORA-00920: invalid relational operator
SQL>
Table
SQL> select empno, ename, job, hiredate, sal, deptno from emp;
EMPNO ENAME JOB HIREDATE SAL DEPTNO
---------- ---------- --------- --------- ---------- ----------
7839 KING PRESIDENT 17-NOV-81 5000 10
7698 BLAKE MANAGER 01-MAY-81 2850 30
7782 CLARK MANAGER 09-JUN-81 2450 10
7566 JONES MANAGER 02-APR-81 2975 20
7654 MARTIN SALESMAN 28-SEP-81 1250 30
7499 ALLEN SALESMAN 20-FEB-81 1600 30
7844 TURNER SALESMAN 08-SEP-81 1500 30
7900 JAMES CLERK 03-DEC-81 950 30
7521 WARD SALESMAN 22-FEB-81 1250 30
7902 FORD ANALYST 03-DEC-81 3000 20
7369 SMITH CLERK 17-DEC-80 800 20
EMPNO ENAME JOB HIREDATE SAL DEPTNO
---------- ---------- --------- --------- ---------- ----------
7788 SCOTT ANALYST 09-DEC-82 3000 20
7876 ADAMS CLERK 12-JAN-83 1100 20
7934 MILLER CLERK 23-JAN-82 1300 10
14 rows selected.

You are getting the invalid relational operator error because you nave omitted the less than
sign in your query. Please learn to read the documentation.
Beyond that, you need to write a sub-query to calculate the average salary. So your query should look like this:
select empno, ename, job, hiredate, sal, deptno
from emp
where sal > (select avg(sal) from emp)
order by sal desc;

Doesn't Oracle support windowed functions? I'm not sure whether you can use a windowed function directly in the WHERE clause, but at least you can derive a table from a query that uses it and then reference the corresponding column in a condition:
SELECT
empno, ename, job, hiredate, sal, deptno
FROM (
SELECT
empno, ename, job, hiredate, sal, deptno
AVG(sal) OVER () AS avgsal
FROM emp
) s
WHERE sal > avgsal

Related

Calculate sum of current row and child rows in hierarchical WITH query in Oracle

I have a Oracle hiƫrarchical query using a WITH clause (common table expression). My goal is to calculate the sum of the salaries of the current row and the child rows. I've come up with the following, using a correlated subquery between a cte that calculates the sum of every row of which the current name is part of one of the paths of the previous cte. Is there a better way to do this?
with managers(empno, mgr, ename, sal, path) as (
select empno, mgr, ename, sal, '/'||empno
from emp
where mgr is null
union all
select e.empno, e.mgr, e.ename, e.sal, m.path||'/'||e.empno
from emp e join managers m
on (e.mgr = m.empno)
)
search depth first by ename set search_order
, totals(empno, total) as (
select e.empno, (select sum(sal)
from managers
where regexp_like( path||'/', '/'||e.empno||'/'))
from emp e)
select empno, mgr, ename, sal, path, total
from managers join totals using (empno)
order by search_order;
Result:
EMPNO MGR ENAME SAL PATH TOTAL
--------- --------- ----------- --------- -------------------- ---------
7839 KING 5000 /7839 29025
7698 7839 BLAKE 2850 /7839/7698 9400
7499 7698 ALLEN 1600 /7839/7698/7499 1600
7900 7698 JAMES 950 /7839/7698/7900 950
7654 7698 MARTIN 1250 /7839/7698/7654 1250
7844 7698 TURNER 1500 /7839/7698/7844 1500
7521 7698 WARD 1250 /7839/7698/7521 1250
7782 7839 CLARK 2450 /7839/7782 3750
7934 7782 MILLER 1300 /7839/7782/7934 1300
7566 7839 JONES 2975 /7839/7566 10875
7902 7566 FORD 3000 /7839/7566/7902 3800
7369 7902 SMITH 800 /7839/7566/7902/7369 800
7788 7566 SCOTT 3000 /7839/7566/7788 4100
7876 7788 ADAMS 1100 /7839/7566/7788/7876 1100
An efficient, pure SQL approach uses a connect by query to assign levels in the hierarchy, followed by a carefully constructed match_recognize operation. Once we have the nodes in the proper hierarchical order and with levels assigned, the descendants of any node can be recognized by a continuous sequence of nodes with levels strictly greater than the level of the starting node. match_recognize can identify such sequences efficiently, in a single pass over the rows.
This approach avoids the much more costly generation of multiple rows, followed by aggregation.
Possible downside: this will only work in Oracle 12.1 and higher - match_recognize does not exist in Oracle 11.2 and earlier. It is also supported only by a limited number of database products (including Oracle db), even though it is part of the SQL Standard, not a proprietary extension.
The solution looks something like this (using the standard scott.emp table):
with
first_pass as (
select empno, ename, mgr, sal, rownum as ord, level as lvl
from scott.emp
start with mgr is null
connect by mgr = prior empno
order siblings by ename -- Not sure why you are doing it this way
)
select empno, ename, mgr, sal, total_sal
from first_pass
match_recognize (
order by ord
measures x.empno as empno, x.ename as ename, x.mgr as mgr, x.sal as sal,
sum(sal) as total_sal
after match skip to next row
pattern ( x y* )
define y as lvl > x.lvl
);
Output:
EMPNO ENAME MGR SAL TOTAL_SAL
---------- ---------- ---------- ---------- ----------
7839 KING 5000 29025
7698 BLAKE 7839 2850 9400
7499 ALLEN 7698 1600 1600
7900 JAMES 7698 950 950
7654 MARTIN 7698 1250 1250
7844 TURNER 7698 1500 1500
7521 WARD 7698 1250 1250
7782 CLARK 7839 2450 3750
7934 MILLER 7782 1300 1300
7566 JONES 7839 2975 10875
7902 FORD 7566 3000 3800
7369 SMITH 7902 800 800
7788 SCOTT 7566 3000 4100
7876 ADAMS 7788 1100 1100
A database approach would be a recursive CTE that first list all emps along their salaries.
In the recursive part you add for each empa new record with the related person (REL_EMPNO) for all their direct and indirect managers.
You get something like this (I'm using only subset of the data)
with dt(EMPNO, REP_EMPNO, REL_ENAME, SAL, MGR) as (
select EMPNO, EMPNO, ENAME, SAL, MGR
from emp
union all
select dt.EMPNO, emp.EMPNO, emp.ENAME, dt.SAL, emp.MGR
from emp
join dt on emp.empno = dt.mgr
)
select
EMPNO, REP_EMPNO, REL_ENAME, SAL
from dt order by 1,2;
EMPNO REP_EMPNO REL_EN SAL
---------- ---------- ------ ----------
7499 7499 ALLEN 1600
7499 7698 BLAKE 1600
7499 7839 KING 1600
7654 7654 MARTIN 1250
7654 7698 BLAKE 1250
7654 7839 KING 1250
7698 7698 BLAKE 2850
7698 7839 KING 2850
7839 7839 KING 5000
7900 7698 BLAKE 950
7900 7839 KING 950
7900 7900 JAMES 950
So for example you see that the salary of ALLEN (1600) is related to him and also to BLAKE and KING.
This is nearly done only remains to group by the related person and sum the salary.
with dt(EMPNO, REP_EMPNO, REL_ENAME, SAL, MGR) as (
select EMPNO, EMPNO, ENAME, SAL, MGR
from emp
union all
select dt.EMPNO, emp.EMPNO, emp.ENAME, dt.SAL, emp.MGR
from emp
join dt on emp.empno = dt.mgr
)
select
REP_EMPNO EMPNO, REL_ENAME ENAME, sum(SAL) SAL
from dt
group by REP_EMPNO, REL_ENAME
order by 1;
EMPNO ENAME SAL
---------- ------ ----------
7499 ALLEN 1600
7654 MARTIN 1250
7698 BLAKE 6650
7839 KING 11650
7900 JAMES 950
If on the contrary you have a more program developer background you may find usefull approach in defining a recursive PL/SQL function, that returns the salary of a person if the person have no childs. Otherwise the function calls itself recursively to add the sum of salaries of all the childs.
create or replace function get_hir_sal(p_empno int) return number as
v_tot_child int;
v_own_sal NUMBER;
v_child_sal NUMBER;
begin
/* check if child noted exists */
select count(*) into v_tot_child
from emp
where mgr = p_empno;
/* own salary */
select sal into v_own_sal
from emp
where empno = p_empno;
if v_tot_child = 0 then
return(v_own_sal);
else
select sum(get_hir_sal(empno))
into v_child_sal
from emp
where mgr = p_empno;
return(v_own_sal + v_child_sal);
end if;
end;
/
You call it with following query to get an identical result
select EMPNO, MGR, ENAME, SAL,
get_hir_sal(EMPNO) total
from emp
order by 1;

ORA-00913: too many values error when I run a query in SQL*Plus

I am trying to get the dname, loc, and count the ename's, plus I want to include the sal from the table. Can someone tell me what I am doing wrong.
Heres my statement with the error I get
SQL> select dname, loc, (select count(ename), sal from emp where DEPTNO = dept.deptno) as Number_of_people from dept;
select dname, loc, (select count(ename), sal from emp where DEPTNO = dept.deptno) as Number_of_people from dept
*
ERROR at line 1:
ORA-00913: too many values
SQL>
Heres my table
SQL> select empno, ename, job, hiredate, sal from emp;
EMPNO ENAME JOB HIREDATE SAL
---------- ---------- --------- --------- ----------
7839 KING PRESIDENT 17-NOV-81 5000
7698 BLAKE MANAGER 01-MAY-81 2850
7782 CLARK MANAGER 09-JUN-81 2450
7566 JONES MANAGER 02-APR-81 2975
7654 MARTIN SALESMAN 28-SEP-81 1250
7499 ALLEN SALESMAN 20-FEB-81 1600
7844 TURNER SALESMAN 08-SEP-81 1500
7900 JAMES CLERK 03-DEC-81 950
7521 WARD SALESMAN 22-FEB-81 1250
7902 FORD ANALYST 03-DEC-81 3000
7369 SMITH CLERK 17-DEC-80 800
EMPNO ENAME JOB HIREDATE SAL
---------- ---------- --------- --------- ----------
7788 SCOTT ANALYST 09-DEC-82 3000
7876 ADAMS CLERK 12-JAN-83 1100
7934 MILLER CLERK 23-JAN-82 1300
14 rows selected.
SQL>
Heres the second table
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
To get number of people and total Salary, try this...
select dept.dname, dept.loc,
count(emp.ename) as Number_of_people, sum(emp.sal) as Total_Salary
from emp
join dept on emp.DEPTNO = dept.deptno
group by dept.dname,dept.loc
You can get other things as well
select dept.dname, dept.loc,
count(emp.ename) as Number_of_people,
sum(emp.sal) as Total_Salary,
avg(emp.sal) as Average_salary,
min(emp.hiredate) as First_dept_hire,
max(emp.hireDate) as Last_dept_hire
from emp
join dept on emp.DEPTNO = dept.deptno
group by dept.dname,dept.loc
The scalar (inline) cursor can only have only one value in its projection.
If you want to have more than one value, use a join and aggregate all the values, as Sparky suggests.

I want to choose hiredate between '20-FEB-81' AND '01-MAY-81'

I want the names of the employees, job, hiredate between '20-FEB-81' AND '01-MAY-81', and in ascending order
query I ran with error
SQL> select ename, job, hiredate where hiredate between '20-FEB-81' AND '01-MAY-81' from emp;
select ename, job, hiredate where hiredate between '20-FEB-81' AND '01-MAY-81' from emp
*
ERROR at line 1:
ORA-00923: FROM keyword not found where expected
SQL>
My table
SQL> select empno, ename, job, hiredate, sal from emp;
EMPNO ENAME JOB HIREDATE SAL
---------- ---------- --------- --------- ----------
7839 KING PRESIDENT 17-NOV-81 5000
7698 BLAKE MANAGER 01-MAY-81 2850
7782 CLARK MANAGER 09-JUN-81 2450
7566 JONES MANAGER 02-APR-81 2975
7654 MARTIN SALESMAN 28-SEP-81 1250
7499 ALLEN SALESMAN 20-FEB-81 1600
7844 TURNER SALESMAN 08-SEP-81 1500
7900 JAMES CLERK 03-DEC-81 950
7521 WARD SALESMAN 22-FEB-81 1250
7902 FORD ANALYST 03-DEC-81 3000
7369 SMITH CLERK 17-DEC-80 800
EMPNO ENAME JOB HIREDATE SAL
---------- ---------- --------- --------- ----------
7788 SCOTT ANALYST 09-DEC-82 3000
7876 ADAMS CLERK 12-JAN-83 1100
7934 MILLER CLERK 23-JAN-82 1300
14 rows selected.
SQL>
The WHERE part goes after the FROM part.
select ename, job, hiredate
from emp
where hiredate between '20-FEB-81' AND '01-MAY-81'
Note that your date literals may not always work if the NLS settings change. It is highly recommended to use to_date() instead.
select ename, job, hiredate
from emp
where hiredate between to_date('20-FEB-81', 'DD-MON-RR') AND to_date('01-MAY-81', 'DD-MON-RR')
But this is still subject to language settings problems, better not use month names at all:
select ename, job, hiredate
from emp
where hiredate between to_date('20-02-81', 'DD-MM-RR') AND to_date('01-05-81', 'DD-MM-RR')

I want to get all jobs that are in dept 30 including the location

I want to get all jobs that are in dept 30 including the location
SQL> select deptno,job from emp where deptno =30 (select loc from dept);
select deptno,job from emp where deptno =30 (select loc from dept)
*
ERROR at line 1:
ORA-00933: SQL command not properly ended
SQL>
Table emp
SQL> select empno, ename, job, hiredate, deptno from emp;
EMPNO ENAME JOB HIREDATE DEPTNO
---------- ---------- --------- --------- ----------
7839 KING PRESIDENT 17-NOV-81 10
7698 BLAKE MANAGER 01-MAY-81 30
7782 CLARK MANAGER 09-JUN-81 10
7566 JONES MANAGER 02-APR-81 20
7654 MARTIN SALESMAN 28-SEP-81 30
7499 ALLEN SALESMAN 20-FEB-81 30
7844 TURNER SALESMAN 08-SEP-81 30
7900 JAMES CLERK 03-DEC-81 30
7521 WARD SALESMAN 22-FEB-81 30
7902 FORD ANALYST 03-DEC-81 20
7369 SMITH CLERK 17-DEC-80 20
EMPNO ENAME JOB HIREDATE DEPTNO
---------- ---------- --------- --------- ----------
7788 SCOTT ANALYST 09-DEC-82 20
7876 ADAMS CLERK 12-JAN-83 20
7934 MILLER CLERK 23-JAN-82 10
14 rows selected.
Table dept
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
You need to join the two tables
SELECT deptno,
e.job,
d.loc
FROM emp e
JOIN dept d USING (deptno)
WHERE deptno = 30
SELECT empno, ename, job, hiredate, emp.deptno, location FROM emp
LEFT JOIN dept ON emp.deptno = dept.deptno
WHERE emp.deptno = 30

Getting data from two tables?

I am trying to get data from two different table and put into on statement but its not working. This is what I am looking to get as a complete statement: I want the query to display the dname, loc, Number of People. I am having a problems with the sub query.
SQL> select dname, loc from dept where ename in (count(ename) AS Number_of_People from emp);
select dname, loc from dept where ename in (count(ename) AS Number_of_People from emp)
*
ERROR at line 1:
ORA-00934: group function is not allowed here
SQL>
Table emp
SQL> select empno, ename, job, hiredate, deptno from emp;
EMPNO ENAME JOB HIREDATE DEPTNO
---------- ---------- --------- --------- ----------
7839 KING PRESIDENT 17-NOV-81 10
7698 BLAKE MANAGER 01-MAY-81 30
7782 CLARK MANAGER 09-JUN-81 10
7566 JONES MANAGER 02-APR-81 20
7654 MARTIN SALESMAN 28-SEP-81 30
7499 ALLEN SALESMAN 20-FEB-81 30
7844 TURNER SALESMAN 08-SEP-81 30
7900 JAMES CLERK 03-DEC-81 30
7521 WARD SALESMAN 22-FEB-81 30
7902 FORD ANALYST 03-DEC-81 20
7369 SMITH CLERK 17-DEC-80 20
EMPNO ENAME JOB HIREDATE DEPTNO
---------- ---------- --------- --------- ----------
7788 SCOTT ANALYST 09-DEC-82 20
7876 ADAMS CLERK 12-JAN-83 20
7934 MILLER CLERK 23-JAN-82 10
14 rows selected.
SQL>
Table dept
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Are you trying to achieve something like this?
select dname, loc, (select count(ename) from emp where DEPTNO = dept.deptno) as Number_of_people
from dept;
I'm not sure what you're trying to do, but something that sticks out is that you probably need to use a select in your subquery. Try:
select dname, loc
from dept
where ename in (select count(ename) AS Number_of_People from emp);
Try this
select dept.dname, dept.loc,count(*)
from emp
join dept on emp.deptNo=dept.deptno
group by dept.dname,dept.loc