Database date depending on SYSDATE with a condition - sql

I would like to select and return a number of rows depending on 'today's' date.
Something like...
SELECT * FROM my_table
WHERE
//some conditions AND
my_DATE BETWEEN trunc (sysdate, 'mm')/*current month*/ AND SYSDATE
However I would like to return:
Only rows for the last two months (excluding this month) if today's date 'dd' is less than 15
Return rows for the last two months (including this month) if today's date is >= 15
I am thinking of a case. Something like
WHERE
(CASE
when trunc (sysdate, 'dd') < 15 THEN
TO_CHAR(my_DATE, 'MMYYY') BETWEEN TO_CHAR((add_months(sysdate,-3)) AND TO_CHAR((add_months(sysdate,-1))
Any pointers are highly appreciated as I get acquainted with this arena. Thank you

I would do all the computation on sysdate, to help the optimizer user indexes:
where datecol >= add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then -2 else -1 end)
) and
datecol < add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then 0 else 1 end)
)
EDIT:
Based on the comment:
where datecol >= add_months(trunc(sysdate, 'MON'), -2) and
datecol < add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then 0 else 1 end)
)

If this represents sample data:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select * from test order by id;
ID DATUM
---------- ----------
1 20.12.2020
2 07.01.2021
3 15.02.2021
4 25.02.2021
5 10.03.2021
then see whether the following code makes sense. The whole date '&&par_sysdate' shuld be replaced by sysdate in real life; for testing purposes, I used a parameter as sysdate returns today's date.
First example: par_sysdate = 10.03.2021 (day is less than 15):
SQL> select t.id, t.datum
2 from test t
3 where t.datum >=
4 trunc(add_months(date '&&par_sysdate',
5 -case when to_number(to_char(date '&&par_sysdate', 'dd')) < 15 then 2
6 else 1
7 end
8 ), 'mm')
9 and t.datum < case when to_number(to_char(date '&&par_sysdate', 'dd')) < 15 then
10 trunc(date '&&par_sysdate', 'mm')
11 else date '&&par_sysdate'
12 end;
Enter value for par_sysdate: 2021-03-10
ID DATUM
---------- ----------
2 07.01.2021
3 15.02.2021
4 25.02.2021
Second example: using today's date (20.03.2021) where day is greater than 15:
SQL> undefine par_sysdate
SQL> /
Enter value for par_sysdate: 2021-03-20
ID DATUM
---------- ----------
3 15.02.2021
4 25.02.2021
5 10.03.2021
SQL>
Or, as I said, using sysdate:
SQL> select t.id, t.datum
2 from test t
3 where t.datum >=
4 trunc(add_months(sysdate,
5 -case when to_number(to_char(sysdate, 'dd')) < 15 then 2
6 else 1
7 end
8 ), 'mm')
9 and t.datum < case when to_number(to_char(sysdate, 'dd')) < 15 then
10 trunc(sysdate, 'mm')
11 else sysdate
12 end;
ID DATUM
---------- ----------
3 15.02.2021
4 25.02.2021
5 10.03.2021
SQL>

Your requirements are not wholly clear, but I think you want something like this:
select t.*
from mytable t
where (to_number(to_char(sysdate, 'dd')) < 15
and t.dt >= add_months(trunc(sysdate, 'mm'),-3)
and t.dt < trunc(sysdate, 'mm')
)
or (to_number(to_char(sysdate, 'dd')) >= 15
and t.dt >= add_months(trunc(sysdate, 'mm'),-2)
and t.dt <= last_day(sysdate)
)
I have put a demo version of this code on db<>fiddle with an affordance for changing the date of today instead of using sysdate.
the previous months should be FULL not previous months based on today's date.
To get full months, truncate the date using the 'mm' mask, which returns the first of the month.

Related

Generating Rows for each date from start date to end date [duplicate]

