Need help understanding range between in SQL window functions - sql

I am trying understand how range clause is working in below case (oracle database)
SELECT
EMPID,NAME,
HIRE_DATE_1,
SALARY,
count(1) over(order by HIRE_DATE_1 range between 1 preceding and 1 preceding) as PREV_MIN_SA
FROM (
SELECT
EMPID,
NAME,
(EXTRACT(year from HIRE_DATE)*10000)+(EXTRACT(MONTH FROM HIRE_DATE) * 100) + (extract(DAY from HIRE_DATE)) as HIRE_DATE_1,SALARY
FROM EMPLOYEE A order by HIRE_DATE,SALARY
) A
ORDER BY HIRE_DATE_1
Result Set :
EMPID NAME HIRE_DATE_1 SALARY PREV_MIN_SA
100 Ravi 20180101 5000 0
101 Kumar 20180101 7000 0
102 Satish 20180101 13000 0
103 Naresh 20180102 7500 3
105 Lalith 20180104 17300 0
104 Suresh 20180104 40000 0
106 Latha 20180201 16000 0
The inner query is just converting date into numeric YYYYMMDD format.
My intention is to get the count of people who joined immediately prior to the date of the employee in each record. I can take the count of rows with same HIRE_DATE and use LAG function but somehow not understand how the sql is returning this result set.
Also, once I am done with the counts I would like to get the MIN(SALARY) of the employees who joined immediately prior to the employee in current row and find the difference in salaries so wondering if somehow I can define the window to only have all records with immediately prior HIRE_DATE.
Thanks

This should get the preceding hire date...
SELECT
EMPID,NAME, HIRE_DATE, SALARY,
MAX(HIRE_DATE) OVER (ORDER BY HIRE_DATE
RANGE BETWEEN UNBOUNDED PRECEDING
AND INTERVAL '1 DAY' PRECEDING
)
AS PREV_HIRE_DATE
FROM
EMPLOYEE
Then I think you need to join back on to the employee table to find the number of employees and their min salary?
SELECT
*
FROM
(
SELECT
EMPID,NAME, HIRE_DATE, SALARY,
MAX(HIRE_DATE) OVER (ORDER BY HIRE_DATE
RANGE BETWEEN UNBOUNDED PRECEDING
AND INTERVAL '1 DAY' PRECEDING
)
AS PREV_HIRE_DATE
FROM
EMPLOYEE
)
EMPS
LEFT JOIN
(
SELECT
HIRE_DATE,
COUNT(*) AS COUNT_EMPS,
MIN(SALARY) AS MIN_SALARY
FROM
EMPLOYEE
GROUP BY
HIRE_DATE
)
PREV_EMPS
ON PREV_EMPS.HIRE_DATE = EMPS.PREV_HIRE_DATE
EDIT:
Maybe try something like this? (I have to run, good luck!)
WITH
ranked AS
(
SELECT
*,
DENSE_RANK() OVER (ORDER BY HIRE_DATE) AS HIRE_SEQ_ID
FROM
EMPLOYEE
)
SELECT
*,
MIN(SALARY) OVER (ORDER BY HIRE_SEQ_ID
RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING
)
AS PREV_MIN_SALARY,
COUNT(*) OVER (ORDER BY HIRE_SEQ_ID
RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING
)
AS COUNT_PREV_EMPS
FROM
ranked

