Employees with the lower salary than the average in their dept - oracle

I got two challenges on my college exam to do.
The first one is to Show data for employees (name, salary, and department) who are paid less than the average salary for their department.
I tried this:
SELECT ename, sal, deptno
FROM Emp
WHERE sal < (SELECT AVG(sal) FROM EMP)
GROUP BY deptno;
But i get the ORA-00979: not a GROUP BY expression error.
The other one i didn't started but is quite the same, Show the data of employees (name, commission and department) who receive commission greater than the average commission in their department.
ps: I'm starting with SQL so the topic isn't very deep yet.

You can use a window function for this
SELECT ename, sal, deptno
FROM (
SELECT *,
AVG(sal) OVER (PARTITION BY deptno) AS AvgPerDept
FROM Emp
) AS Emp
WHERE sal < AvgPerDept

Your attempt to the first one is not correct
The expression SELECT AVG(sal) FROM EMP will give the average salary of everyone, regardless which department they're in.
To get the average for each deparment, you will need to GROUP BY dept_no
SELECT Emp.* , dept_salary.average_sal
FROM Emp
INNER JOIN
-- join with an average salary for each department
(
SELECT deptno, AVG(sal) as average_sal
FROM Emp
GROUP BY deptno
) dept_salary
ON Emp.deptno = dept_salary.deptno
WHERE Emp.dept_no = dept_salary.deptno AND Emp.sal < dept_salary.average_sal;

Related

Finding list of max average salary, department wise, whilst displaying both max average salary and department_id

When I try it without displaying department_id it works fine as :
SQL> SELECT MAX(AVG(SALARY)) FROM EMPLOYEE GROUP BY DEPARTMENT_ID;
MAX(AVG(SALARY))
----------------
800000
But when I want to display the department_id's too, it gives me error as follows:
SQL> SELECT DEPARTMENT_ID, MAX(AVG(SALARY)) FROM EMPLOYEE GROUP BY DEPARTMENT_ID;
SELECT DEPARTMENT_ID, MAX(AVG(SALARY)) FROM EMPLOYEE GROUP BY DEPARTMENT_ID
*
ERROR at line 1:
ORA-00937: not a single-group group function
Is there any explanation for this? What am I doing wrong? I went through answers of previous questions like this and tried their solutions but got same or some other error. Any help would be appreciated.
I suggest you use
SELECT department_id, avg_salary
FROM ( SELECT DEPARTMENT_ID,
AVG (SALARY) avg_salary,
RANK () OVER (ORDER BY AVG (salary) DESC) rnk
FROM EMPLOYEE
GROUP BY DEPARTMENT_ID)
WHERE rnk = 1;
i.e.
use your first query as a "source"
additionally, rank average salaries in descending order (using the rank analytic function)
select row which ranks as highest

Hide column from Query

I don't want rank column but still, want the data in the same format by applying dense rank.
select ename,position,deptno,dense_rank() over(partition by deptno order by ename asc) as rank from emp where deptno in ('10','30');
Why do you need the rank just order by deptno first then ename.
SELECT ename,position,deptno,
FROM emp
WHERE deptno in ('10','30')
ORDER BY DeptNo, Ename
Using the analytical function two options derived table or CTE
Derived table/inline view.
SELECT ename,position,deptno
FROM (select ename,position,deptno,dense_rank() over(partition by deptno order by ename asc) as rank
from emp
where deptno in ('10','30')) Z
ORDER BY deptNo, rank
Common Table Expression (CTE):
with Z AS (SELECT ename,position,deptno
, dense_rank() over(partition by deptno order by ename asc) as rank
FROM emp
WHERE deptno in ('10','30'))
SELECT ename,position,deptno
FROM z
ORDER BY deptno, rank
Both these last 2 techniques simply avoid exposing the rank function to the outer query in which the results are returned. They are "Tricks" and sub-optimal execution time. unless there's a specific reason to have the rank data; I'd not use it.

how to get a row number for each group in report?

i am making a report that getting employees for each group and i need to get a row number for each employee in each department group
select rownum,e.empno, e.ename, e.sal, e.comm, e.deptno, d.dname
from emp e
left join dept d on emp.deptno = dept.deptno
order by deptno;
We can try using ROW_NUMBER here:
SELECT
ROW_NUMBER() OVER (PARTITION BY emp.deptno ORDER BY emp.ename) rn,
emp.empno,
emp.ENAME,
emp.SAL,
emp.COMM,
emp.DEPTNO,
dept.dname
FROM emp
LEFT JOIN dept
ON emp.deptno = dept.deptno
ORDER BY
emp.deptno;
Note that you never provided logic for what should decide the ordering of each employee within a department. In the absence of this, I have used the employee name. If you want some other ordering, then just modify the ORDER BY clause in the call to ROW_NUMBER.