This question already has answers here:
Row for each date from start date to end date
(2 answers)
Generate series of months for every row in Oracle
(1 answer)
Create all months list from a date column in ORACLE SQL
(3 answers)
Closed 2 years ago.
What I'm trying to do is take a record that looks like this:
Start_DT End_DT ID
4/5/2013 10/9/2013 1
and change it to look like this:
DT ID
4/1/2013 1
5/1/2013 1
6/1/2013 1
7/1/2013 1
8/1/2013 1
9/1/2013 1
10/1/2013 1
I am having difficult time making this work. Any help would be appreciated.
Thanks
You can use a recursive CTE for this:
with dates (dte, end_mon, id) as (
select trunc(start_dt, 'MON') as dte, trunc(end_dt, 'MON') as end_mon, id
from t
union all
select dte + interval '1' month, end_mon, id
from dates
where dte < end_mon
)
select *
from dates;
Here is a db<>fiddle.
Alternatively, but similarly - row generator technique is what you're looking for.
(My date format is DD.MM.YYYY; can't tell for yours so I'm just guessing as both of your dates can be read in two ways, e.g. 4/5/2013 - is it 4th of May or 5th of April?).
SQL> with dates (start_dt, end_dt, id) as
2 (select date '2013-05-04', date '2013-09-10', 1 from dual)
3 select start_dt + level - 1 dt, id
4 from dates
5 connect by level <= end_dt - start_dt + 1
6 order by dt;
DT ID
---------- ----------
04.05.2013 1
05.05.2013 1
06.05.2013 1
07.05.2013 1
08.05.2013 1
<snip>
06.09.2013 1
07.09.2013 1
08.09.2013 1
09.09.2013 1
10.09.2013 1
130 rows selected.
SQL>
This can be achieved by using CONNECT BY to generate the months between each date.
Query
WITH
dates (start_date, end_date, id)
AS
(SELECT TO_DATE ('4/5/2013', 'MM/DD/YYYY'), TO_DATE ('10/9/2013', 'MM/DD/YYYY'), 1
FROM DUAL)
SELECT TO_CHAR (ADD_MONTHS (TRUNC (start_date, 'MM'), LEVEL - 1), 'MM/DD/YYYY') AS dt, id
FROM dates
CONNECT BY ADD_MONTHS (TRUNC (start_date, 'MM'), LEVEL - 1) <= TRUNC (end_date, 'MM');
Result
DT ID
_____________ _____
04/01/2013 1
05/01/2013 1
06/01/2013 1
07/01/2013 1
08/01/2013 1
09/01/2013 1
10/01/2013 1

Problem with getting the quarter from a date in Oracle

I've written a query to get the start date of the quarters from current year and previous year by using the sysdate.
eg. Today falls in the 1st quarter of the year, therefore I only want to get the start date of 1st quarter of last year and this year.
If I'm on December (which is in the 4th quarter), I want to get the start dates of 8 quarters (4 from last year, 4 from this year.)
select b.dt,
to_number(to_char(SYSDATE, 'Q')),
to_number(to_char(b.dt, 'Q'))
from dual a,
(select add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12),
'yyyy'),
(rownum - 1) * 3) dt
from all_objects
where rownum <= 8
and add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12),
'yyyy'),
(rownum - 1) * 3) <= SYSDATE
and to_number(to_char(SYSDATE, 'Q')) >=
to_number(to_char(add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE,
'MM'),
-12),
'yyyy'),
(rownum - 1) * 3),
'Q'))) b
This query only returns the start date of 1st quarter of last year. I expect to get the start date of the 1st quarter of this year as well.
Here's one option; see comments within the code.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with
2 -- this mimics SYSDATE
3 today (datum) as
4 (select date '&date_literal' from dual),
5 -- which quarter does DATUM belong to? Find 1st day in "this" and "previous" year
6 quart as
7 (select trunc(datum, 'yyyy') this,
8 trunc(add_months(datum, -12), 'yyyy') previous,
9 to_char(datum, 'q') quart from today)
10 -- the fina result
11 select add_months(this, (level - 1) * 3) result
12 from quart
13 connect by level <= quart
14 union all
15 select add_months(previous, (level - 1) * 3) result
16 from quart
17 connect by level <= quart;
Enter value for date_literal: 2019-03-24
RESULT
----------
01.01.2019
01.01.2018
SQL> /
Enter value for date_literal: 2019-08-13
RESULT
----------
01.01.2019
01.04.2019
01.07.2019
01.01.2018
01.04.2018
01.07.2018
6 rows selected.
SQL>

Oracle date as fraction of month

