Oracle SQL Check current rows with previous rows dynamically - sql

I am trying to work on something in Oracle SQL in the attached table.
The goal is to define a Visit Type where a Primary Visit refers to any visits that happened after 3 months from previous visit(s). However, the challenge is that sometimes I'll need to compare to previous row and sometimes I need to compare with previous N rows.
For e.g., Transaction ID 3 is a revisit because its start date is within the 'end date +90 days' of Transaction ID 1 (Dec 16). Transaction ID 4 is primary because it happened after it took place after the the very first 'end date+90 days', meaning I not only need to compare to previous 1 row but previous 3 rows.
Hope this is clear!
Thanks.
See Details above. Thank you!

From Oracle 12 you can use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT transaction_id, start_date, end_date, visit_type
FROM table_name
MATCH_RECOGNIZE(
ORDER BY start_date
MEASURES
CLASSIFIER() AS visit_type
ALL ROWS PER MATCH
PATTERN (PRIMARY revisit*)
DEFINE revisit AS start_date <= FIRST(end_date) + INTERVAL '90' DAY
);
Which, for the sample data:
CREATE TABLE table_name (transaction_id, start_date, end_date) AS
SELECT 1, DATE '2020-08-05', DATE '2020-09-07' FROM DUAL UNION ALL
SELECT 2, DATE '2020-09-19', DATE '2020-10-27' FROM DUAL UNION ALL
SELECT 3, DATE '2020-11-01', DATE '2020-12-19' FROM DUAL UNION ALL
SELECT 4, DATE '2021-01-23', DATE '2021-01-26' FROM DUAL UNION ALL
SELECT 5, DATE '2021-02-27', DATE '2021-03-27' FROM DUAL;
Outputs:
TRANSACTION_ID
START_DATE
END_DATE
VISIT_TYPE
1
2020-08-05 00:00:00
2020-09-07 00:00:00
PRIMARY
2
2020-09-19 00:00:00
2020-10-27 00:00:00
REVISIT
3
2020-11-01 00:00:00
2020-12-19 00:00:00
REVISIT
4
2021-01-23 00:00:00
2021-01-26 00:00:00
PRIMARY
5
2021-02-27 00:00:00
2021-03-27 00:00:00
REVISIT

Related

Order Data based on previous row data

I have a query in Oracle SQL. My query gives three columns: old_data ,new_data and transaction_date. I want to sort this data primarily based on increasing transaction_date and secondly in such a way that new_data of previous row equals old_data of next row. Both new_data and old_date are number fields that can decrease or increase.
If I sort just by transaction_date, some data has the same exact date and time and hence the order will not be accurate as I need new_data of previous row to match old_data of current row. I also cannot use a hierarchical query alone to meet the second sorting condition since transaction_date sorting is the primary sorting condition.
Can anyone suggest a solution?
A sample output will need to look like below:
output_sample
Thanks in advance
You could use a hierarchical query and connect by equal dates as well as the relationship between old- and new-data:
SELECT transaction_date,
new_data,
old_data
FROM table_name
START WITH old_data IS NULL -- You need to define how to pick the first row
CONNECT BY
PRIOR transaction_date = transaction_date
AND PRIOR new_data = old_date
ORDER SIBLINGS BY
transaction_date
Which, for the sample data:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
CREATE TABLE table_name ( transaction_date, new_data, old_data ) AS
SELECT DATE '2022-01-01', 1, NULL FROM DUAL UNION ALL
SELECT DATE '2022-01-01', 2, 1 FROM DUAL UNION ALL
SELECT DATE '2022-01-01', 3, 2 FROM DUAL UNION ALL
SELECT DATE '2022-01-02', 3, NULL FROM DUAL UNION ALL
SELECT DATE '2022-01-02', 1, 3 FROM DUAL UNION ALL
SELECT DATE '2022-01-02', 2, 1 FROM DUAL UNION ALL
SELECT DATE '2022-01-03', 4, NULL FROM DUAL;
Outputs:
TRANSACTION_DATE
NEW_DATA
OLD_DATA
2022-01-01 00:00:00
1
null
2022-01-01 00:00:00
2
1
2022-01-01 00:00:00
3
2
2022-01-02 00:00:00
3
null
2022-01-02 00:00:00
1
3
2022-01-02 00:00:00
2
1
2022-01-03 00:00:00
4
null
fiddle
Update
Given the updated sample data:
CREATE TABLE table_name ( transaction_date, old_data, new_data ) AS
SELECT DATE '2021-12-20'+INTERVAL '11:25' HOUR TO MINUTE, 0, 115.09903 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:25' HOUR TO MINUTE, 115.09903, 115.13233 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:25' HOUR TO MINUTE, 115.13233, 115.16490 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:25' HOUR TO MINUTE, 115.16490, 115.19678 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:35' HOUR TO MINUTE, 115.19678, 115.22799 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:35' HOUR TO MINUTE, 115.22799, 115.25854 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:35' HOUR TO MINUTE, 115.25854, 115.28846 FROM DUAL UNION ALL
SELECT DATE '2021-12-20'+INTERVAL '11:35' HOUR TO MINUTE, 115.28846, 115.31776 FROM DUAL;
Then, since both old_data and new_data increase with time, you could use:
SELECT *
FROM table_name
ORDER BY old_data;
or:
SELECT *
FROM table_name
ORDER BY new_data;
or, if you want to use the hierarchy then:
SELECT transaction_date,
old_data,
new_data
FROM table_name
START WITH old_data = 0
CONNECT BY
PRIOR transaction_date <= transaction_date
AND PRIOR new_data = old_data
ORDER SIBLINGS BY
transaction_date
Which all output:
TRANSACTION_DATE
OLD_DATA
NEW_DATA
2021-12-20 11:25:00
0
115.09903
2021-12-20 11:25:00
115.09903
115.13233
2021-12-20 11:25:00
115.13233
115.1649
2021-12-20 11:25:00
115.1649
115.19678
2021-12-20 11:35:00
115.19678
115.22799
2021-12-20 11:35:00
115.22799
115.25854
2021-12-20 11:35:00
115.25854
115.28846
2021-12-20 11:35:00
115.28846
115.31776
fiddle