This does what you're after, I believe (I've left it as an exercise for you to find the difference between the max and min salaries!):
WITH employee AS (SELECT 100 empid, 'Ravi' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 5000 salary FROM dual UNION ALL
SELECT 101 empid, 'Kumar' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 7000 salary FROM dual UNION ALL
SELECT 102 empid, 'Satish' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 13000 salary FROM dual UNION ALL
SELECT 103 empid, 'Naresh' NAME, to_date('02/01/2018', 'dd/mm/yyyy') hire_date, 7500 salary FROM dual UNION ALL
SELECT 104 empid, 'Lalith' NAME, to_date('04/01/2018', 'dd/mm/yyyy') hire_date, 17300 salary FROM dual UNION ALL
SELECT 105 empid, 'Suresh' NAME, to_date('04/01/2018', 'dd/mm/yyyy') hire_date, 40000 salary FROM dual UNION ALL
SELECT 106 empid, 'Latha' NAME, to_date('01/02/2018', 'dd/mm/yyyy') hire_date, 16000 salary FROM dual)
SELECT empid,
NAME,
hire_date,
salary,
COUNT(*) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_hire_count,
MIN(salary) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_min_sal,
MAX(salary) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_max_sal
FROM employee;
EMPID NAME HIRE_DATE SALARY PREV_DAY_HIRE_COUNT PREV_DAY_MIN_SAL PREV_DAY_MAX_SAL
---------- ------ ----------- ---------- ------------------- ---------------- ----------------
100 Ravi 01/01/2018 5000 0
101 Kumar 01/01/2018 7000 0
102 Satish 01/01/2018 13000 0
103 Naresh 02/01/2018 7500 3 5000 13000
104 Lalith 04/01/2018 17300 0
105 Suresh 04/01/2018 40000 0
106 Latha 01/02/2018 16000 0

Related

Find day of week with most hires

