Rolling 6 Months dates in oracle - oracle

I wanna roll six months dates starting from 2016 to current date, output should be like below
Year Start_Date End_Date
2016 1/1/2016 30/6/2016
2016 1/7/2016 31/12/2016
2017 ... like this for 2017 & 2018
I tried like fetch first date of the year like
SELECT TRUNC(to_date(Date_key, 'YYYYMMDD'), 'YEAR')
FROM Table;
and adding six months from first date, but in this case end date will be problem. Is there a function I can do this without loop?

Something like this?
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select extract (year from add_months (date '2016-01-01', (level - 1) * 6)) year,
2 add_months (date '2016-01-01', (level - 1) * 6) start_date,
3 add_months (date '2016-01-01', (level) * 6) - 1 end_date
4 from dual
5 connect by level <= (extract (year from sysdate) - 2016 + 1) * 2;
YEAR START_DATE END_DATE
---------- ---------- ----------
2016 01.01.2016 30.06.2016
2016 01.07.2016 31.12.2016
2017 01.01.2017 30.06.2017
2017 01.07.2017 31.12.2017
2018 01.01.2018 30.06.2018
2018 01.07.2018 31.12.2018
6 rows selected.
SQL>

You can also try this,
SELECT :p_from_year + CEIL(ROWNUM/2)-1,
ADD_MONTHS(TRUNC(TO_DATE(:p_from_year + CEIL(rownum/2)-1, 'YYYY'), 'YYYY'),6-(MOD(rownum, 2)*6)) from_date,
ADD_MONTHS(TRUNC(TO_DATE(:p_from_year + CEIL(rownum/2)-1, 'YYYY'), 'YYYY'),12-(MOD(rownum, 2)*6))-1 to_date
FROM dual
CONNECT BY CEIL(rownum/2)-1 <= (:p_to_year - :p_from_year)

Related

Oracle: Return the specific records based on one column date

I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle

Calendar Last day of Month Oracle [duplicate]

This question already has answers here:
Oracle How to list last days of months between 2 dates
(5 answers)
Closed 1 year ago.
I need to create a calendar in Oracle with only last days of month between two dates.
I tried to do with this but it creates a calendar with all days between two dates and I only need the last dayS of the month.
begin
begin_date := TO_NUMBER(TO_CHAR(TO_DATE('2021-01-01', 'yyyy-MM-dd'), 'j'));
end_date := TO_NUMBER(TO_CHAR(ADD_MONTHS(LAST_DAY(TO_DATE(sysdate, 'yyyy-MM-dd')),-1), 'j'));
WITH calendar AS (
SELECT to_date(begin_date, 'mm/dd/yyyy') + ROWNUM - 1 c_date
FROM dual
CONNECT BY LEVEL <= to_date(end_date, 'mm/dd/yyyy')
- to_date(begin_date, 'mm/dd/yyyy') + 1
)
SELECT c_date "date", ID
FROM (SELECT c.c_date, EXPE.ID AS ID
FROM EXPEDIENTE EXPE, calendar c
WHERE EXPE.ID=1)
ORDER BY 1,2;
How can i do that?
No need for PL/SQL.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with
2 period as
3 (select date '2020-11-07' start_date,
4 date '2021-04-15' end_date
5 from dual
6 )
7 select last_day(add_months(start_date, level - 1)) mon
8 from period
9 connect by level <= months_between(end_date, start_date) + 1
10 order by mon;
MON
----------
30.11.2020
31.12.2020
31.01.2021
28.02.2021
31.03.2021
30.04.2021
6 rows selected.
SQL>

DATE DIFF IN YEARS

How to find date diff in years in 'YYYY-MM-DD' format .
I am using below two querues:
SELECT TRUNC(TO_NUMBER(SYSDATE - TO_DATE('1994-08-13')) / 365.25) AS AGE FROM DUAL;
ORA-01861: literal does not match format string
SELECT (TO_DATE(SYSDATE, 'YYYY-MM-DD') - TO_NUMBER('1994-08-13', 'YYYY-MM-DD')) FROM DUAL;
O/P-: -727738
Desired o/p: 26
I suggest to use "months_between" function because it takes leap years into account (months_between wants 2 dates as parameters):
select months_between(sysdate, to_date('1994-08-13', 'YYYY-MM-DD'))/12 from dual;
26,4729751904122
of course, if you need to truncate:
select trunc(months_between(sysdate, to_date('1994-08-13', 'YYYY-MM-DD'))/12) from dual;
26
Just subtract one date from the other:
sysdate - date '1994-08-13'
Or
sysdate - to_date('1994-08-13', 'yyyy-mm-dd')
returns the number of days between the two dates.
So rewrite your first query to:
select (sysdate - date '1994-08-13') / 365.25 as age
from dual
So, extract years from these dates and subtract them:
SQL> select extract (year from sysdate) - extract(year from date '1994-08-13') diff
2 from dual;
DIFF
----------
27
SQL>
Because,
SQL> select 2021 - 1994 diff from dual;
DIFF
----------
27
SQL>

Query for a Specific Time