I would like to get a table of months between two dates with a fraction of each month that the two dates cover.
For example with a start date of 15/01/2017 and end date of 01/03/2017 it would output:
01/2017 : 0.5483..
02/2017 : 1
03/2017: 0.0322..
where for January and March the calculations are 17/31 and 1/31 respectively. I currently have the query:
WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE(:givenEndDate,'dd/mm/yyyy'), 'MON')
)
select * from dates_between
This outputs each month between two dates and formats it to the start of the month. I just need another column to give me the fraction the start and end dates cover. I'm not sure of a way to do this without it getting messy.
The months_between() function "calculates the fractional portion of the result based on a 31-day month". That means that if your range starts or ends in a month that doesn't have 31 days, the fraction you get might not be quite what you expect:
select months_between(date '2017-04-02', date '2017-04-01') as calc from dual
CALC
----------
.0322580645
... which is 1/31, not 1/30. To get 0.0333... instead you'd need to calculate the number of days in each month, at least for the first and last month. This uses a recursive CTE (11gR2+) to get the months, using a couple of date ranges provided by another CTE as a demo to show the difference (you can use a hierarchical query too of course):
with ranges (id, start_date, end_date) as (
select 1, date '2017-01-15', date '2017-03-01' from dual
union all select 2, date '2017-01-31', date '2017-03-01' from dual
union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
select id,
trunc(start_date, 'MM'),
extract(day from last_day(start_date)),
start_date,
end_date
from ranges
union all
select id,
month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select id,
to_char(month_start, 'YYYY-MM-DD') as month_start,
month_days,
case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end as range_days,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by id, month_start;
which gets:
ID MONTH_STAR MONTH_DAYS RANGE_DAYS FRACTION
------ ---------- ---------- ---------- --------
1 2017-01-01 31 17 0.5483
1 2017-02-01 28 28 1
1 2017-03-01 31 1 0.0322
2 2017-01-01 31 1 0.0322
2 2017-02-01 28 28 1
2 2017-03-01 31 1 0.0322
3 2017-02-01 28 1 0.0357
3 2017-03-01 31 31 1
3 2017-04-01 30 1 0.0333
The first CTE ranges is just the demo data. The second, recursive, CTE months generates the start and number of days in each month, while keeping track of the original range dates too. The final query just calculates the fractions based on the number of days in the month in the range against the number of days in that month overall.
The month_days and range_days are only shown in the output so you can see what the calculation is based on, you can obviously omit those from your actual result, and format the month start date however you want.
With your original single pair of bind variables the equivalent would be:
with months (month_start, month_days, range_start, range_end) as (
select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
to_date(:givenstartdate, 'DD/MM/YYYY'),
to_date(:givenenddate, 'DD/MM/YYYY')
from dual
union all
select month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by month_start;
MONTH FRACTION
------- --------
01/2017 0.5483
02/2017 1
03/2017 0.0322
Here's how I would do it (n.b. I have expanded your dates_between to work against multiple rows, purely for demonstration purposes. If you're only working with a single set of parameters, you wouldn't need to do that):
WITH params AS (SELECT 1 ID, '15/01/2017' givenstartdate, '01/03/2017' givenenddate FROM dual UNION ALL
SELECT 2 ID, '15/01/2017' givenstartdate, '23/01/2017' givenenddate FROM dual UNION ALL
SELECT 3 ID, '01/01/2017' givenstartdate, '07/04/2017' givenenddate FROM dual),
dates_between AS (SELECT ID,
to_date(givenstartdate, 'dd/mm/yyyy') givenstartdate,
to_date(givenenddate, 'dd/mm/yyyy') givenenddate,
add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) start_of_month,
last_day(add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1)) end_of_month
FROM params
CONNECT BY add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) <=
trunc(to_date(givenenddate, 'dd/mm/yyyy'), 'MON')
AND PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL)
SELECT ID,
givenstartdate,
givenenddate,
start_of_month date_out,
end_of_month,
months_between(LEAST(givenenddate, end_of_month) + 1, GREATEST(start_of_month, givenstartdate))
FROM dates_between;
ID GIVENSTARTDATE GIVENENDDATE DATE_OUT END_OF_MONTH DIFF
1 15/01/2017 01/03/2017 01/01/2017 31/01/2017 0.54838709
1 15/01/2017 01/03/2017 01/02/2017 28/02/2017 1
1 15/01/2017 01/03/2017 01/03/2017 31/03/2017 0.03225806
2 15/01/2017 23/01/2017 01/01/2017 31/01/2017 0.29032258
3 01/01/2017 07/04/2017 01/01/2017 31/01/2017 1
3 01/01/2017 07/04/2017 01/02/2017 28/02/2017 1
3 01/01/2017 07/04/2017 01/03/2017 31/03/2017 1
3 01/01/2017 07/04/2017 01/04/2017 30/04/2017 0.22580645
N.B. You may need to add a case statement to decide whether you want to add 1 or not to the diff calculation, based on your requirements.
Try this
For first month, I have calculated remaining days / total days and for last month, I subtracted it by 1 to get days passed / total days.
DBFiddle Demo
WITH tbl AS
(SELECT date '2017-01-15' AS givenStartDate
,date '2017-03-01' AS givenEndDate
FROM dual
)
SELECT ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) AS date_out ,
CASE
WHEN
rownum - 1 = 0
THEN months_between(last_day(givenStartDate), givenStartDate)
WHEN ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) = TRUNC(givenEndDate, 'MON')
THEN 1 - (months_between(last_day(givenEndDate), givenEndDate))
ELSE 1
END AS perc
FROM tbl
CONNECT BY ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1)
<= TRUNC(givenEndDate, 'MON');
Output
+-----------+-------------------------------------------+
| DATE_OUT | PERC |
+-----------+-------------------------------------------+
| 01-JAN-17 | .5161290322580645161290322580645161290323 |
| 01-FEB-17 | 1 |
| 01-MAR-17 | .0322580645161290322580645161290322580645 |
+-----------+-------------------------------------------+

