SQL Query for emp and dept table in ORACLE - sql

This is with reference to the emp and dept table of ORACLE. I want an output table to display dname, deptno, total salary for the employees hired in 1981 and 1982. I want the ouput in following fashion
Deptno | Dname | TotalSal_1981 | TotalSal_1982
I can find the total salary for employees hired in 1981 in 1982 seperately. But how to club them to get output with above columns?
Thank you

You would probably want to join the employees hired in 1981 and 1982 and then sum conditionally:
select
d.deptno,
d.dname,
sum(case when extract(year from hiredate) = 1981 then sal end) as totalsal_1981,
sum(case when extract(year from hiredate) = 1982 then sal end) as totalsal_1982
from dept d
join emp e on e.deptno = d.deptno and extract(year from e.hiredate) in (1981,1982)
group by d.deptno, d.dname;
Use a left outer join instead, if you want to show departments with no hired employees in these years, too.

Related

Analytic functions and means of window clause

I'm using Oracle and SQL Developer. I have downloaded HR schema and need to do some queries with it. Now I'm working with table Employees. As an user I need to see employees with the highest gap between their salary and the average salary of all later hired colleagues in corresponding department. It seems quite interesting and really complicated. I have read some documentation and tried, for example LEAD(), that provides access to more than one row of a table at the same time:
SELECT
employee_id,
first_name
|| ' '
|| last_name,
department_id,
salary,
hire_date,
LEAD(hire_date)
OVER(PARTITION BY department_id
ORDER BY
hire_date DESC
) AS Prev_hiredate
FROM
employees
ORDER BY
department_id,
hire_date;
That shows for every person in department hiredate of later hired person. Also I have tried to use window clause to understand its concepts:
SELECT
employee_id,
first_name
|| ' '
|| last_name,
department_id,
hire_date,
salary,
AVG(salary)
OVER(PARTITION BY department_id
ORDER BY
hire_date ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
) AS avg_sal
FROM
employees
ORDER BY
department_id,
hire_date;
The result of this query will be:
However, it is not exactly what I need. I need to reduce the result just by adding column with gap (salary-avr_sal), where the gap will be highest and receive one employee per department. How should the result look like: for example, we have 60 department. We have 5 employees there ordering by hire_date. First has salary 4800, second – 9000, third – 4800, fourth – 4200, fifth – 6000. If we do calculations: 4800 - ((9000+4800+4200+6000)/4)=-1200, 9000-((4800+4200+6000)/3)=4000, 4800 -((4200+6000)/2)=-300, 4200 - 6000=-1800 and the last person in department will have the highest gap: 6000 - 0 = 6000. Let's take a look on 20 department. We have two people there: first has salary 13000, second – 6000. Calculations: 13000 - 6000 = 7000, 6000 - 0 = 6000. The highest gap will be for first person. So for department 20 the result should be person with salary 13000, for department 60 the result should be person with salary 6000 and so on.
How should look my query to get the appropriate result (what I need is marked bold up, also I want to see column with highest gap, can be different solutions with analytic functions, but should be necessarily included window clause)?
You can get the average salary of employees that were hired prior to the current employee by just adapting the rows clause of your avg:
AVG(salary) OVER(
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS avg_salary
The 1 PRECEDING clause tells the database not to include the current row in the window.
If you are looking for the employees with the greatest gap to that average, we can just order by the resultset:
SELECT e.*,
AVG(salary) OVER(
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS avg_salary
FROM employees e
ORDER BY ABS(salary - avg_salary) DESC;
Finally, if you want the top "outlier salary" per department, then we need at least one more level. The shortest way to express this probably is to use ROW_NUMBER() to rank employees in each department by their salary gap to the average, and then to fetch all top rows per group using WITH TIES:
SELECT *
FROM (
SELECT e.*,
AVG(salary) OVER(
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS avg_salary
FROM employees e
) e
ORDER BY ROW_NUMBER() OVER(
PARTITION BY department_id
ORDER BY ABS(salary - avg_salary) DESC
)
FETCH FIRST ROW WITH TIES
Maybe this is what you are looking for.
Sample data:
WITH
emp (ID, EMP_NAME, HIRE_DATE, SALARY, DEPT) AS
(
Select 601, 'HILLER', To_Date('23-JAN-82', 'dd-MON-yy'), 4800, 60 From Dual Union All
Select 602, 'MILLER', To_Date('23-FEB-82', 'dd-MON-yy'), 9000, 60 From Dual Union All
Select 603, 'SMITH', To_Date('23-MAR-82', 'dd-MON-yy'), 4800, 60 From Dual Union All
Select 604, 'FORD', To_Date('23-APR-82', 'dd-MON-yy'), 4200, 60 From Dual Union All
Select 605, 'KING', To_Date('23-MAY-82', 'dd-MON-yy'), 6000, 60 From Dual Union All
Select 201, 'SCOT', To_Date('23-MAR-82', 'dd-MON-yy'), 13000, 20 From Dual Union All
Select 202, 'JONES', To_Date('23-AUG-82', 'dd-MON-yy'), 6000, 20 From Dual
),
Create CTE named grid with several analytic functions and windowing clauses. They are not all needed but the resulting dataset below shows the logic with all components included.
grid AS
(
Select
g.*, Max(GAP) OVER(PARTITION BY DEPT) "DEPT_MAX_GAP"
From
(
Select
ROWNUM "RN",
Sum(1) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN Unbounded Preceding And Current Row) "RN_DEPT",
ID, EMP_NAME, HIRE_DATE, DEPT, SALARY,
--
Nvl(Sum(SALARY) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following), 0) "SUM_SAL_LATER",
Nvl(Sum(1) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following), 0) "COUNT_EMP_LATER",
--
Nvl(Sum(SALARY) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following) /
Sum(1) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following), 0) "AVG_LATER",
--
SALARY -
Nvl((
Sum(SALARY) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following) /
Sum(1) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following)
), 0) "GAP"
from
emp
Order By
DEPT, HIRE_DATE, ID
) g
Order By
RN
)
CTE grid resultiing dataset:
RN
RN_DEPT
ID
EMP_NAME
HIRE_DATE
DEPT
SALARY
SUM_SAL_LATER
COUNT_EMP_LATER
AVG_LATER
GAP
DEPT_MAX_GAP
1
1
601
HILLER
23-JAN-82
60
4800
24000
4
6000
-1200
6000
2
2
602
MILLER
23-FEB-82
60
9000
15000
3
5000
4000
6000
3
3
603
SMITH
23-MAR-82
60
4800
10200
2
5100
-300
6000
4
4
604
FORD
23-APR-82
60
4200
6000
1
6000
-1800
6000
5
5
605
KING
23-MAY-82
60
6000
0
0
0
6000
6000
6
1
201
SCOT
23-MAR-82
20
13000
6000
1
6000
7000
7000
7
2
202
JONES
23-AUG-82
20
6000
0
0
0
6000
7000
Main SQL
SELECT
g.ID, g.EMP_NAME, g.HIRE_DATE, g.DEPT, g.SALARY, g.GAP
FROM
grid g
WHERE
g.GAP = g.DEPT_MAX_GAP
Order By
RN
Resulting as:
ID
EMP_NAME
HIRE_DATE
DEPT
SALARY
GAP
605
KING
23-MAY-82
60
6000
6000
201
SCOT
23-MAR-82
20
13000
7000
Without CTE and with all unnecessery columns excluded it looks like this:
SELECT ID, EMP_NAME, HIRE_DATE, DEPT, SALARY, GAP
FROM
(
( Select g.*, Max(GAP) OVER(PARTITION BY DEPT) "DEPT_MAX_GAP"
From( Select
ID, EMP_NAME, HIRE_DATE, DEPT, SALARY,
SALARY -
Nvl(( Sum(SALARY) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following) /
Sum(1) OVER(Partition By DEPT Order By DEPT, HIRE_DATE, ID ROWS BETWEEN 1 Following And Unbounded Following)
), 0) "GAP"
From emp
Order By DEPT, HIRE_DATE, ID
) g
)
)
WHERE GAP = DEPT_MAX_GAP
Order By DEPT, HIRE_DATE, ID
It seems like this is all you need.
Regards...