Getting last 4 months data from given date column some months data is midding

I have below data
Record_date ID
28-feb-2022 xyz
31-Jan-2022 ABC
30-nov-2022 jkl
31-oct-2022 dcs
I want to get last 3 months data from given date column. We don't have to consider the missing month.
Output should be:
Record_date ID
28-feb-2022 xyz
31-Jan-2022 ABC
30-nov-2022 jkl
In the last 3 months Dec is missing but we have to ignore it as the data is not available. Tried many things but not working.
Any suggestions?
Assuming you are using Oracle then you can use Oralce ADD_MONTHS function and filter the data.
--- untested
-- Assumption Record_date is a date column
SELECT * FROM table1
where Record_date > ADD_MONTHS(SYSDATE, -3)
To get the data for the three months that are latest in the table, you can use:
SELECT record_date,
id
FROM (
SELECT t.*,
DENSE_RANK() OVER (ORDER BY TRUNC(Record_date, 'MM') DESC) AS rnk
FROM table_name t
)
WHERE rnk <= 3;
Which, for the sample data:
CREATE TABLE table_name (Record_date, ID) AS
SELECT DATE '2022-02-28', 'xyz' FROM DUAL UNION ALL
SELECT DATE '2022-01-31', 'ABC' FROM DUAL UNION ALL
SELECT DATE '2022-11-30', 'jkl' FROM DUAL UNION ALL
SELECT DATE '2022-10-31', 'dcs' FROM DUAL;
Outputs:
RECORD_DATE
ID
2022-11-30 00:00:00
jkl
2022-10-31 00:00:00
dcs
2022-02-28 00:00:00
xyz
db<>fiddle here

Oracle How to list last days of months between 2 dates