How can I generate 4-week ranges of dates?

I have to generate date range for 1 year in gap of 4 weeks ( or 28 days ) from a fixed date backward and going forward. For example I have DATE '2016-02-20'. I need to generate the below.
Start date = Sunday , End-date = Saturday
No Start_date End_date
==== ========= =======
1 1/24/2016 2/20/2016
2 12/27/2015 1/23/2016
3 11/29/2015 12/26/2015
4 .....
13 2/22/2015 3/21/2015
14 1/25/2015 2/21/2015
But,when 03/20/2016(Sunday) comes,it should add
1. 2/21/2016 3/19/2016 & remove 14. 1/25/2015 2/21/2015
and so on after every 4 weeks.
I have written the below, but I need help to iterate in minimal code( if possible.)
SELECT LEVEL,
DATE '2016-02-20'-27*LEVEL-LEVEL+1 AS start_date,
DATE '2016-02-20'-28*(LEVEL-1) AS end_date
FROM DUAL
Connect BY LEVEL < 15;
It seems you want to have a rolling window of a year's worth of four-week ranges, based from the current date. To do that you need a fixed known period start (or end) date you can work from. Picking one that happens to be January 1st you can do:
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
Which will find 54 periods up to today. On March 21st it will find 55 periods, etc. You only want those that are in the last year, so use that as an inline view and restrict the range:
SELECT ROW_NUMBER() OVER (ORDER BY start_date DESC) AS no, start_date, end_date
FROM (
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE end_date >= ADD_MONTHS(TRUNC(sysdate), -12)
ORDER BY start_date DESC;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
11 04/19/2015 05/16/2015
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
The ROW_NUMBER() just generates your NO column, as the LEVEL is now in the wrong order.
If you always want exactly 14 rows in the result set you can move the ROW_NUMBER() into the inline view:
SELECT no, start_date, end_date
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY LEVEL DESC) AS no,
DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE no <= 14
ORDER BY no;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
14 01/25/2015 02/21/2015
14 rows selected

sql oracle case when date

This is what I got so far:
select
to_char(sysdate, 'yyyy') Time
from
dual;
Which gives me:
TIME
2015
Its working until this point.
I would like to add
if the month is >= 7 I get as output 01.07.current year
if the month is <= 7 I get as output 01.07.(current year - 1 year)
Any ideas how to handle this? I thought about CASE WHEN but I dont get know how.
Thanks!
A simple CASE expression would do the job.
For example,
SQL> SELECT
2 '01.07.' ||
3 CASE
4 WHEN TO_CHAR(SYSDATE, 'MM') < '07'
5 THEN
6 TO_CHAR(SYSDATE, 'YYYY')
7 ELSE
8 TO_CHAR(add_months(SYSDATE,-12), 'YYYY')
9 END case_date
10 FROM dual;
CASE_DATE
----------
01.07.2015
SQL>
To keep it even more precise, you could keep the common value outside the CASE expression:
SQL> SELECT '01.07.'
2 ||
3 CASE
4 WHEN TO_CHAR(SYSDATE, 'MM') < '07'
5 THEN TO_CHAR(SYSDATE, 'YYYY')
6 ELSE TO_CHAR(add_months(SYSDATE,-12), 'YYYY')
7 END case_date
8 FROM dual;
CASE_DATE
----------
01.07.2015
SQL>
Using extract more readable
SELECT
to_date((CASE
WHEN extract(MONTH FROM SYSDATE) >= 7 THEN
0
ELSE
-1
END) + extract(YEAR FROM SYSDATE) || '07-01', 'yyyy-mm-dd') END
FROM dual