SQL Oracle, what is done here?

Can someone telle me what this code exactly does?
What does the 4 mean after count-function?
SELECT ROUND(SUM(correct)/COUNT(*),4) AS accuracy
FROM (SELECT DECODE(survived,
PREDICTION(DT_TITANIC USING *), 1, 0) AS correct
FROM titanic_test_data);
What does it do? Calculates an average and rounds it to 4 decimals.
SQL> select sum(sal) sum_salary,
2 count(*) num_of_employees,
3 --
4 sum(sal) / count(*) average_salary,
5 --
6 round(sum(sal) / count(*), 4) rounded_avg_salary
7 from emp;
SUM_SALARY NUM_OF_EMPLOYEES AVERAGE_SALARY ROUNDED_AVG_SALARY
---------- ---------------- -------------- ------------------
29025 14 2073,21429 2073,2143
sum_salary is sum of all salaries in the table
num_of_employees is number of employees
average_salary, as sum_salary divided by num_of_employees
rounded_avg_salary is what "your" code does
Note that we usually do it as
SQL> select avg(sal) from emp;
AVG(SAL)
----------
2073,21429

oracle database query for getting monthly salary and total salary

I have a table where monthly salary of employees are stored.
create table myemp
(
empno number ,
month number,
year number,
salary number
);
Now i need a query to get results like below
empno|month|Year|salary
0001 2 2016 10000
0001 3 2016 11000
0001 4 2016 12000
0001 -- ---- (10000+11000+12000)
0002 2 2016 15000
0002 3 2016 16000
0002 4 2016 15000
0002 -- ----(15000+16000+15000)
We can set total and subtotal using Rollup function of oracle like given below
select empno,month,year,sum(salary) from myemp
GROUP BY year,ROLLUP (empno,month)
here empno and month are in rollup function that gives total and subtotal of
empno and month group.
i hope this will help.
Here you go:
SELECT *
FROM (
(
SELECT empno, month, year, salary
FROM myemp
)
UNION ALL
(
SELECT empno, NULL AS month, NULL AS year, sum(salary)
FROM myemp
GROUP BY empno
)
) AS foo
ORDER BY empno, year IS NULL, year, month
would look like this
select lastname , dept_no, salary,
sum(salary) over (partition by dept_no order by lastname) dept_total
from myemp
order by salary, lastname;

