Here is an example of data (I'm just sharing an example of one person to hep illustrate my question):
ID
DATE
1
January 1, 2021
1
February 1, 2021
1
March 1, 2021
1
April 1, 2021
1
May 1, 2021
1
June 1, 2021
ID = a unique identifier for a person
DATE = when that person enrolled in a program
A person can enroll in a program for up to 40 days. After that period is up, they become eligible for enrollment again. However, sometimes (for various administrative purposes) someone may enter an enrollment for someone before they are eligible. I am trying to figure out the best way to flag which enrollments were actually eligible.
So for example, the January enrollment is fine because that was the member's first enrollment (so it should be flagged). However, their February enrollment did not occur after 40 days of the prior eligible enrollment (from January), and it should not be flagged. The March enrollment did occur more than 40 days after the eligible January enrollment, so it should be flagged. The April enrollment did not occur after 40 days of the prior eligible enrollment (March) and so it should not be flagged......
I suspect I need to create some sort of loop (I would know how to do this in other programming languages but not SQL. My thinking is along the lines of:
January is the first enrollment so it should be flagged and move to the next row (January stored as the date reference).
February did not occur more than 40 days after the January reference so it should not be flagged, move to the next row (January still the reference).
March did occur more than 40 days after the January reference so it should be flagged, move to the next row (March is now the reference)
.....
Can anyone help me figure out how I could accomplish this?
Here is a brief demo using match_recognize (which has been available since Oracle 12.1). It takes some time to learn it if you don't know it already; if you are familiar with analytic functions and with regular expressions (for strings), the learning will be easier.
First some test data (creating a small table):
create table enrolments (id, enrolment_date) as
select 1, date '2021-01-01' from dual union all
select 1, date '2021-02-01' from dual union all
select 1, date '2021-03-01' from dual union all
select 1, date '2021-04-01' from dual union all
select 1, date '2021-05-01' from dual union all
select 1, date '2021-06-01' from dual union all
select 8, date '2021-01-05' from dual union all
select 8, date '2021-03-10' from dual union all
select 8, date '2021-03-15' from dual union all
select 8, date '2021-04-10' from dual
;
A few notes here - DATE is a reserved keyword, so it can't be a column name (I hope it isn't in your real-life data); in my example the date column is date data type (and I hope it is in your real-life data too, rather than being string data type); and I used the date literal syntax to insert dates.
Then the query and output (and a command to change date formatting in the output to match yours):
alter session set nls_date_format = 'fmMonth dd, yyyy';
select id, enrolment_date, flag
from enrolments
match_recognize(
partition by id
order by enrolment_date
measures nullif(classifier(), 'NOT_ELIGIBLE') as flag
all rows per match
pattern ( ELIGIBLE NOT_ELIGIBLE* )
define NOT_ELIGIBLE as enrolment_date < ELIGIBLE.enrolment_date + 40
);
ID ENROLMENT_DATE FLAG
-- ----------------- -----------
1 January 1, 2021 ELIGIBLE
1 February 1, 2021
1 March 1, 2021 ELIGIBLE
1 April 1, 2021
1 May 1, 2021 ELIGIBLE
1 June 1, 2021
8 January 5, 2021 ELIGIBLE
8 March 10, 2021 ELIGIBLE
8 March 15, 2021
8 April 10, 2021
Related
I need to filter an Oracle SQL query on persons who turned age 13 during the calendar year of 2019 - in other words, persons who had their 13th birthday sometime in 2019. Anyone know the code for that? Thanks in advance.
Assuming your table has a date_of_birth column this would work (2006 being 2019-13):
select *
from your_table
where to_number(extract(year from date_of_birth)) = 2006;
I notice your question title has a different year value from its body, so perhaps we need to hand over the troublesome maths to SQL:
select *
from your_table
where date '2019-01-01' = trunc(date_of_birth, 'yyyy') + interval '21' year
/
Truncating the DOB with a format mask converts all the dates to the first of January for that year. We then add an interval of 21 years. Comparing that value to the first of January 2019 will give you everybody who turned 21 last year.
I would use between clause with date_of_birth + 21 year to let oracle use index on date_of_birth, if any as following:
select *
from your_table
where date_of_birth + interval '21' year between date '2019-01-01' and date '2019-12-31';
Cheers!!
Can someone help me understand the working of Oracle Months_Between Function?
If I query select MONTHS_BETWEEN('02-28-2015', '01-28-2015')
I get an integer value of 1 but if I query
select MONTHS_BETWEEN('02-28-2015', '01-29-2015') I get 0.96.
Refer to the documentation. https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions089.htm
Note - the "31 day month" convention may cause weird results around month-ends. Consider:
select months_between(date '2016-07-02', date '2016-07-01') as one_day,
months_between(date '2016-07-01', date '2016-06-30') as another_day
from dual;
ONE_DAY ANOTHER_DAY
---------- -----------
.032258065 .064516129
1 row selected.
As if June had 31 days. It doesn't, but months_between treats it as though it did.
If you're working with just trying to determine the number of months in a set of months and don't care about the days. I find myself in this situation often... You can do a bit of date manipulation which is rather reliable for determining the number of months in a set of months. Say for instance Jul - Sep while starting with dates.
Thusly:
WITH MONTHS AS (
SELECT
SYSDATE DATE_ONE
, SYSDATE+57 DATE_TWO
FROM DUAL
)
SELECT
m.*
,TO_CHAR(m.DATE_ONE,'MON') START_MONTH
,TO_CHAR(m.DATE_TWO,'MON') END_MONTH
,MONTHS_BETWEEN(m.DATE_TWO,m.DATE_ONE) UNEXPECTED_RESULT
,MONTHS_BETWEEN(LAST_DAY(m.DATE_TWO),LAST_DAY(ADD_MONTHS(m.DATE_ONE,-1))) EXPECTED_RESULT
FROM MONTHS m
;
I'm currently trying to do a comparison in my select. If the current date is before August 1st of the current year then display august 1st of the last year, otherwise display august 1st of this year. Essentially I'm trying to do:
CASE
WHEN (SYSDATE < 08/01/2015) THEN
08/01/2014
ELSE
08/01/2015
But I am at a loss as to how to get august for the month. So far I have:
TRUNC(SYSDATE, 'MON')
To get /01/ but how would I get it to constantly return august as the month? Would it be better to hardcode in the date and month and dynamically get the year instead? like 01/08/
Try something like this:
1 select sysdate,
2 trunc(sysdate,'YEAR'),
3 add_months(trunc(sysdate,'YEAR'),7),
4 add_months(trunc(sysdate,'YEAR'),7-12)
5* from dual
SQL> /
SYSDATE TRUNC(SYSDA ADD_MONTHS( ADD_MONTHS(
----------- ----------- ----------- -----------
31-jul-2015 01-jan-2015 01-aug-2015 01-aug-2014
SQL>
the columns are:
1) pulling the current sysdate.
2) converting to the first day of the year.
3) adding 7 months to get Aug 1 of current year.
4) -12 months to get Aug 1 of last year.
(that shows you the usage, you can figure out how to plug those suckers into your CASE statement ;) )
I am trying to set between a date range for 6 months in the past for two different fields that will group the data by month. How do I set such a between clause to achieve this?
SELECT TO_CHAR(mopend, 'MM-yyyy') AS month, MOPSTATUS, COUNT(*) MTS_COMPLETE_CNT
FROM MOPUSER.MOPACTIVITY
WHERE UPPER(MOPSTATUS) = 'COMPLETE'
AND TO_CHAR(MOPACTIVITY.MOPSTART, 'yyyy-mm-dd hh24:mi') BETWEEN TO_CHAR(sysdate,'YYYY-MM-DD')||' 06:02:00' AND TO_CHAR(sysdate,'YYYY-MM-DD')||' 22:59:59'
OR TO_CHAR(MOPACTIVITY.MOPEND, 'yyyy-mm-dd hh24:mi') BETWEEN TO_CHAR(SYSDATE,'YYYY-MM-DD')||' 06:02:00' AND TO_CHAR(SYSDATE,'YYYY-MM-DD')||' 22:59:59'
GROUP BY TO_CHAR(mopend, 'MM-yyyy'), MOPSTATUS
ORDER BY TO_CHAR(mopend, 'MM-yyyy'), MOPSTATUS
I will answer one part of your question first, and then based on your comments, I can give you the full query.
The following query returns the end points between which you want to search. T1 is 06:02 in the morning on the date that is six months back in time. T2 is the last second of today.
select sysdate
,add_months( trunc(sysdate) + interval '06:02' hour to minute, -6) as t1
, trunc(sysdate) + interval '23:59:59' hour to second as t2
from dual;
The above query returns the following (using yyyy-mm-dd hh24:mi:ss):
sydate: 2014-04-11 13:54:28
t1: 2013-10-11 06:02:00
t2: 2014-04-11 23:59:59
If I interpret you correctly, this is the time period you want to search?
For the second part of the answer, I'd need to know the following:
Can any of MOPSTART or MOPEND be null? If so, how do you want to treat those rows?
Do you want to include the end points, i.e. rows where MOPSTART >= t1? Or only where MOTSTART > t1?
Same as (2) but for MOPEND
What month do you want to group by (see below)?
For example, row (a), do you want count it once for each month, or only in JAN (started) or only in JUN(ended)?
JAN FEB MAR APR MAY JUN
a: |-------------------|
b: |---|---|
c: |---|
d: |-----------|
e: |--------|
I'm trying to figure out how to compare the result of a date substraction in a where clause.
Clients subscribed to a service and therefore are linked to a subscription that has an end date. I want to display the list of subscriptions that will come to an end within the next 2 weeks.
I did not designed the databse but noticed that the End_Date column type is a varchar and not a date.. I can't change that.
My problem is the following:
If I try to select the result of the substraction for example with this request:
SELECT(TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) , s.name
from SUBSCRIPTION s WHERE s.id_acces = 15
This will work and give me the number of days between the end of the subscription and the current date.
BUT now, if I try to include the exact same request in a clause where for comparison:
SELECT s.name
from SUBSCRIPTION S
WHERE (TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) between 0 and 16
I will get an error: "ORA-01839 : date not valid for month specified".
Any help would be appreciated..
Somewhere in the table you have your date formatted in a different way from YYYY-MM-DD. In your first query you check a certain row (or a set of rows, s.id_acces = 15), which is probably ok, but in the second you scan through all the table.
Try finding these rows with something like,
select end_date from subscription
where not regexp_like(end_date, '[0-9]{4}-[0-9]{2}-[0-9]{2}')
Check your DD value (ie: day of the month). This value must be between 1 and the number of days in the month.
January - 1 to 31
February - 1 to 28 (1 to 29, if a leap year)
March - 1 to 31
April - 1 to 30
May - 1 to 31
June - 1 to 30
July - 1 to 31
August - 1 to 31
September - 1 to 30
October - 1 to 31
November - 1 to 30
December - 1 to 31
" the End_Date column type is a varchar and not a date.. I can't
change that."
If you can't change the date you'll have to chang3 the data. You can identify the rogue values with this function:
create or replace check_date_format (p_str in varchar2) return varchar2
is
d date;
begin
d := to_date(p_str,'YYYY-MM-DD');
return 'VALID';
exception
when others then
return 'INVALID';
end;
You can use this function in a query:
select sid, end_date
from SUBSCRIPTION
where check_date_format(end_date) != 'VALID';
Your choices are:
fix the data so all the dates are in the same format
fix the data and apply a check constraint to enforce future validity
write a bespoke MY_TO_DATE() function which takes a string and applies lots of different date format masks to it in the hope of getting a successful conversion.