I manage to get all the days between 2 dates.
But I would like to get all the lasts day of months between 2 dates (using one request).
All days between 2 dates:
select to_date('01/01/2000','dd/mm/yyyy') + (LEVEL-1) as jour
from dual
connect by level <= to_date('31/12/2050','dd/mm/yyyy')-to_date('01/01/2000','dd/mm/yyyy')
Last day of current month:
select LAST_DAY(sysdate) FROM dual
I don't know how to mix both and get the expected result:
20000131
20000228
20000331
etc...
That would be DISTINCT + LAST_DAY, I presume.
Setting date format (so that it matches yours; alternatively, apply TO_CHAR to the jour value with appropriate format mask):
SQL> alter session set nls_Date_format = 'yyyymmdd';
Session altered.
I shortened time span to 2 years (to save space :)).
SQL> select distinct last_day(to_date('01/01/2000','dd/mm/yyyy') + (LEVEL-1)) as jour
2 from dual
3 connect by level <= to_date('31/12/2002','dd/mm/yyyy')-to_date('01/01/2000','dd/mm/yyyy')
4 order by 1;
JOUR
--------
20000131
20000229
20000331
20000430
20000531
20000630
20000731
20000831
<snip>
20020630
20020731
20020831
20020930
20021031
20021130
20021231
36 rows selected.
SQL>
I like to use standard recursive queries rather than Oracle's specific CONNECT BY syntax. Here, you could enumerate the start of months, then offset to the end of months:
with cte (dt) as (
select date '2020-01-01' dt from dual
union all
select dt + interval '1' month from cte where dt + interval '1' month < date '2051-01-01'
)
select last_day(dt) dt from cte order by dt
Note that this uses standard date literals (date 'YYYY-MM-DD') rather than to_date() - this makes the query shorter, and, again, more standard.
Demo on DB Fiddle:
| DT |
| :-------- |
| 31-JAN-20 |
| 29-FEB-20 |
| 31-MAR-20 |
| 30-APR-20 |
| 31-MAY-20 |
...
| 31-OCT-50 |
| 30-NOV-50 |
| 31-DEC-50 |
You can do this with a CONNECT BY query. (You can also do it with a recursive query, like GMB has proposed, but it would have to be adapted to solve the problem you posed - it should allow for input start and end date, and it should return zero rows if there are no ends of month between the two dates.)
In the query below I use a WITH clause to give the start and end date. More likely, in your problem they are bind variables. (Or are they read from a table?)
Pay attention to the START WITH clause. The CONNECT BY condition is applied only to levels 2 and above; you need the START WITH condition for level=1, for the case when there are NO ends of month between the give dates (such as, between 10 January and 23 January of the same year).
with
input_dates(start_dt, end_dt) as (
select date '2020-01-22', date '2020-04-03' from dual
)
select add_months(last_day(start_dt), level - 1) as eom
from input_dates
start with last_day(start_dt) <= end_dt
connect by add_months(last_day(start_dt), level - 1) <= end_dt
;
EOM
----------
2020-01-31
2020-02-29
2020-03-31
Your initial query just needs some adjustment. Instead of just level-1 (which winds up do daily) convert it to a monthly increment "level-1) * interval '1' month. Then for the connect by just get month_between the desired date. Note: I have converted to ISO standard format for dates instead of to_date function. Makes query shorter and easier to read.
select last_day(date '2000-01-01' + (level-1)*interval '1' month) as jour
from dual
connect by level <= 1+months_between(date '2050-12-31',date '2000-01-01');
You can use a recursive sub-query.
This will work for:
Multiple input ranges;
Inputs when the start date is at the end of the month;
When the range does not contain the end of any month.
WITH input_ranges ( start_date, end_date ) AS (
-- Should return a single row.
SELECT DATE '2020-01-31', DATE '2020-02-01' FROM DUAL UNION ALL
-- Should return multiple rows.
SELECT DATE '2021-02-01', DATE '2021-06-01' FROM DUAL UNION ALL
-- Should not return any rows as there is no end of the month in the range.
SELECT DATE '2021-10-06', DATE '2021-10-20' FROM DUAL UNION ALL
-- Should work even though February does not have 30 days.
SELECT DATE '2022-01-30', DATE '2022-03-02' FROM DUAL
),
month_ends ( month_end, end_date ) AS (
SELECT LAST_DAY( start_date ),
end_date
FROM input_ranges
WHERE LAST_DAY( start_date ) <= end_date
UNION ALL
SELECT ADD_MONTHS( month_end, 1 ),
end_date
FROM month_ends
WHERE ADD_MONTHS( month_end, 1 ) <= end_date
)
SELECT month_end
FROM month_ends
ORDER BY month_end;
Which outputs:
| MONTH_END |
| :------------------ |
| 2020-01-31 00:00:00 |
| 2021-02-28 00:00:00 |
| 2021-03-31 00:00:00 |
| 2021-04-30 00:00:00 |
| 2021-05-31 00:00:00 |
| 2022-01-31 00:00:00 |
| 2022-02-28 00:00:00 |
db<>fiddle here

Finding overlapping and calculation of min and max dates with in overlap in oracle [duplicate]