How do i find who got maximum % salary from employee table?

Here the emp table :
To show the employee who got the maximum % salary between 01-jan-81 and 31-dec-81
EMPNO ENAME SAL HIREDATE
------- ---------- ---------- ---------
7369 SMITH 800 17-DEC-80
7499 ALLEN 1600 20-FEB-81
7521 WARD 1250 22-FEB-81
7566 JONES 2975 02-APR-81
7654 MARTIN 1250 28-SEP-81
7698 BLAKE 2850 01-MAY-81
7782 CLARK 2450 09-JUN-81
7788 SCOTT 3000 09-DEC-82
7839 KING 5000 17-NOV-81
7844 TURNER 1500 08-SEP-81
7876 ADAMS 1100 12-JAN-83
7900 JAMES 950 03-DEC-81
7902 FORD 3000 03-DEC-81
7934 MILLER 1300 23-JAN-82
The typical way to do this is to use order by and some sort of limit. In Oracle 12c+, you can do:
select t.*
from emptable t
where hiredate between date '1981-01'01' and date '1981-12-31'
order by sal desc
fetch first 1 row only;
In earlier versions, you can use a subquery:
select x.*
from (select t.*
from emptable t
where hiredate between date '1981-01'01' and date '1981-12-31'
order by sal desc
) x
where rownum = 1;
Note: This only shows one employee. If there are ties, an arbitrary employee is chosen. To get all of them, use rank() or dense_rank():
select x.*
from (select t.*, dense_rank() over (order by sal desc) as seqnum
from emptable t
where hiredate between date '1981-01'01' and date '1981-12-31'
order by sal desc
) x
where seqnum = 1;
SELECT EMPNO, ENAME from EMPTABLE
WHERE
sal = (SELECT MAX(sal) from EMPTABLE where hiredate BETWEEN '01-JAN-81' and '31-DEC-81')
AND
hiredate BETWEEN '01-JAN-81' and '31-DEC-81'
Employee with the maximum percentage salary also has the maximum salary in the given period.
PS: This is generic sql syntax
First we create a temp table that includes all salary of everyone in specified date and then we select the person who got the most using max.
SELECT ENAME,max(ALLSAL)
FROM (SELECT ENAME,SUM(SAL) AS ALLSAL
FROM t1 GROUP BY ENAME
WHERE hiredate between date '1981-01'01' and date '1981-12-31' ) AS t2
Just in case you want to print % salary also
SELECT *
FROM
(SELECT emp.*,
round((salary/
(SELECT SUM(salary) FROM emp
))*100,2) pcent
FROM emp
WHERE hiredate BETWEEN DATE '1981-01-01' AND DATE '1981-12-31'
ORDER BY pcent DESC
)
WHERE ROWNUM<=1;
You can use KEEP ( DENSE_RANK [FIRST|LAST] ORDER BY ... ) to get the maximum of two columns:
SELECT MAX( EName ) KEEP ( DENSE_RANK LAST ORDER BY Salary ) AS EName,
100 * MAX( Salary ) / SUM( Salary ) AS Percentage_Of_Total_Salary
FROM Employees
WHERE HIRE_DATE >= DATE '1981-01-01'
AND HIRE_DATE < DATE '1982-01-01';
Something like this should work.
with selected_rows as (
select empno, ename, sal
from scott.emp
where hiredate between date '1981-01-01' and date '1981-12-31'
),
max_and_total_sal (max_sal, total_sal) as (
select max(sal), sum(sal)
from selected_rows
)
select empno, ename, sal, to_char(100*sal/total_sal, '99.99') || '%' as percent_sal
from selected_rows cross join max_and_total_sal
where sal = max_sal;
Result:
EMPNO ENAME SAL PERCENT_SAL
---------- ---------- ---------- -----------
7839 KING 5000 21.91%
1 row selected.
Note that the percentage is based on the sum of salaries of the employees hired in 1981 (NOT the sum of all salaries in the table). As a quick verification, clearly King has the highest salary (and therefore also the highest percentage) of all employees, and indeed he was hired in 1981, so he must be the (unique) answer to the "WHO" part of the question.
Note also that the solution depends on KNOWING that the hiredate column in the table only stores "pure dates" (that is, with the time set to 00:00:00); otherwise the "between ... and ..." part would have to be refined.

Oracle Database sql query. Having?

Show the name of all the employees who were hired on the day of the week on which the highest number of employees were hired.
Table:
Steven 06/17/1987
Neena 09/21/1989
Lex 01/13/1993
Alex 01/03/1990
Bruce 05/21/1991
Diana 02/07/1999
Kevin 11/16/1999
Trenna 10/17/1995
Curtis 01/29/1997
Randall 03/15/1998
Peter 07/09/1998
Eleni 01/29/2000
Ellen 05/11/1996
Jonath 03/24/1998
Kimber 05/24/1999
Jenni 09/17/1987
Michael 02/17/1996
Pat 08/17/1997
Shelley 06/07/1994
William 06/07/1994
What I have so far.
SELECT FIRST_NAME, to_char(hire_date,'d') AS DOW FROM EMPLOYEES;
Steven 4
Neena 5
Lex 4
Alex 4
Bruce 3
Diana 1
Kevin 3
Trenna 3
Curtis 4
Randall 1
Peter 5
Eleni 7
Ellen 7
Jonath 3
Kimbe 2
Jenni 5
Michael 7
Pat 1
Shelley 3
William 3
Sunday is 1, monday is 2, ... so on...
Now i need to select the one with the max repeating number.
Which by looking at the table we will know it's 3 (tuesday). I know i will need to use a subquery to get it, is it having?
I would be inclined to use analytic functions for this:
select e.*
from (SELECT to_char(hire_date, 'd') AS DOW, count(*) as cnt,
row_number() over (order by count(*) desc) as seqnum
FROM EMPLOYEES
) dow join
EMPLOYEEs e
on dow.DOW = to_char(e.hire_date, 'd') and seqnum = 1;
One way, extending your query above (SQL Fiddle Example):
SELECT FIRST_NAME, to_char("hire_date", 'd') AS DOW
FROM EMPLOYEES
WHERE to_char("hire_date", 'd') =
(
SELECT b.DOW
FROM
(
select a.*, ROWNUM rnum
from (
SELECT to_char("hire_date", 'd') AS DOW, COUNT(1) AS cnt
FROM EMPLOYEES
GROUP BY to_char("hire_date", 'd')
ORDER BY cnt DESC
) a
where rownum = 1
) b
)
select *
from employees
where to_char(hire_date, 'd') = (
select max(to_char(hire_date, 'd')) keep (dense_rank last order by count(*))
from employees
group by to_char(hire_date, 'd')
);
SQLFiddle