How can i return data from 2 days ago at 11:00:00 PM to all of yesterday ending at 11:59:59 PM?
I currently have only yesterdays date query:
SELECT *
FROM table
WHERE code = '00'
AND to_char(RQST_TMSTMP, 'yyyy-mm-dd') = to_char(sysdate-1, 'yyyy-mm-dd')
How about
select *
from table
where code = '00'
and rqst_tmstmp >= trunc(sysdate - 2) + 11/24
and rqst_tmstmp <= trunc(sysdate);
Here's what all those TRUNCs represent (so that you could follow what's going on):
SQL> select sysdate, -- today, right now
2 trunc(sysdate) ts, -- today at midnight
3 trunc(sysdate - 2) ts_2, -- 2 days ago at midnight
4 trunc(sysdate - 2) + 11/24 ts_2_11 -- 2 days ago at midnight + 11 hours
5 from dual;
SYSDATE TS TS_2 TS_2_11
---------------- ---------------- ---------------- ----------------
29.11.2018 17:07 29.11.2018 00:00 27.11.2018 00:00 27.11.2018 11:00
SQL>
If the column is capturing hours & minutes then use,
TO_CHAR(RQST_TMSTMP,'DD-MM-YY HH24:MI')

How to convert week number to date range in Oracle?

In Oracle we get week number from following query:
select to_char(TO_DATE(SYSDATE,'DD-MM-YY'),'IW') from dual
I want to get date range of given week number, for example for week no:1 date range is 01-01-2017 to 08-01-2017.
is there any way to get the date range for given week number?
"week no:1 date range is 01-01-2017 to 08-01-2017"
No it isn't. You're confusing 'IW' (which runs MON - SUN) with 'WW' which runs from the first day of the year:
SQL> with dts as (
2 select date '2017-01-01' + (level-1) as dt
3 from dual
4 connect by level <= 8
5 )
6 select dt
7 , to_char(dt, 'DY') as dy_dt
8 , to_char(dt, 'IW') as iw_dt
9 , to_char(dt, 'WW') as ww_dt
10 from dts
11 order by 1;
DT DY_DT IW WW
--------- ------------ -- --
01-JAN-17 SUN 52 01
02-JAN-17 MON 01 01
03-JAN-17 TUE 01 01
04-JAN-17 WED 01 01
05-JAN-17 THU 01 01
06-JAN-17 FRI 01 01
07-JAN-17 SAT 01 01
08-JAN-17 SUN 01 02
8 rows selected.
SQL>
However, it's easy enough to generate a range for the the IW week number. You need to multiple the IW number by 7 which you can convert to a date with the day of year mask. Then you can use next_day() function to get the previous Monday and the next Sunday relative to that date:
SQL> with tgt as (
2 select to_date( &iw *7, 'DDD') as dt from dual
3 )
4 select next_day(dt-8, 'mon') as start_date
5 , next_day(dt, 'sun') as end_date
6* from tgt;
Enter value for iw: 23
old 2: select to_date( &iw *7, 'DDD') as dt from dual
new 2: select to_date( 23 *7, 'DDD') as dt from dual
START_DAT END_DATE
--------- ---------
05-JUN-17 11-JUN-17
SQL>
Obvious this solution uses my NLS Settings (English): you may need to tweak the solution if you use different settings.
These kinds of problems are easy to solve with calendar tables.
The following query builds on the assumption (ISO 8601) that the 4th of January is present in the first week in a year. Therefore I can generate a valid date in the first week of any year by constructing the 4th of January like: to_date(year || '-01-04', 'yyyy-mm-dd'). Oracle will tell me the day of week (sun=1, sat=7) for any date using to_char(date, 'D'). The 4th of JAN 2017 happens to be a wednesday (day 4). Subtracting 3 days will give me the first day (sunday) of the first week of the year.
Now it is easy to find the start day in any given week in the year by simply adding 7 days for each week (not counting the first week).
with weeks as(
select 2017 as year, 39 as week from dual union all
select 2017 as year, 40 as week from dual union all
select 2018 as year, 35 as week from dual
)
select a.*
,to_date(year || '-01-04', 'yyyy-mm-dd') - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (week-1)) as start_day
,to_date(year || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D')) + (7 * (week-1)) as end_day
from weeks a;
Edit: These are the "convert" expressions you need to convert from week to date range. Note that 2017 and 39 are variable...
start date = to_date(2017 || '-01-04', 'yyyy-mm-dd') - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (39-1))
end date = to_date(2017 || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D')) + (7 * (39-1))
Here's a query to list all ISO weeks from 2001 to 2099
SELECT TO_CHAR(TRUNC(dt, 'IW') + 6, 'IYYY-IW') AS week,
TRUNC(dt, 'IW') AS start_date,
TRUNC(dt, 'IW') + 6 AS end_date
FROM (SELECT DATE '2001-01-01' + ((LEVEL - 1) * 7) dt
FROM DUAL
CONNECT BY LEVEL <= 5165);
For the first and last week of year this query needs some CASE logic, but for other weeks works good. This solution use current NLS settings.
select to_char( start_of_week, 'day dd.mm.yyyy' ) start_of_week,
to_char( start_of_week + 6, 'day dd.mm.yyyy' ) end_of_week
from
(
select trunc( date '2017-01-01' + 38*7 , 'day') start_of_week
from dual
)
1) date '2017-01-01' - in what year we look for weeks
or it may be trunc (sysdate, 'YEAR') to take first day of current year
2) date '2017-01-01' + 38*7 - jump to 38th week
3) trunc ( ... , 'day' ) - gives date of first day of the week
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions201.htm
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm
I use this function:
FUNCTION ISOWeekDate(WEEK INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF WEEK > 53 OR WEEK < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( WEEK - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;
Please note, according to my comment it is ambiguous if you only provide a week number without a year. The function returns the first day of given ISO Week.

Resources