not a single-group group function when using max

I am trying to get out the max avrg using query below but I am getting wrror saying
ORA-00937: not a single-group group function
00937. 00000 - "not a single-group group function"
*Cause:
*Action: Error at Line: 1 Column:
SELECT B.STUDENT_ID,
A.FRIST_NAME,
A.FATHER_NAME,
A.LAST_NAME,
SUM (B.GRADE) AS SUM_GRADE,
COUNT(B.COURSE_ID) AS COURSE_COUNT,
max(SUM(B.GRADE) / COUNT(B.COURSE_ID)) AS AVRG
FROM STUDENT A,
STUDENT_COURSE B
WHERE A.STUDENT_ID = B.STUDENT_ID
GROUP BY A.FRIST_NAME, A.FATHER_NAME, A.LAST_NAME, B.STUDENT_ID;
this error gone when I remove the max function any one can help me why ?
I tried to use having maxbut I am getting error that says invalid renational
any way to use having with this query ?
One option is to use your current query (without MAX) as an inline view, and apply the MAX function to "sum/count":
SELECT student_id,
first_name,
father_name,
last_name,
sum_grade,
course_count,
-- this:
MAX(avrg) max_avrt
FROM (-- your current query
SELECT b.student_id,
a.frist_name,
a.father_name,
a.last_name,
SUM(b.grade) AS sum_grade,
COUNT(b.course_id) AS course_count,
SUM(b.grade) / COUNT(b.course_id) AS avrg
FROM student a,
student_course b
WHERE a.student_id = b.student_id
GROUP BY a.frist_name,
a.father_name,
a.last_name,
b.student_id
)
GROUP BY student_id, first_name, father_name, last_name, sum_grade, course_count;
However, you won't achieve anything good, as you'd still get the same record set due to outer GROUP BY clause. Consider using SUM in its analytic form.
Here's a simple example which shows what I mean, based on Scott's schema.
This is what you have now:
SQL> select deptno, sum(sal) / count(*) ssc
2 from emp
3 group by deptno
4 order by deptno;
DEPTNO SSC
---------- ----------
10 2916,66667
20 2258,33333
30 1566,66667
Apparently, you'd like to select the first SSC value (2916). If you apply what I wrote earlier (i.e. use that query as an inline view), you'd get this:
SQL> select deptno, max(ssc) max_ssc
2 from (select deptno, sum(sal) / count(*) ssc
3 from emp
4 group by deptno
5 )
6 group by deptno
7 order by deptno;
DEPTNO MAX_SSC
---------- ----------
10 2916,66667
20 2258,33333
30 1566,66667
SQL>
No improvement, eh? So, analytical function might be what you need:
SQL> select deptno,
2 max(sum(sal) / count(*)) over (order by deptno) max_ssc
3 from emp
4 group by deptno
5 order by deptno;
DEPTNO MAX_SSC
---------- ----------
10 2916,66667
20 2916,66667
30 2916,66667
This does return desired MAX value (if that's what you're looking for. If not, explain what you'd want to get as a result).

Display Data in own order by using union

I want to display
all emps who are seniors to king and who are juniors to smith as in following order.
who are seniors to king are under king header and juniors to smith are under smith header
I tried this one,
select ename from emp where hiredate<(select hiredate from emp where ename='king')
union
select ename from emp where hiredate>(select hiredate from emp where ename='smith');
OUTPUT is only one header(ENAME)
How can i get my desired output(Two Headers KING SMITH)
Can any one help me
Addding another artificial column is a little trick:
select 1 status, ename from emp
where hiredate<(select hiredate from emp where ename='king')
union all
select 2 status, ename from emp
where hiredate>(select hiredate from emp where ename='smith')
order by 1;
You can even use a union all instead of union since all lines will be different. Or, if you really need headers/separators:
select 0 status, 'KING''S SENIORS' from dual
union all
select 1 status, ename from emp where hiredate<(select hiredate from emp where ename='king')
union all
select 2 status, 'SMITH''S JUNIORS' from dual
union all
select 3 status, ename from emp where hiredate>(select hiredate from emp where ename='smith')
order by 1;
may be this one will be useful
select e.ename,
case when e.hiredate < khd.hiredate then 1 else 0 end king_header,
case when e.hiredate > shd.hiredate then 1 else 0 end smith_juniour
from emp e,
(select hiredate from emp where ename='king') khd,
(select hiredate from emp where ename='smith') shd
where e.hiredate < khd.hiredate or e.hiredate > shd.hiredate

Resources