I am trying to find the employee hire_date (day of week) where the most employees were hired. In my test CASE below the answer should be Tuesday.
As you can see I can list all the days but I'm having a problem narrowing down the result to 1 row.
Any help would be greatly appreciated. I listed my failed attempt. If there is a more efficient way to rewrite the query I would prefer any input.
CREATE TABLE employees (employee_id, first_name, last_name, hire_date) AS
SELECT 1, 'Lisa', 'Saladino', DATE '2001-04-03' FROM DUAL UNION ALL
SELECT 2, 'Abby', 'Abbott', DATE '2001-04-04' FROM DUAL UNION ALL
SELECT 3, 'Beth', 'Cooper', DATE '2001-04-05' FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Orr', DATE '2001-04-06' FROM DUAL UNION ALL
SELECT 5, 'Nancy', 'Turner', DATE '2001-04-07' FROM DUAL UNION ALL
SELECT 6, 'Cheryl', 'Ford', DATE '2001-04-08' FROM DUAL UNION ALL
SELECT 7, 'Leslee', 'Gold', DATE '2001-04-10' FROM DUAL UNION ALL
SELECT 8, 'Jill', 'Coralnick', DATE '2001-04-11' FROM DUAL UNION ALL
SELECT 9, 'Faith', 'Aaron', DATE '2001-04-17' FROM DUAL;
SELECT TO_CHAR(HIRE_DATE,'DAY') DAY, count(*) cnt FROM EMPLOYEES GROUP BY TO_CHAR(HIRE_DATE,'DAY')
DAY CNT
TUESDAY 3
FRIDAY 1
SUNDAY 1
SATURDAY 1
WEDNESDAY 2
THURSDAY 1
/* not working */
SELECT e.*
FROM EMPLOYEES e
INNER JOIN
(SELECT employee_id, TO_CHAR(HIRE_DATE,'DAY') DAY
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY')
HAVING COUNT(1)=(SELECT MAX(COUNT(1))FROM EMPLOYEES GROUP BY TO_CHAR(HIRE_DATE,'DAY'))) AS empdays
ON TO_CHAR(e.HIRE_DATE, 'DAY') = empdays.DAY;
You neither need a subquery or nor a join to use if the DB's version is 12c+, but just use the FETCH clause following ORDER BY in order to sort the counts descendingly such as
SELECT TO_CHAR(hire_date, 'DAY') AS day, COUNT(*) AS cnt
FROM employees
GROUP BY TO_CHAR(hire_date, 'DAY')
ORDER BY cnt DESC
FETCH FIRST 1 ROW WITH TIES
This works for me:
select DAY, cnt
from (SELECT TO_CHAR(HIRE_DATE,'DAY') DAY
,count(*) cnt
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY'))
where cnt = (select max(cnt)
from (SELECT TO_CHAR(HIRE_DATE,'DAY') DAY
,count(*) cnt
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY')))
Produces following results
DAY
CNT
Tuesday
3
Refer to this db<>fiddle
In a comment, you asked for dense_rank example; here it is:
SQL> with temp as
2 (select to_char(hire_date, 'Day') day,
3 count(*) cnt,
4 dense_rank() over (order by count(*) desc) rnk
5 from employees
6 group by to_char(hire_date, 'Day')
7 )
8 select day, cnt
9 from temp
10 where rnk = 1;
DAY CNT
------------------------------------ ----------
Tuesday 3
SQL>

List the branch that monthly pays the most in salaries

I have this table, the expected output should be B003 since it's pays 54,000
STAFF
SALARY
BRAN
SL21
30000
B005
SG37
12000
B003
SG14
18000
B003
SA9
9000
B007
SG5
24000
B003
SL41
9000
B005
So far I only have this subquery, which isn't working how I expected.
SELECT BRANCHNO
FROM STAFF
WHERE (SALARY) IN (SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO);
This works but I want a subquery that returns the branchno
SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO;
select BRANCHNO max(sum_sal)
from (SELECT BRANCHNO, SUM(SALARY) sum_sal
FROM STAFF
GROUP BY BRANCHNO) q1
group by BRANCHNO ;
The column used to group the rows can be displayed. So, add BRANCHNO to your select clause.
One option is to use rank analytic function which ranks branches by sum of their salaries in descending order; you'd then return the one(s) that rank as the highest (rnk = 1).
Sample data:
SQL> with staff (staff, salary, bran) as
2 (select 'SL21', 30000, 'B005' from dual union all
3 select 'SG37', 12000, 'B003' from dual union all
4 select 'SG14', 18000, 'B003' from dual union all
5 select 'SA9' , 9000, 'B007' from dual union all
6 select 'SG5' , 24000, 'B003' from dual union all
7 select 'SL41', 9000, 'B005' from dual
8 )
Query:
9 select bran
10 from (select bran, rank() over (order by sum(salary) desc) rnk
11 from staff
12 group by bran
13 )
14 where rnk = 1;
BRAN
----
B003
SQL>

Oracle SQL - Scanning attribute Changes

I have the following employee table
EMPID RECORD_DATE DEPARTMENT
123456 2020-01-01 HR
123456 2020-02-01 HR
123456 2020-03-01 FINANCE
123456 2020-04-01 FINANCE
987654 2020-01-01 HR
987654 2020-02-01 HR
987654 2020-03-01 HR
987654 2020-04-01 LEGAL
Using Oracle PL/SQL, I need to build an expression to ascertain a list of employee movement, specifically that have moved from HR to any other (non-HR) department.
Expected result:
EMPID MOVEMENT_DATE DEPT_BEFORE DEPT_AFTER
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL
I know you can use the Lead or Lag function, but it's a little off for me:
SELECT
,EMP
,RECORD_DATE
,LAG(DEPARTMENT, 1, 0) OVER (PARTITION BY EMP ORDER BY RECORD_DATE) PREV
FROM EMP
Here are some values to work with:
CREATE TABLE #EMP
(
EMP VARCHAR(30) NOT NULL ,
RECORD_DATE DATE NOT NULL ,
DEPARTMENT VARCHAR(30) NOT NULL
);
INSERT INTO #EMP (EMP, DATE_WORKED, CITY)
VALUES
('123456','2020-01-01','HR'),
('123456','2020-02-01','HR'),
('123456','2020-03-01','FINANCE'),
('123456','2020-04-01','FINANCE'),
('987654','2020-01-01','HR'),
('987654','2020-02-01','HR'),
('987654','2020-03-01','HR'),
('987654','2020-04-01','LEGAL')
You could do it using LAG function:
WITH data AS(
SELECT 123456 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-03-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-04-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-03-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-04-01' RECORD_DATE, 'LEGAL' DEPARTMENT FROM dual
)
SELECT * FROM(
SELECT
EMPID,
RECORD_DATE MOVEMENT_DATE,
LAG(DEPARTMENT) OVER (PARTITION BY EMPID ORDER BY RECORD_DATE) DEPARTMENT_BEFORE,
DEPARTMENT DEPARTMENT_AFTER
FROM data
)
WHERE DEPARTMENT_BEFORE <> DEPARTMENT_AFTER;
EMPID MOVEMENT_DATE DEPARTMENT_BEFORE DEPARTMENT_AFTER
---------- --------------- ----------------- -----------------
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL

Execute a oracle pl/sql query and return the result set based on the run time date value

I have below data
empid date amount
1 12-FEB-2017 10
1 12-FEB-2017 10
1 13-FEB-2017 10
1 14-FEB-2017 10
I need a query to return the total amount for a given id and date i.e, below result set
empid date amount
1 12-FEB-2017 20
1 13-FEB-2017 10
1 14-FEB-2017 10
but the think is, from the UI i will be getting the date as input.. if they pass the date return the result for that date .. if they dont pass the date return the result for most recent date.
below is the query that I wrote .. but it is working partially..
SELECT sum(amount),empid,date
FROM employee emp,
where
((date= :ddd) OR aum_valutn_dt = (select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
Please help..
I think you could do something like this
but it is pretty bad you should try to do it some other way
it is doing extra work to get the most recent date
select amt, empid, date
from
(
select amt, empid, date, rank() over (order by date desc) date_rank
from
(SELECT sum(amount) amt,empid,date
FROM employee emp
where emp.id = '1'
and (date = :ddd or :ddd is null)
group by empid, date)
)
where date = :ddd or (:ddd is null and date_rank=1)
Here's another option; scans TEST table twice so ... mind the performance.
SQL> with test (empid, datum, amount) as
2 (select 1, date '2017-02-12', 10 from dual union all
3 select 1, date '2017-02-12', 10 from dual union all
4 select 1, date '2017-02-13', 10 from dual union all
5 select 1, date '2017-02-14', 10 from dual
6 )
7 select t.empid, t.datum, sum(t.amount) sum_amount
8 from test t
9 where t.datum = (select max(t1.datum)
10 from test t1
11 where t1.empid = t.empid
12 and (t1.datum = to_date('&&par_datum', 'dd.mm.yyyy')
13 or '&&par_datum' is null)
14 )
15 group by t.empid, t.datum;
Enter value for par_datum: 13.02.2017
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 13.02.2017 10
SQL> undefine par_datum
SQL> /
Enter value for par_datum:
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 14.02.2017 10
SQL>
SELECT sum(amount),empid,date
FROM employee emp,
where date =nvl((:ddd ,(select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
My solution is following:
with t (empid, datum, amount) as
(select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-13', 10 from dual union all
select 1, date '2017-02-14', 10 from dual
)
select empid, datum, s
from (select empid, datum, sum(amount) s, max(datum) over (partition by empid) md
from t
group by empid, datum)
where datum = nvl(to_date(:p, 'yyyy-mm-dd'), md);
Calculate maximal date in the subquery and then, in outer subquery, compare the date with nvl(to_date(:p, 'yyyy-mm-dd'), md). If the paremeter is null, then the date field is compared with maximal date.

how to retrieve highest and lowest salary from following table?

I have employee table
EMP_ID | F_NAME | L_NAME | SALARY | JOINING_DATE | DEPARTMENT
-----------------------------------------------------------------------------------
101 | John | Abraham | 100000 | 01-JAN-14 09.15.00.000000 AM | Banking
102 | Michel | Clarke | 800000 | | Insaurance
102 | Roy | Thomas | 70000 | 01-FEB-13 12.30.00.000000 PM | Banking
103 | Tom | Jose | 600000 | 03-FEB-14 01.30.00.000000 AM | Insaurance
105 | Jerry | Pinto | 650000 | 01-FEB-13 12.00.00.000000 PM | Services
106 | Philip | Mathew | 750000 | 01-JAN-13 02.00.00.000000 AM | Services
107 | TestName1 | 123 | 650000 | 01-JAN-13 12.05.00.000000 PM | Services
108 | TestName2 | Lname% | 600000 | 01-JAN-13 12.00.00.000000 PM | Insaurance
i want to find highest and lowest salary from above table in oracle sql.
if i do
select max(salary) from (select * from (select salary from employee) where rownum <2);
it returns MAX(SALARY) = 100000 where it should return 800000
If I do
select max(salary)
from (select * from (select salary from employee)
where rownum <3);
it returns MAX(SALARY) = 800000
If I do
select min(salary)
from (select * from(select salary from employee)
where rownum < 2);
it will return MIN(SALARY) = 100000 where it should return 70000.
What is wrong in this query?
what should be the correct query?
You don't need all these subqueries:
SELECT MAX(salary), MIN(salary)
FROM employee
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE employee ( EMP_ID, F_NAME, L_NAME, SALARY, JOINING_DATE, DEPARTMENT ) AS
SELECT 101, 'John', 'Abraham', 100000, TIMESTAMP '2014-01-01 09:15:00', 'Banking' FROM DUAL
UNION ALL SELECT 102, 'Michel', 'Clarke', 800000, NULL, 'Insurance' FROM DUAL
UNION ALL SELECT 102, 'Roy', 'Thomas', 70000, TIMESTAMP '2013-02-01 12:30:00', 'Banking' FROM DUAL
UNION ALL SELECT 103, 'Tom', 'Jose', 600000, TIMESTAMP '2014-02-03 01:30:00', 'Insurance' FROM DUAL
UNION ALL SELECT 105, 'Jerry', 'Pinto', 650000, TIMESTAMP '2013-02-01 12:00:00', 'Services' FROM DUAL
UNION ALL SELECT 106, 'Philip', 'Mathew', 750000, TIMESTAMP '2013-01-01 02:00:00', 'Services' FROM DUAL
UNION ALL SELECT 107, 'TestName1', '123', 650000, TIMESTAMP '2013-01-01 12:05:00', 'Services' FROM DUAL
UNION ALL SELECT 108, 'TestName2', 'Lname%', 600000, TIMESTAMP '2013-01-01 12:00:00', 'Insurance' FROM DUAL;
Query 1 - To find the highest-n salaries:
SELECT *
FROM (
SELECT salary
FROM employee
ORDER BY salary DESC
)
WHERE rownum <= 3 -- replace with the number of salaries you want to retrieve.
Results:
| SALARY |
|--------|
| 800000 |
| 750000 |
| 650000 |
Query 2 - To find the lowest-n salaries:
SELECT *
FROM (
SELECT salary
FROM employee
ORDER BY salary ASC
)
WHERE rownum <= 3 -- replace with the number of salaries you want to retrieve.
Results:
| SALARY |
|--------|
| 70000 |
| 100000 |
| 600000 |
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.
So in your case :
select max(salary) from (select * from (select salary from employee) where rownum <2);
This query will return
101 John Abraham 100000 01-JAN-14 09.15.00.000000 AM Banking
only this row as output... and hence the max value will be 100000 only.
select max(salary) from (select * from (select salary from employee) where rownum <3);
This query will tak first 2 rows from your table, i.e.,
101 John Abraham 100000 01-JAN-14 09.15.00.000000 AM Banking
102 Michel Clarke 800000 Insaurance
and hence the max salary will be 800000.
Similarly,
select min(salary)from (select * from(select salary from employee)where rownum<2);
will only select 1st row
select min(salary)from (select * from(select salary from employee)where rownum<2);
so min salary will be 100000.
P.S. : You could simply write your queries like this :
select max(salary) from employee where rownum<[n];
where n will be ROWNUM to which you want to limit the number of rows returned by your query
Try it:
select *
from (
select T.*, rownum RRN
from (
select salary
from employee
order by salary desc) T)
where RRN < 3
so you want the 2nd highest and 2nd lowest salary? Check this out
select max(salary), min(salary) from employee
where salary < (select max(salary) from employee)
and salary > (select min(salary) from employee)
;
1) For lowest salary.
select * from (
select empno,job,ename,sal
from emp order by sal)
where rownum=1;
2) For Highest salary.
select * from (
select empno,job,ename,sal
from emp order by sal desc)
where rownum=1;
i don't know why you make complicated queries you can simply write this and get the same result:
select salary
from employees
where rownum <=3
order by salary desc;
you can solve your problem with following queries:
Highest salary:
Select * from Employee(Select salary from Employee ORDER BY salary DISC) where rownum=1;
Lowest salary:
Select * from Employee(Select salary from Employee ORDER BY salary) where rownum=1;
Second highest salary:
Select MAX(Salary) from Employee
Where Salary < (Select MAX(Salary) from employee);
Second Lowest salary :
Select MIN(Salary) from Employee
Where Salary > (Select MIN(Salary) from employee);