I have a table tab_assignment_xx
date_from date_end action person_number
01-Apr-2014 31-Jul-2014 HIRE 050498
01-Aug-2014 31-Jan-2015 OTHERS 050498
01-Feb-2015 30-Jun-2015 OTHERS 050498
01-Jul-2015 15-Nov-2015 OTHERS 050498
16-Nov-2015 31-Dec-2015 OTHERS 050498
01-Jan-2016 30-JAN-2016 OTHERS 050498
01-APR-2016 31-JUL-2016 hire 83982
01-jan-2015 31-dec-4712 Others 6447
Now i want to check for all those particular employees with effective_start_date and effective_End_date that it should have 31-dec-4712 as max(effective_end_date) for example : for 050498 the max(effective_end_date) is not '31-dec-4712' and same goes for 83982. 6447 is correct.
for this i made :
select * from (
select T.*,
max(EFFECTIVE_START_DATE) over (partition by PERSON_NUMBER order by EFFECTIVE_START_DATE) MAX_FROM
from tab_assignment_xx T
where 1=1
--T.PERSON_NUMBER = '093343'
AND ASSIGNMENT_TYPE='E'
)
where MAX_FROM <> to_date('31-DEC-4712')
;
But this is not working in the sense that it is both the rows with and without 31-dec-4712 max eff end date
Oracle Setup:
CREATE TABLE table_name ( date_from, date_end, action, person_number ) AS
SELECT DATE '2014-04-01', DATE '2014-07-31', 'HIRE', '050498' FROM DUAL UNION ALL
SELECT DATE '2014-08-01', DATE '2015-01-31', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-02-01', DATE '2015-06-30', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-07-01', DATE '2015-11-15', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-11-16', DATE '2015-12-31', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2016-01-01', DATE '2016-01-30', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2016-04-01', DATE '2016-07-31', 'hire', '83982' FROM DUAL UNION ALL
SELECT DATE '2015-01-01', DATE '4712-12-31', 'Others', '6447' FROM DUAL;
Query:
SELECT Date_From, Date_To, action, person_number
FROM (
SELECT t.*,
MAX( date_end )
OVER ( PARTITION BY person_number
ORDER BY ROWNUM
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS mx
FROM table_name t
)
WHERE mx <> DATE '4712-12-31';
Output:
DATE_FROM DATE_END ACTION PERSON_NUMBER
------------------- ------------------- ------ -------------
2014-04-01 00:00:00 2014-07-31 00:00:00 HIRE 050498
2014-08-01 00:00:00 2015-01-31 00:00:00 OTHERS 050498
2015-02-01 00:00:00 2015-06-30 00:00:00 OTHERS 050498
2015-07-01 00:00:00 2015-11-15 00:00:00 OTHERS 050498
2015-11-16 00:00:00 2015-12-31 00:00:00 OTHERS 050498
2016-01-01 00:00:00 2016-01-30 00:00:00 OTHERS 050498
2016-04-01 00:00:00 2016-07-31 00:00:00 hire 83982
Please check if this works(I din't try). Comment if anything wrong.
select * from tab_assignment_xx a where to_date('31-DEC-4712')<>(select max(date_end) from tab_assignment_xx b where a.person_number=b.person_number);
Related
I want active ids but not those record which have I' as status till next record for that id but if the previous record has status 'A' then it should come but not after the status 'I record'.
INPUT:
id
start_date
end_date
status
1000000278
8/25/2021
8/25/2022
I
1000000278
8/25/2022
8/25/2023
A
1000000284
8/20/2021
8/25/2022
A
1000000284
8/25/2022
8/25/2023
A
1000000285
8/20/2024
8/20/2028
A
1000000285
8/21/2028
8/20/2030
I
1000000285
8/21/2030
8/20/2031
A
1000000286
8/25/2021
8/25/2022
A
OUTPUT:
id
start_date
end_date
status
1000000284
8/20/2021
8/25/2022
A
1000000284
8/25/2022
8/25/2023
A
1000000285
8/20/2024
8/20/2028
A
1000000286
8/25/2021
8/25/2022
A
From Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row processing. To get the rows for each id with the earliest status of A until the first status I row, you can use:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY id
ORDER BY start_date
ALL ROWS PER MATCH
PATTERN ( ^ a_status+ )
DEFINE a_status AS status = 'A'
)
Which, for the sample data:
CREATE TABLE table_name (id, start_date, end_date, status) AS
SELECT 1000000278, DATE '2021-08-25', DATE '2022-08-25', 'I' FROM DUAL UNION ALL
SELECT 1000000278, DATE '2022-08-25', DATE '2023-08-25', 'A' FROM DUAL UNION ALL
SELECT 1000000284, DATE '2021-08-20', DATE '2022-08-25', 'A' FROM DUAL UNION ALL
SELECT 1000000284, DATE '2022-08-25', DATE '2023-08-25', 'A' FROM DUAL UNION ALL
SELECT 1000000285, DATE '2024-08-20', DATE '2028-08-20', 'A' FROM DUAL UNION ALL
SELECT 1000000285, DATE '2028-08-21', DATE '2030-08-20', 'I' FROM DUAL UNION ALL
SELECT 1000000285, DATE '2030-08-21', DATE '2031-08-20', 'A' FROM DUAL UNION ALL
SELECT 1000000286, DATE '2021-08-25', DATE '2022-08-25', 'A' FROM DUAL;
Outputs:
ID
START_DATE
END_DATE
STATUS
1000000284
2021-08-20 00:00:00
2022-08-25 00:00:00
A
1000000284
2022-08-25 00:00:00
2023-08-25 00:00:00
A
1000000285
2024-08-20 00:00:00
2028-08-20 00:00:00
A
1000000286
2021-08-25 00:00:00
2022-08-25 00:00:00
A
fiddle
We have following table to query data from,
EMPLOYEE_ABSENCE_TAB
emp_id
absence_id
from_date
to_date
absence_continuation
100
1
01/01/2022
03/01/2022
100
2
01/02/2022
05/02/2022
1
200
3
01/01/2022
07/01/2022
200
4
10/01/2022
14/01/2022
3
200
5
16/01/2022
20/01/2022
4
300
6
01/01/2022
14/01/2022
We need to connect data hierarchically based on the following logic.
Output should be emp_id, absence_days, from_date and to_date.
absence_days : number of absence days = sum(to_date - from_date)
from_date : first from_date where absence_continuation is NULL
to_date : last to_date connect by prior absence_id = absence_continuation
Expected Outcome
emp_id
absence_days
from_date
to_date
100
8
01/01/2022
05/02/2022
200
17
01/01/2022
20/01/2022
300
14
01/01/2022
14/01/2022
I tried hierarchical but could able to come up with working solution. How can this be achieved using hierarchical query in Oracle SQL?
You don't need to consider the hierarchy in the data (assuming that there are no overlaps); you just need to aggregate:
SELECT emp_id,
SUM(to_date - from_date + 1) AS absence_days,
MIN(from_date) AS from_date,
MAX(to_date) AS to_date
FROM EMPLOYEE_ABSENCE_TAB
GROUP BY emp_id
Which, for the sample data:
CREATE TABLE EMPLOYEE_ABSENCE_TAB
(emp_id, absence_id, from_date, to_date, absence_continuation) AS
SELECT 100, 1, DATE '2022-01-01', DATE '2022-01-03', NULL FROM DUAL UNION ALL
SELECT 100, 2, DATE '2022-02-01', DATE '2022-02-05', 1 FROM DUAL UNION ALL
SELECT 200, 3, DATE '2022-01-01', DATE '2022-01-07', NULL FROM DUAL UNION ALL
SELECT 200, 4, DATE '2022-01-10', DATE '2022-01-14', 3 FROM DUAL UNION ALL
SELECT 200, 5, DATE '2022-01-16', DATE '2022-01-20', 4 FROM DUAL UNION ALL
SELECT 300, 6, DATE '2022-01-01', DATE '2022-01-14', NULL FROM DUAL;
Outputs:
EMP_ID
ABSENCE_DAYS
FROM_DATE
TO_DATE
100
8
2022-01-01 00:00:00
2022-02-05 00:00:00
200
17
2022-01-01 00:00:00
2022-01-20 00:00:00
300
14
2022-01-01 00:00:00
2022-01-14 00:00:00
If you did want to consider the hierarchy and get the duration of each absence (and not the total absences per employee) then you can use:
SELECT emp_id,
absence_id,
SUM(to_date - from_date + 1) AS absence_days,
MIN(from_date) AS from_date,
MAX(to_date) AS to_date
FROM (
SELECT emp_id,
CONNECT_BY_ROOT absence_id AS absence_id,
to_date - from_date + 1 AS absence_days,
from_date,
to_date
FROM EMPLOYEE_ABSENCE_TAB
START WITH absence_continuation IS NULL
CONNECT BY PRIOR absence_id = absence_continuation
)
GROUP BY
emp_id,
absence_id
Which outputs the same (with an additional absence_id column for the id of the first part of the absence) for your sample data as you only have a single absence for each employee; if you had more absences then it would aggregate each absence separately.
db<>fiddle here
I have a problem with fetching few exceptions from DB.
Example, table b:
sn
v_num
start_date
end_date
1
001
01-01-2019
31-12-2099
1
002
01-01-2021
31-01-2022
1
003
01-02-2022
31-12-2099
2
001
01-01-2022
31-12-2099
2
002
01-07-2022
31-07-2022
2
003
01-08-2022
31-12-2099
Expected output:
sn
v_num
start_date
end_date
1
003
01-02-2022
31-12-2099
2
001
01-01-2022
31-12-2099
Currently I'm here:
SELECT * FROM table a, table b
WHERE a.sn = b.sn
AND b.v_num = (SELECT max (v_num) FROM b WHERE a.sn = b.sn)
but obviously that is not good because of a few cases like this with sn = 2.
Conclusion, I need to get unique sn record where v_num is max (95% of them in DB) except in case if start_date of max v_num record is > today.
Filter using start_date <= TRUNC(SYSDATE) then use the ROW_NUMBER analytic function:
SELECT *
FROM (
SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY sn ORDER BY v_num DESC) AS rn
FROM "TABLE" a
WHERE start_date <= TRUNC(SYSDATE)
)
WHERE rn = 1;
If the start_date has a time component then you can use start_date < TRUNC(SYSDATE) + INTERVAL '1' DAY to get all the values for today from 00:00:00 to 23:59:59.
If you can have ties for the maximum and want to return all the ties then you can use the RANK analytic function instead of ROW_NUMBER.
Which, for the sample data:
CREATE TABLE "TABLE" (sn, v_num, start_date, end_date) AS
SELECT 1, '001', DATE '2022-01-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 1, '002', DATE '2022-01-01', DATE '2022-01-31' FROM DUAL UNION ALL
SELECT 1, '003', DATE '2022-02-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 2, '001', DATE '2022-01-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 2, '002', DATE '2022-07-01', DATE '2022-07-31' FROM DUAL UNION ALL
SELECT 2, '003', DATE '2022-08-01', DATE '2099-12-31' FROM DUAL;
Outputs:
SN
V_NUM
START_DATE
END_DATE
RN
1
003
2022-02-01 00:00:00
2099-12-31 00:00:00
1
2
001
2022-01-01 00:00:00
2099-12-31 00:00:00
1
db<>fiddle here
For example, I am having a table name test_cross_months and the data is as below :
id
start_date
end_date
44
2020-01-04
2020-01-04
44
2020-01-30
2020-02-10
44
2020-02-27
2020-03-03
Expected result:
id
start_date
end_date
44
2020-01-04
2020-01-04
44
2020-01-30
2020-01-31
44
2020-02-01
2020-02-10
44
2020-02-27
2020-02-29
44
2020-03-01
2020-03-03
So for
|44|2020-01-30 |2020-02-10|
there should be two rows that are from 30-Jan-2020 to 31-Jan-2020 and 1-Feb-2020 to 10-Feb-2020
I tried by comparing the end date with the last day for the start_date but facing issues as a new row is not getting created for the end_date range.
Could any please suggest a solution?
You can use a recursive query (which will work regardless of how many months your ranges span):
WITH months ( id, start_date, end_date, final_date ) AS (
SELECT id,
start_date,
LEAST( LAST_DAY( start_date ), end_date ),
end_date
FROM table_name
UNION ALL
SELECT id,
end_date + INTERVAL '1' DAY,
LEAST( ADD_MONTHS( end_date, 1 ), final_date ),
final_date
FROM months
WHERE end_date < final_date
)
SEARCH DEPTH FIRST BY final_date SET dt_order
SELECT id,
start_date,
end_date
FROM months;
Which, for the sample data:
CREATE TABLE table_name (id, start_date, end_date) AS
SELECT 44, DATE '2020-01-04', DATE '2020-01-04' FROM DUAL UNION ALL
SELECT 44, DATE '2020-01-30', DATE '2020-02-10' FROM DUAL UNION ALL
SELECT 44, DATE '2020-02-27', DATE '2020-03-03' FROM DUAL;
Outputs:
ID
START_DATE
END_DATE
44
2020-01-04 00:00:00
2020-01-04 00:00:00
44
2020-01-30 00:00:00
2020-01-31 00:00:00
44
2020-02-01 00:00:00
2020-02-10 00:00:00
44
2020-02-27 00:00:00
2020-02-29 00:00:00
44
2020-03-01 00:00:00
2020-03-03 00:00:00
db<>fiddle here
Using a table of numbers and date arithmetic
-- example of table of numbers
with nmbrs(n) as(
select 0 from dual union all
select 1 from dual union all
select 2 from dual
)
select t.id,
case when n=0 then t.start_date else trunc(t.start_date, 'MM') + NUMTOYMINTERVAL(n, 'MONTH') end s,
case when n=MONTHS_BETWEEN(last_day(t.end_date), last_day(t.start_date)) then t.end_date
else last_day(t.start_date + NUMTOYMINTERVAL(n, 'MONTH')) end e
from test_cross_months t
join nmbrs on nmbrs.n <= MONTHS_BETWEEN(last_day(t.end_date), last_day(t.start_date))
order by t.id, s
db<>fiddle
ID EFF_DT END_DT
FLA1 2018-01-01 00:00:00 2019-12-31 00:00:00
FLA1 2020-01-01 00:00:00 9999-12-31 00:00:00
The above structure needs to be splited. And the split should be based on the date.
the output should have additional column as year
ID EFF_DT END_DT YEAR
FLA1 2018-01-01 00:00:00 2019-12-31 00:00:00 2019
FLA1 2020-01-01 00:00:00 2020-12-31 00:00:00 2020
FLA1 2021-01-01 00:00:00 9999-12-31 00:00:00 2021
I am using union for this purpose and it is generating duplicates. Any other approach / refine solution will work. Thanks in advance.
You can use a recursive sub-query factoring clause:
WITH split ( ID, EFF_DT, END_DT, MAX_DT ) AS (
SELECT id,
eff_dt,
LEAST(
ADD_MONTHS( TRUNC( SYSDATE, 'YY' ), 12 ) - INTERVAL '1' DAY,
end_dt
),
end_dt
FROM table_name
UNION ALL
SELECT id,
end_dt + INTERVAL '1' DAY,
max_dt,
max_dt
FROM split
WHERE end_dt < max_dt
)
SELECT id,
eff_dt,
end_dt
FROM split;
Which, for your sample data:
CREATE TABLE table_name ( ID, EFF_DT, END_DT ) AS
SELECT 'FLA1', DATE '2018-01-01', DATE '2019-12-31' FROM DUAL UNION ALL
SELECT 'FLA1', DATE '2020-01-01', DATE '9999-12-31' FROM DUAL;
Outputs:
ID | EFF_DT | END_DT
:--- | :------------------ | :------------------
FLA1 | 2018-01-01 00:00:00 | 2019-12-31 00:00:00
FLA1 | 2020-01-01 00:00:00 | 2020-12-31 00:00:00
FLA1 | 2021-01-01 00:00:00 | 9999-12-31 00:00:00
db<>fiddle here
If you want to generate all years of data, then:
with cte (id, eff_dt, end_dt, orig_end_dt)
select id, eff_dt, end_dt, end_dt
from t
union all
select cte.id, end_dt + interval '1' day,
least(orig_end_dte, trunc(end_dt, 'YYYY') + interval '1' year
from cte
where trunc(eff_dt, 'YYYY') < trunc(end_dt, 'YYYY')
)
select id, eff_dt, end_dt, to_char(end_dt, 'YYYY') as year
from cte;
Note: This produces a separate row for every year in the period.
If you want a limit on the year, then it would be something like this:
with cte (id, eff_dt, end_dt, orig_end_dt)
select id, eff_dt, end_dt, end_dt
from t
union all
select cte.id, end_dt + interval '1' day,
least(orig_end_dte, trunc(end_dt, 'YYYY') + interval '1' year
from cte
where trunc(eff_dt, 'YYYY') < least(trunc(end_dt, 'YYYY'), date '2021-01-01')
)
select id, eff_dt,
(case when end_dt = date '2021-12-31' then orig_end_dt else end_dt end),
to_char(end_dt, 'YYYY') as year
from cte;