This question already has answers here:
print start and end date in one row for continous or overlaping date ranges in oracle SQL
(2 answers)
Closed 2 years ago.
How can we find the between the overlap lap b/w the dates .
overlap means when start date and end date are within same range
for below example row 1 has no over lap.
Row 2to 5 can be considered as one set of over lap as there start date and end are over lap with themselves
Row 6 & 7 can be considered as one set of over lap
for eg. row 6 & 7 --> start date of row 7 is in same range with respect to end date of row 6
so it becomes an overlap
Once overlap is found then and need to find out min(start date) and max(end date) and
want to assign a unique id to each overlap and in the S.NO column should show which rows are overlapped .Below is the I/p and O/p
I/p
You can do it simply and efficiently using MATCH_RECOGNIZE to perform a row-by-row comparison and aggregation:
SELECT id, start_date, end_date
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY start_date
MEASURES FIRST(start_date) AS start_date,
MAX(end_date) AS end_date
PATTERN ( overlapping_dates* last_date )
DEFINE overlapping_dates as MAX(end_date) >= NEXT(start_date)
);
Which, for the sample data:
CREATE TABLE table_name ( sno, id, start_date, end_date ) AS
SELECT 1, 1, DATE '2019-10-11', DATE '2019-10-11' FROM DUAL UNION ALL
SELECT 2, 1, DATE '2019-11-04', DATE '2019-12-11' FROM DUAL UNION ALL
SELECT 3, 1, DATE '2019-11-05', DATE '2019-11-10' FROM DUAL UNION ALL
SELECT 4, 1, DATE '2019-11-06', DATE '2019-11-10' FROM DUAL UNION ALL
SELECT 5, 1, DATE '2019-11-20', DATE '2019-12-20' FROM DUAL UNION ALL
SELECT 6, 1, DATE '2020-01-01', DATE '2020-01-20' FROM DUAL UNION ALL
SELECT 7, 1, DATE '2020-01-15', DATE '2020-03-25' FROM DUAL;
Outputs:
ID | START_DATE | END_DATE
-: | :------------------ | :------------------
1 | 2019-10-11 00:00:00 | 2019-10-11 00:00:00
1 | 2019-11-04 00:00:00 | 2019-12-20 00:00:00
1 | 2020-01-01 00:00:00 | 2020-03-25 00:00:00
db<>fiddle here

Number of days in a month

I have a monthly amount that I need to spread equally over the number of days in the month. The data looks like this:
Month Value
----------- ---------------
01-Jan-2012 100000
01-Feb-2012 121002
01-Mar-2012 123123
01-Apr-2012 118239
I have to spread the Jan amount over 31 days, the Feb amount over 29 days and the March amount over 31 days.
How can I use PL/SQL to find out how many days there are in the month given in the month column?
SELECT CAST(to_char(LAST_DAY(date_column),'dd') AS INT)
FROM table1
Don't use to_char() and stuff when doing arithmetics with dates.
Strings are strings and dates are dates. Please respect the data types and use this instead:
1+trunc(last_day(date_column))-trunc(date_column,'MM')
Indeed, this is correct. It computes the difference between the value of the last day of the month and the value of the first (which is obviously always 1 and therefore we need to add this 1 again).
You must not forget to use the trunc() function if your date columns contains time, because last_day() preserves the time component.
SELECT EXTRACT(DAY FROM LAST_DAY(SYSDATE)) num_of_days FROM dual;
/
SELECT SYSDATE, TO_CHAR(LAST_DAY(SYSDATE), 'DD') num_of_days FROM dual
/
-- Days left in a month --
SELECT SYSDATE, LAST_DAY(SYSDATE) "Last", LAST_DAY(SYSDATE) - SYSDATE "Days left"
FROM DUAL
/
You can add a month and subtract the two dates
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select date '2012-01-01' dt from dual union all
3 select date '2012-02-01' from dual union all
4 select date '2012-03-01' from dual union all
5 select date '2012-01-31' from dual
6 )
7 select dt, add_months(trunc(dt,'MM'),1) - trunc(dt,'MM')
8* from x
SQL> /
DT ADD_MONTHS(TRUNC(DT,'MM'),1)-TRUNC(DT,'MM')
--------- -------------------------------------------
01-JAN-12 31
01-FEB-12 29
01-MAR-12 31
31-JAN-12 31
select add_months(my_date, 1)-my_date from dual;
SELECT TO_CHAR(LAST_DAY(SYSDATE), 'fmday-mon-rr dd') as LastDayOfMonth
FROM dual;
Use the following Oracle query:
select to_number(to_char(last_day(sysdate),'dd')) TotalDays from dual
Date_Parameter='01-Oct-2017'
select to_number(to_char(last_day('Date_Parameter'),'dd')) TotalDays from dual